From 551ab5073506f7e93ec7c54b9df058b94fca3841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B2=BD=EC=A2=85?= Date: Fri, 8 May 2026 16:31:17 +0900 Subject: [PATCH] remove files --- .agents/plugins/marketplace.json | 20 - .agents/skills/harness-review/SKILL.md | 59 - .../skills/harness-review/agents/openai.yaml | 4 - .agents/skills/harness-workflow/SKILL.md | 154 - .../harness-workflow/agents/openai.yaml | 4 - .codex/agents/conversion-architect.toml | 12 - .../agents/formula-pipeline-specialist.toml | 12 - .codex/agents/harness-reviewer.toml | 11 - .../layout-table-figure-specialist.toml | 11 - .codex/agents/pdf-toolchain-researcher.toml | 12 - .codex/agents/phase-planner.toml | 12 - .codex/agents/quality-evaluator.toml | 12 - .codex/agents/sample-corpus-analyst.toml | 12 - .codex/commands/conversion-policy-review.md | 26 - .codex/commands/env-check.md | 28 - .codex/commands/model-cache-check.md | 25 - .codex/commands/phase-draft.md | 28 - .codex/commands/quality-plan.md | 26 - .codex/commands/sample-audit.md | 27 - .codex/commands/sprint-contract.md | 31 - .codex/commands/status.md | 27 - .codex/config.toml | 9 - .codex/hooks.json | 40 - .codex/hooks/drift_policy.py | 81 - .codex/hooks/handoff_policy.py | 95 - .codex/hooks/pre_tool_use_policy.py | 50 - .codex/hooks/stop_continue.py | 55 - .../skills/conversion-architecture/SKILL.md | 23 - .../agents/openai.yaml | 4 - .codex/skills/formula-quality/SKILL.md | 24 - .../skills/formula-quality/agents/openai.yaml | 4 - .codex/skills/markdown-quality/SKILL.md | 27 - .../markdown-quality/agents/openai.yaml | 4 - .codex/skills/pdf-toolchain/SKILL.md | 23 - .../skills/pdf-toolchain/agents/openai.yaml | 4 - .codex/skills/sample-corpus/SKILL.md | 27 - .../skills/sample-corpus/agents/openai.yaml | 4 - .codex/skills/windows-runtime/SKILL.md | 23 - .../skills/windows-runtime/agents/openai.yaml | 4 - .gitattributes | 1 - .gitignore | 21 - AGENTS.md | 235 - PLAN.md | 151 - PROGRESS.md | 177 - README.md | 63 - docs/ADR.md | 142 - docs/ARCHITECTURE.md | 152 - docs/CONVERSION_POLICY.md | 91 - docs/HARNESS.md | 114 - docs/IMPLEMENTATION_PLAN.md | 121 - docs/PRD.md | 88 - docs/TOOLCHAIN.md | 90 - docs/UI_GUIDE.md | 39 - phases/0-harness-foundation/index.json | 30 - phases/0-harness-foundation/step0.md | 63 - phases/0-harness-foundation/step1.md | 59 - phases/0-harness-foundation/step2.md | 63 - phases/0-harness-foundation/step3.md | 63 - phases/1-core-runtime-contracts/index.json | 30 - phases/1-core-runtime-contracts/step0.md | 38 - phases/1-core-runtime-contracts/step1.md | 38 - phases/1-core-runtime-contracts/step2.md | 39 - phases/1-core-runtime-contracts/step3.md | 39 - phases/2-marker-adapter/index.json | 26 - phases/2-marker-adapter/step0.md | 38 - phases/2-marker-adapter/step1.md | 38 - phases/2-marker-adapter/step2.md | 39 - phases/2-marker-adapter/step3.md | 38 - phases/3-formula-pipeline/index.json | 26 - phases/3-formula-pipeline/step0.md | 37 - phases/3-formula-pipeline/step1.md | 38 - phases/3-formula-pipeline/step2.md | 38 - phases/3-formula-pipeline/step3.md | 37 - phases/4-semantic-enrichment/index.json | 26 - phases/4-semantic-enrichment/step0.md | 38 - phases/4-semantic-enrichment/step1.md | 37 - phases/4-semantic-enrichment/step2.md | 37 - phases/4-semantic-enrichment/step3.md | 38 - phases/5-markdown-rendering-assets/index.json | 26 - phases/5-markdown-rendering-assets/step0.md | 38 - phases/5-markdown-rendering-assets/step1.md | 37 - phases/5-markdown-rendering-assets/step2.md | 39 - phases/5-markdown-rendering-assets/step3.md | 39 - phases/6-cli-runtime-resume/index.json | 31 - phases/6-cli-runtime-resume/step0.md | 38 - phases/6-cli-runtime-resume/step1.md | 37 - phases/6-cli-runtime-resume/step2.md | 37 - phases/6-cli-runtime-resume/step3.md | 39 - phases/6-cli-runtime-resume/step4.md | 38 - phases/7-mvp-quality-hardening/index.json | 26 - phases/7-mvp-quality-hardening/step0.md | 38 - phases/7-mvp-quality-hardening/step1.md | 37 - phases/7-mvp-quality-hardening/step2.md | 37 - phases/7-mvp-quality-hardening/step3.md | 37 - phases/8-release-docs-packaging/index.json | 26 - phases/8-release-docs-packaging/step0.md | 36 - phases/8-release-docs-packaging/step1.md | 38 - phases/8-release-docs-packaging/step2.md | 36 - phases/8-release-docs-packaging/step3.md | 36 - phases/9-pyqt-thin-client/index.json | 26 - phases/9-pyqt-thin-client/step0.md | 38 - phases/9-pyqt-thin-client/step1.md | 36 - phases/9-pyqt-thin-client/step2.md | 36 - phases/9-pyqt-thin-client/step3.md | 36 - phases/index.json | 44 - .../.codex-plugin/plugin.json | 22 - .../harness-engineering/agents/openai.yaml | 4 - .../harness-engineering/commands/harness.md | 44 - .../harness-engineering/commands/review.md | 38 - pyproject.toml | 12 - requirements.txt | 27 - .../2007쉘구조물의유한요소해석에대하여.pdf | 33600 ---------------- ...FourNodeQuadrilateralShellElementMITC4.pdf | Bin 408138 -> 0 bytes samples/MITC공부.pdf | Bin 444039 -> 0 bytes samples/metadata.json | 91 - ...요소해석법을이용한쉘구조물의동적좌굴해석.pdf | Bin 1616164 -> 0 bytes scripts/execute.py | 424 - scripts/test_execute.py | 562 - scripts/test_validate_workspace.py | 92 - scripts/validate_workspace.py | 142 - src/pdftomd/__init__.py | 47 - src/pdftomd/models.py | 235 - src/pdftomd/options.py | 67 - src/pdftomd/paths.py | 87 - src/pdftomd/preanalysis.py | 100 - src/pdftomd/quality.py | 374 - src/pdftomd/runtime_contracts.py | 80 - tests/conftest.py | 11 - tests/test_models.py | 140 - tests/test_options.py | 49 - tests/test_paths.py | 67 - tests/test_preanalysis.py | 93 - tests/test_quality.py | 166 - tests/test_runtime_contracts.py | 50 - tests/test_sample_metadata.py | 87 - 135 files changed, 41205 deletions(-) delete mode 100644 .agents/plugins/marketplace.json delete mode 100644 .agents/skills/harness-review/SKILL.md delete mode 100644 .agents/skills/harness-review/agents/openai.yaml delete mode 100644 .agents/skills/harness-workflow/SKILL.md delete mode 100644 .agents/skills/harness-workflow/agents/openai.yaml delete mode 100644 .codex/agents/conversion-architect.toml delete mode 100644 .codex/agents/formula-pipeline-specialist.toml delete mode 100644 .codex/agents/harness-reviewer.toml delete mode 100644 .codex/agents/layout-table-figure-specialist.toml delete mode 100644 .codex/agents/pdf-toolchain-researcher.toml delete mode 100644 .codex/agents/phase-planner.toml delete mode 100644 .codex/agents/quality-evaluator.toml delete mode 100644 .codex/agents/sample-corpus-analyst.toml delete mode 100644 .codex/commands/conversion-policy-review.md delete mode 100644 .codex/commands/env-check.md delete mode 100644 .codex/commands/model-cache-check.md delete mode 100644 .codex/commands/phase-draft.md delete mode 100644 .codex/commands/quality-plan.md delete mode 100644 .codex/commands/sample-audit.md delete mode 100644 .codex/commands/sprint-contract.md delete mode 100644 .codex/commands/status.md delete mode 100644 .codex/config.toml delete mode 100644 .codex/hooks.json delete mode 100644 .codex/hooks/drift_policy.py delete mode 100644 .codex/hooks/handoff_policy.py delete mode 100644 .codex/hooks/pre_tool_use_policy.py delete mode 100644 .codex/hooks/stop_continue.py delete mode 100644 .codex/skills/conversion-architecture/SKILL.md delete mode 100644 .codex/skills/conversion-architecture/agents/openai.yaml delete mode 100644 .codex/skills/formula-quality/SKILL.md delete mode 100644 .codex/skills/formula-quality/agents/openai.yaml delete mode 100644 .codex/skills/markdown-quality/SKILL.md delete mode 100644 .codex/skills/markdown-quality/agents/openai.yaml delete mode 100644 .codex/skills/pdf-toolchain/SKILL.md delete mode 100644 .codex/skills/pdf-toolchain/agents/openai.yaml delete mode 100644 .codex/skills/sample-corpus/SKILL.md delete mode 100644 .codex/skills/sample-corpus/agents/openai.yaml delete mode 100644 .codex/skills/windows-runtime/SKILL.md delete mode 100644 .codex/skills/windows-runtime/agents/openai.yaml delete mode 100644 .gitattributes delete mode 100644 .gitignore delete mode 100644 AGENTS.md delete mode 100644 PLAN.md delete mode 100644 PROGRESS.md delete mode 100644 README.md delete mode 100644 docs/ADR.md delete mode 100644 docs/ARCHITECTURE.md delete mode 100644 docs/CONVERSION_POLICY.md delete mode 100644 docs/HARNESS.md delete mode 100644 docs/IMPLEMENTATION_PLAN.md delete mode 100644 docs/PRD.md delete mode 100644 docs/TOOLCHAIN.md delete mode 100644 docs/UI_GUIDE.md delete mode 100644 phases/0-harness-foundation/index.json delete mode 100644 phases/0-harness-foundation/step0.md delete mode 100644 phases/0-harness-foundation/step1.md delete mode 100644 phases/0-harness-foundation/step2.md delete mode 100644 phases/0-harness-foundation/step3.md delete mode 100644 phases/1-core-runtime-contracts/index.json delete mode 100644 phases/1-core-runtime-contracts/step0.md delete mode 100644 phases/1-core-runtime-contracts/step1.md delete mode 100644 phases/1-core-runtime-contracts/step2.md delete mode 100644 phases/1-core-runtime-contracts/step3.md delete mode 100644 phases/2-marker-adapter/index.json delete mode 100644 phases/2-marker-adapter/step0.md delete mode 100644 phases/2-marker-adapter/step1.md delete mode 100644 phases/2-marker-adapter/step2.md delete mode 100644 phases/2-marker-adapter/step3.md delete mode 100644 phases/3-formula-pipeline/index.json delete mode 100644 phases/3-formula-pipeline/step0.md delete mode 100644 phases/3-formula-pipeline/step1.md delete mode 100644 phases/3-formula-pipeline/step2.md delete mode 100644 phases/3-formula-pipeline/step3.md delete mode 100644 phases/4-semantic-enrichment/index.json delete mode 100644 phases/4-semantic-enrichment/step0.md delete mode 100644 phases/4-semantic-enrichment/step1.md delete mode 100644 phases/4-semantic-enrichment/step2.md delete mode 100644 phases/4-semantic-enrichment/step3.md delete mode 100644 phases/5-markdown-rendering-assets/index.json delete mode 100644 phases/5-markdown-rendering-assets/step0.md delete mode 100644 phases/5-markdown-rendering-assets/step1.md delete mode 100644 phases/5-markdown-rendering-assets/step2.md delete mode 100644 phases/5-markdown-rendering-assets/step3.md delete mode 100644 phases/6-cli-runtime-resume/index.json delete mode 100644 phases/6-cli-runtime-resume/step0.md delete mode 100644 phases/6-cli-runtime-resume/step1.md delete mode 100644 phases/6-cli-runtime-resume/step2.md delete mode 100644 phases/6-cli-runtime-resume/step3.md delete mode 100644 phases/6-cli-runtime-resume/step4.md delete mode 100644 phases/7-mvp-quality-hardening/index.json delete mode 100644 phases/7-mvp-quality-hardening/step0.md delete mode 100644 phases/7-mvp-quality-hardening/step1.md delete mode 100644 phases/7-mvp-quality-hardening/step2.md delete mode 100644 phases/7-mvp-quality-hardening/step3.md delete mode 100644 phases/8-release-docs-packaging/index.json delete mode 100644 phases/8-release-docs-packaging/step0.md delete mode 100644 phases/8-release-docs-packaging/step1.md delete mode 100644 phases/8-release-docs-packaging/step2.md delete mode 100644 phases/8-release-docs-packaging/step3.md delete mode 100644 phases/9-pyqt-thin-client/index.json delete mode 100644 phases/9-pyqt-thin-client/step0.md delete mode 100644 phases/9-pyqt-thin-client/step1.md delete mode 100644 phases/9-pyqt-thin-client/step2.md delete mode 100644 phases/9-pyqt-thin-client/step3.md delete mode 100644 phases/index.json delete mode 100644 plugins/harness-engineering/.codex-plugin/plugin.json delete mode 100644 plugins/harness-engineering/agents/openai.yaml delete mode 100644 plugins/harness-engineering/commands/harness.md delete mode 100644 plugins/harness-engineering/commands/review.md delete mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 samples/2007쉘구조물의유한요소해석에대하여.pdf delete mode 100644 samples/FourNodeQuadrilateralShellElementMITC4.pdf delete mode 100644 samples/MITC공부.pdf delete mode 100644 samples/metadata.json delete mode 100644 samples/유한요소해석법을이용한쉘구조물의동적좌굴해석.pdf delete mode 100644 scripts/execute.py delete mode 100644 scripts/test_execute.py delete mode 100644 scripts/test_validate_workspace.py delete mode 100644 scripts/validate_workspace.py delete mode 100644 src/pdftomd/__init__.py delete mode 100644 src/pdftomd/models.py delete mode 100644 src/pdftomd/options.py delete mode 100644 src/pdftomd/paths.py delete mode 100644 src/pdftomd/preanalysis.py delete mode 100644 src/pdftomd/quality.py delete mode 100644 src/pdftomd/runtime_contracts.py delete mode 100644 tests/conftest.py delete mode 100644 tests/test_models.py delete mode 100644 tests/test_options.py delete mode 100644 tests/test_paths.py delete mode 100644 tests/test_preanalysis.py delete mode 100644 tests/test_quality.py delete mode 100644 tests/test_runtime_contracts.py delete mode 100644 tests/test_sample_metadata.py diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json deleted file mode 100644 index 056725e..0000000 --- a/.agents/plugins/marketplace.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "local-harness-engineering", - "interface": { - "displayName": "Local Harness Engineering" - }, - "plugins": [ - { - "name": "harness-engineering", - "source": { - "source": "local", - "path": "./plugins/harness-engineering" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "Productivity" - } - ] -} diff --git a/.agents/skills/harness-review/SKILL.md b/.agents/skills/harness-review/SKILL.md deleted file mode 100644 index 9a6f9cc..0000000 --- a/.agents/skills/harness-review/SKILL.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -name: harness-review -description: Review a Harness Engineering repository against its persistent rules and design docs. Use when Codex is asked to review local changes, generated phase files, or implementation output against `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, `docs/UI_GUIDE.md`, testing expectations, and Harness step acceptance criteria. ---- - -# Harness Review - -Use this skill when the user wants a repository-grounded review instead of generic commentary. - -## Review input set - -Read these first: - -- `/AGENTS.md` -- `/docs/ARCHITECTURE.md` -- `/docs/HARNESS.md` -- `/docs/ADR.md` -- `/docs/UI_GUIDE.md` -- the changed files or generated `phases/` files under review - -If the user explicitly asks for delegated review, prefer the repo custom agent `harness_reviewer` or built-in read-only explorers. - -## Checklist - -Evaluate the patch against these questions: - -1. Does it follow the architecture described in `docs/ARCHITECTURE.md`? -2. Does it stay within the technology choices documented in `docs/ADR.md`? -3. Are new or changed behaviors covered by tests or other explicit validation? -4. Does it violate any CRITICAL rule in `AGENTS.md`? -5. Do generated `phases/` files include a clear Sprint Contract with hard thresholds? -6. Do generated `phases/` files remain self-contained, executable, and internally consistent? -7. If the user expects verification, does `python scripts/validate_workspace.py` succeed or is the failure explained? - -## Output rules - -- Lead with findings, ordered by severity. -- Include file references for each finding. -- Explain the concrete risk or regression, not just the rule name. -- If there are no findings, say so explicitly and mention residual risks or missing evidence. -- Keep summaries brief after the findings. - -## Preferred review table - -When the user asks for a checklist-style review, use this table: - -| Item | Result | Notes | -|------|------|------| -| Architecture compliance | PASS/FAIL | {details} | -| Tech stack compliance | PASS/FAIL | {details} | -| Test coverage | PASS/FAIL | {details} | -| CRITICAL rules | PASS/FAIL | {details} | -| Build and validation | PASS/FAIL | {details} | - -## What not to do - -- Do not approve changes just because they compile. -- Do not focus on style-only issues when correctness, architecture drift, or missing validation exists. -- Do not assume a passing hook means the implementation is acceptable; review the actual diff and docs. diff --git a/.agents/skills/harness-review/agents/openai.yaml b/.agents/skills/harness-review/agents/openai.yaml deleted file mode 100644 index 555439e..0000000 --- a/.agents/skills/harness-review/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Harness Review" - short_description: "Review changes against Harness project rules" - default_prompt: "Use Harness review to check architecture, tests, and rules." diff --git a/.agents/skills/harness-workflow/SKILL.md b/.agents/skills/harness-workflow/SKILL.md deleted file mode 100644 index 0d04a8f..0000000 --- a/.agents/skills/harness-workflow/SKILL.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -name: harness-workflow -description: Plan and run the Harness Engineering workflow for this repository. Use when Codex needs to read `AGENTS.md` and `docs/*.md`, discuss implementation scope, draft phase plans, or create/update `phases/index.json`, `phases/{phase}/index.json`, and `phases/{phase}/stepN.md` files for staged execution. ---- - -# Harness Workflow - -Use this skill when the user is working in the Harness template and wants structured planning or phase-file generation. - -## Workflow - -### 1. Explore first - -Read these files before proposing steps: - -- `/AGENTS.md` -- `/docs/PRD.md` -- `/docs/ARCHITECTURE.md` -- `/docs/HARNESS.md` -- `/docs/ADR.md` -- `/docs/UI_GUIDE.md` - -If the user explicitly asks for parallel exploration, use built-in Codex subagents such as `explorer`, or the repo-scoped custom agent `phase_planner`. - -### 2. Discuss before locking the plan - -If scope, sequencing, or architecture choices are still ambiguous, surface the decision points before creating `phases/` files. - -### 3. Design steps with strict boundaries - -When drafting a phase plan: - -1. Keep scope minimal. One step should usually touch one layer or one module. -2. Make each step self-contained. Every `stepN.md` must work in an isolated Codex session. -3. List prerequisite files explicitly. Never rely on "as discussed above". -4. Specify interfaces or invariants, not line-by-line implementations. -5. Use executable acceptance commands, not vague success criteria. -6. Include a Sprint Contract with done meaning, hard thresholds, owned files, and dependencies. -7. Write concrete warnings in "do not do X because Y" form. -8. Use kebab-case step names. - -## Files to generate - -### `phases/index.json` - -Top-level phase registry. Append to `phases[]` when the file already exists. - -```json -{ - "phases": [ - { - "dir": "0-mvp", - "status": "pending" - } - ] -} -``` - -- `dir`: phase directory name. -- `status`: `pending`, `completed`, `error`, or `blocked`. -- Timestamp fields are written by `scripts/execute.py`; do not seed them during planning. - -### `phases/{phase}/index.json` - -```json -{ - "project": "", - "phase": "", - "steps": [ - { "step": 0, "name": "project-setup", "status": "pending" }, - { "step": 1, "name": "core-types", "status": "pending" }, - { "step": 2, "name": "api-layer", "status": "pending" } - ] -} -``` - -- `project`: from `AGENTS.md`. -- `phase`: directory name. -- `steps[].step`: zero-based integer. -- `steps[].name`: kebab-case slug. -- `steps[].status`: initialize to `pending`. - -### `phases/{phase}/stepN.md` - -Each step file should contain: - -1. A title. -2. A "read these files first" section. -3. A concrete task section. -4. A Sprint Contract section. -5. Executable acceptance criteria. -6. Verification instructions. -7. Explicit prohibitions. - -Recommended structure: - -```markdown -# Step {N}: {name} - -## Read First -- /AGENTS.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- {files from previous steps} - -## Task -{specific instructions} - -## Sprint Contract -- Done means: {observable or testable completion definition} -- Hard thresholds: {criteria that fail the step if violated} -- Files owned: {paths this step may edit} -- Dependencies: {previous steps or required docs} - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance commands. -2. Check AGENTS and docs for rule drift. -3. Update the matching step in phases/{phase}/index.json: - - completed + summary - - error + error_message - - blocked + blocked_reason - -## Do Not -- {concrete prohibition} -``` -``` - -## Execution - -Run the generated phase with: - -```bash -python scripts/execute.py -python scripts/execute.py --push -``` - -`scripts/execute.py` handles: - -- `feat-{phase}` branch checkout/creation -- guardrail injection from `AGENTS.md` and `docs/*.md` -- accumulation of completed-step summaries into later prompts -- up to 3 retries with prior error feedback -- two-phase commit of code changes and metadata updates -- timestamps such as `created_at`, `started_at`, `completed_at`, `failed_at`, and `blocked_at` - -## Recovery rules - -- If a step is `error`, reset its status to `pending`, remove `error_message`, then rerun. -- If a step is `blocked`, resolve the blocker, reset to `pending`, remove `blocked_reason`, then rerun. diff --git a/.agents/skills/harness-workflow/agents/openai.yaml b/.agents/skills/harness-workflow/agents/openai.yaml deleted file mode 100644 index 890daa1..0000000 --- a/.agents/skills/harness-workflow/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Harness Workflow" - short_description: "Guide Codex through Harness phase planning" - default_prompt: "Use the Harness workflow to plan phases and step files." diff --git a/.codex/agents/conversion-architect.toml b/.codex/agents/conversion-architect.toml deleted file mode 100644 index 644ed0d..0000000 --- a/.codex/agents/conversion-architect.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "conversion_architect" -description = "Read-only conversion architecture specialist for parser boundaries, internal block models, chunk policy, renderer contracts, and output structure." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Read AGENTS.md, PLAN.md, PROGRESS.md, docs/ARCHITECTURE.md, docs/CONVERSION_POLICY.md, and docs/ADR.md before proposing architecture. -Keep Marker as the document-structure source, Nougat as formula-only, and PyMuPDF as pre-analysis/chunk planning unless the user explicitly asks to revisit ADRs. -Define interfaces and invariants rather than line-by-line implementation. -Surface risks around chunk boundaries, fallback paths, deterministic naming, and Markdown output integrity. -Do not edit files unless explicitly instructed. -""" diff --git a/.codex/agents/formula-pipeline-specialist.toml b/.codex/agents/formula-pipeline-specialist.toml deleted file mode 100644 index 8198ef9..0000000 --- a/.codex/agents/formula-pipeline-specialist.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "formula_pipeline_specialist" -description = "Read-only formula pipeline specialist for Nougat handoff, formula detection, LaTeX validation, numbering, references, and fallback behavior." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Read AGENTS.md, PLAN.md, PROGRESS.md, docs/CONVERSION_POLICY.md, docs/TOOLCHAIN.md, and docs/ADR.md before advising. -Treat Nougat as formula-only and Marker source text as the required fallback. -Focus on equation block detection, inline/block formula classification, formula numbering, reference anchors, delimiter repair, and begin/end validation. -Call out confidence thresholds and failure modes explicitly. -Do not edit files unless explicitly instructed. -""" diff --git a/.codex/agents/harness-reviewer.toml b/.codex/agents/harness-reviewer.toml deleted file mode 100644 index 95263fd..0000000 --- a/.codex/agents/harness-reviewer.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "harness_reviewer" -description = "Read-only reviewer for Harness projects, focused on architecture drift, critical rule violations, and missing validation." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Review changes like a repository owner. -Prioritize correctness, architecture compliance, behavior regressions, and missing tests over style. -Always compare the patch against AGENTS.md, docs/ARCHITECTURE.md, docs/ADR.md, and the requested acceptance criteria. -Lead with concrete findings and file references. If no material issues are found, say so explicitly and mention residual risks. -""" diff --git a/.codex/agents/layout-table-figure-specialist.toml b/.codex/agents/layout-table-figure-specialist.toml deleted file mode 100644 index ce34c6c..0000000 --- a/.codex/agents/layout-table-figure-specialist.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "layout_table_figure_specialist" -description = "Read-only layout/table/figure specialist for reading order, paragraph stitching, table rendering, figure extraction, captions, and references." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Read AGENTS.md, PLAN.md, PROGRESS.md, docs/CONVERSION_POLICY.md, docs/ARCHITECTURE.md, and docs/PRD.md before advising. -Focus on logical reading order, multi-column layouts, header/footer removal, paragraph stitching, Markdown vs HTML table decisions, table screenshot fallback, figure asset naming, deduplication, captions, and internal references. -Return testable heuristics and edge cases grounded in sample PDFs when available. -Do not edit files unless explicitly instructed. -""" diff --git a/.codex/agents/pdf-toolchain-researcher.toml b/.codex/agents/pdf-toolchain-researcher.toml deleted file mode 100644 index 55ad56d..0000000 --- a/.codex/agents/pdf-toolchain-researcher.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "pdf_toolchain_researcher" -description = "Read-only PDF toolchain researcher for Marker, Nougat, PyMuPDF, PyTorch/CUDA, model cache, and licensing compatibility." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Read AGENTS.md, PLAN.md, PROGRESS.md, docs/TOOLCHAIN.md, docs/ARCHITECTURE.md, docs/CONVERSION_POLICY.md, and docs/ADR.md before answering. -Focus on official or primary sources for Marker, Nougat, PyMuPDF, PyTorch/CUDA, Markdown math, and comparison baselines. -Return compatibility findings, recommended dependency pins, runtime risks, model cache implications, and licensing questions. -Do not edit files unless the parent agent explicitly asks for a patch. -Do not propose replacing Marker as the primary parser without explaining architecture and ADR impact. -""" diff --git a/.codex/agents/phase-planner.toml b/.codex/agents/phase-planner.toml deleted file mode 100644 index 35d5389..0000000 --- a/.codex/agents/phase-planner.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "phase_planner" -description = "Read-heavy Harness planner that decomposes docs into minimal, self-contained phase and step files." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Plan before implementing. -Read AGENTS.md and the docs directory, identify the smallest coherent phase boundaries, and draft self-contained steps. -Keep each step scoped to one layer or one module when possible. -Do not make code changes unless the parent agent explicitly asks you to write files. -Return concrete file paths, acceptance commands, and blocking assumptions. -""" diff --git a/.codex/agents/quality-evaluator.toml b/.codex/agents/quality-evaluator.toml deleted file mode 100644 index 6e9ea20..0000000 --- a/.codex/agents/quality-evaluator.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "quality_evaluator" -description = "Read-only quality evaluator for focused PDF-to-Markdown tests, sample corpus coverage, regression strategy, and validation gaps." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Read AGENTS.md, PLAN.md, PROGRESS.md, docs/PRD.md, docs/CONVERSION_POLICY.md, and docs/ARCHITECTURE.md before evaluating quality. -Prefer focused assertions over full Markdown snapshots. -Prioritize tests for headings, formula delimiters, LaTeX environment pairs, table parseability, image links, caption matching, chunk integrity, Windows paths, Korean filenames, and no-exception conversion. -Return concrete pytest targets, fixture needs, and residual risks. -Do not write tests unless explicitly asked. -""" diff --git a/.codex/agents/sample-corpus-analyst.toml b/.codex/agents/sample-corpus-analyst.toml deleted file mode 100644 index 63b8e07..0000000 --- a/.codex/agents/sample-corpus-analyst.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "sample_corpus_analyst" -description = "Read-only analyst for samples/ PDFs, focused on page traits, text-layer quality, OCR needs, formulas, tables, figures, and regression metadata." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Read AGENTS.md, PLAN.md, PROGRESS.md, docs/PRD.md, docs/CONVERSION_POLICY.md, and docs/TOOLCHAIN.md before analyzing samples. -Use PyMuPDF-oriented evidence when possible: page count, first-page text length, image count, suspected scan pages, OCR candidates, and layout complexity. -Design sample metadata schema and quality test implications, but do not create or modify metadata files unless explicitly asked. -Preserve Korean filenames exactly in reports. -Return concrete next tests and any sample coverage gaps. -""" diff --git a/.codex/commands/conversion-policy-review.md b/.codex/commands/conversion-policy-review.md deleted file mode 100644 index e0e3b88..0000000 --- a/.codex/commands/conversion-policy-review.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -description: Review conversion policy, architecture, ADRs, and AGENTS.md for consistency. -argument-hint: [optional-topic] -allowed-tools: [Read, Glob, Grep, Bash] ---- - -# /conversion-policy-review - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/CONVERSION_POLICY.md`, `docs/ADR.md`, `docs/PRD.md`, and `docs/TOOLCHAIN.md`. -2. Check for drift in parser responsibilities, output contract, runtime policy, logging/resume policy, environment pins, and sidecar scope. -3. Lead with concrete inconsistencies and file references. -4. Run `python scripts\validate_workspace.py` if file changes were made or if the user asks. -5. Do not edit files unless explicitly asked. - -## Output - -- **Findings** -- **Consistency Status** -- **Open Questions** -- **Suggested Fixes** diff --git a/.codex/commands/env-check.md b/.codex/commands/env-check.md deleted file mode 100644 index 282c4ff..0000000 --- a/.codex/commands/env-check.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -description: Verify the repo-local PDFtoMD Python environment, CUDA, and Nougat CLI. -argument-hint: [quick|full] -allowed-tools: [Read, Bash] ---- - -# /env-check - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `requirements.txt`, `docs/TOOLCHAIN.md`, and `PROGRESS.md`. -2. Run `.\venv\python.exe -m pip check`. -3. Run a CUDA smoke test with `torch.ones((1,), device="cuda")` unless `$ARGUMENTS` says `quick`. -4. Run `.\venv\Scripts\nougat.exe --help`. -5. Summarize versions and failures. -6. Do not install or upgrade packages unless the user explicitly asks. - -## Output - -- **Environment** -- **CUDA** -- **Nougat** -- **Dependency Health** -- **Action Needed** diff --git a/.codex/commands/model-cache-check.md b/.codex/commands/model-cache-check.md deleted file mode 100644 index 9012448..0000000 --- a/.codex/commands/model-cache-check.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -description: Check model cache and offline-readiness assumptions for Marker, Nougat, and Hugging Face assets. -argument-hint: [cache-path-or-empty] -allowed-tools: [Read, Bash] ---- - -# /model-cache-check - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `docs/TOOLCHAIN.md`, `docs/ARCHITECTURE.md`, and `docs/CONVERSION_POLICY.md`. -2. Inspect relevant environment variables and common Hugging Face cache paths. -3. Check whether local cache paths are explicit enough for offline execution. -4. Do not download model weights unless the user explicitly asks. - -## Output - -- **Cache Paths** -- **Offline Readiness** -- **Missing Assets** -- **Documentation Gaps** diff --git a/.codex/commands/phase-draft.md b/.codex/commands/phase-draft.md deleted file mode 100644 index a40d4f6..0000000 --- a/.codex/commands/phase-draft.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -description: Draft Harness phase steps for PDFtoMD implementation without executing them. -argument-hint: [phase-goal] -allowed-tools: [Read, Glob, Grep, Write, Edit] ---- - -# /phase-draft - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/CONVERSION_POLICY.md`, `docs/HARNESS.md`, `docs/ADR.md`, and `docs/TOOLCHAIN.md`. -2. Use `$harness-workflow` guidance if phase files should be created. -3. Keep each step self-contained and scoped to one layer or module. -4. Include executable acceptance commands. -5. Include a Sprint Contract with done criteria, hard thresholds, owned files, and dependencies. -6. Do not create phase files unless the user explicitly requested file generation. - -## Output - -- **Phase Goal** -- **Step List** -- **Dependencies** -- **Acceptance Commands** -- **Do Not** diff --git a/.codex/commands/quality-plan.md b/.codex/commands/quality-plan.md deleted file mode 100644 index 630c47b..0000000 --- a/.codex/commands/quality-plan.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -description: Draft focused pytest coverage for PDFtoMD conversion quality. -argument-hint: [feature-or-sample-focus] -allowed-tools: [Read, Glob, Grep] ---- - -# /quality-plan - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, and `docs/CONVERSION_POLICY.md`. -2. Identify focused tests for headings, formulas, tables, images, captions, links, chunk boundaries, Windows paths, Korean filenames, and no-exception conversion. -3. Prefer concrete pytest names and fixture inputs. -4. Do not write tests unless explicitly asked. - -## Output - -- **Test Goals** -- **Proposed Test Files** -- **Fixture Needs** -- **Acceptance Commands** -- **Residual Risks** diff --git a/.codex/commands/sample-audit.md b/.codex/commands/sample-audit.md deleted file mode 100644 index 81d2e0c..0000000 --- a/.codex/commands/sample-audit.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: Audit samples/ PDFs for page counts, text-layer quality, images, and OCR candidates. -argument-hint: [pdf-glob-or-empty] -allowed-tools: [Read, Glob, Bash, Write, Edit] ---- - -# /sample-audit - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, and `docs/CONVERSION_POLICY.md`. -2. Use PyMuPDF from `.\venv` to inspect matching `samples/*.pdf` files. -3. Report page count, first-page text length, image counts, suspected scan/OCR pages, Korean filename coverage, and obvious layout risks. -4. If the user asks to write metadata, create or update `samples/metadata.json`; otherwise only report. -5. Update `PROGRESS.md` when files are changed. - -## Output - -- **Corpus Summary** -- **Per-PDF Traits** -- **OCR Candidates** -- **Test Implications** -- **Recommended Metadata Changes** diff --git a/.codex/commands/sprint-contract.md b/.codex/commands/sprint-contract.md deleted file mode 100644 index 696879f..0000000 --- a/.codex/commands/sprint-contract.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -description: Draft or review a step-level generator/evaluator contract before implementation. -argument-hint: [phase-dir step-number] -allowed-tools: [Read, Glob, Grep, Edit] ---- - -# /sprint-contract - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `docs/HARNESS.md`, and the target `phases/{phase}/stepN.md`. -2. Confirm the step has a concrete Sprint Contract: - - Done means - - Hard thresholds - - Files owned - - Dependencies - - Acceptance commands - - Explicit Do Not list -3. If the contract is missing or vague, edit only the target step file to make the contract executable by a fresh agent. -4. Do not implement the step. - -## Output - -- **Target Step** -- **Contract Status**: ready | updated | blocked -- **Evaluator Thresholds** -- **Remaining Ambiguity** diff --git a/.codex/commands/status.md b/.codex/commands/status.md deleted file mode 100644 index 84fb416..0000000 --- a/.codex/commands/status.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: Summarize current PDFtoMD plan, progress, blockers, and next work. -argument-hint: [optional-focus] -allowed-tools: [Read, Glob, Grep, Bash] ---- - -# /status - -## Arguments - -The user invoked this command with: $ARGUMENTS - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, and `docs/HARNESS.md`. -2. Summarize the current project goal, scope, completed work, in-progress work, blockers, and next work. -3. If `$ARGUMENTS` names an area, focus the summary on that area. -4. Do not modify files. - -## Output - -- **Goal** -- **Current State** -- **Next Work** -- **Blockers** -- **Relevant Files** -- **Active Phase/Step** diff --git a/.codex/config.toml b/.codex/config.toml deleted file mode 100644 index 39ca33a..0000000 --- a/.codex/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Project-scoped Codex defaults for the Harness template. -# As of 2026-04-15, hooks are experimental and disabled on native Windows. - -[features] -codex_hooks = true - -[agents] -max_threads = 6 -max_depth = 1 diff --git a/.codex/hooks.json b/.codex/hooks.json deleted file mode 100644 index b00b151..0000000 --- a/.codex/hooks.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "python \".codex/hooks/pre_tool_use_policy.py\"", - "statusMessage": "Checking risky shell command" - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "python \".codex/hooks/stop_continue.py\"", - "statusMessage": "Running Harness validation", - "timeout": 300 - }, - { - "type": "command", - "command": "python \".codex/hooks/handoff_policy.py\"", - "statusMessage": "Checking PLAN/PROGRESS handoff", - "timeout": 60 - }, - { - "type": "command", - "command": "python \".codex/hooks/drift_policy.py\"", - "statusMessage": "Checking documentation drift", - "timeout": 60 - } - ] - } - ] - } -} diff --git a/.codex/hooks/drift_policy.py b/.codex/hooks/drift_policy.py deleted file mode 100644 index e9e397d..0000000 --- a/.codex/hooks/drift_policy.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -"""Catch high-confidence documentation drift before a Codex turn ends.""" - -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - - -def changed_paths(root: Path) -> set[str]: - result = subprocess.run( - ["git", "status", "--porcelain"], - cwd=root, - capture_output=True, - text=True, - timeout=20, - ) - if result.returncode != 0: - return set() - - paths: set[str] = set() - for line in result.stdout.splitlines(): - if not line.strip(): - continue - path = line[3:].replace("\\", "/") - if " -> " in path: - path = path.split(" -> ", 1)[1] - paths.add(path) - return paths - - -def block(reason: str) -> int: - json.dump({"decision": "block", "reason": reason}, sys.stdout) - return 0 - - -def main() -> int: - try: - payload = json.load(sys.stdin) - except json.JSONDecodeError: - return 0 - - if payload.get("stop_hook_active"): - return 0 - - root = Path(payload.get("cwd") or ".").resolve() - paths = changed_paths(root) - - if "requirements.txt" in paths and "docs/TOOLCHAIN.md" not in paths: - return block( - "requirements.txt changed without docs/TOOLCHAIN.md. " - "Update the toolchain notes with dependency compatibility rationale." - ) - - sample_pdf_changed = any(path.startswith("samples/") and path.lower().endswith(".pdf") for path in paths) - metadata_changed = "samples/metadata.json" in paths - if sample_pdf_changed and not metadata_changed: - return block( - "A sample PDF changed without samples/metadata.json. " - "Update the sample metadata mapping so quality tests know the corpus traits." - ) - - policy_docs = { - "docs/ARCHITECTURE.md", - "docs/CONVERSION_POLICY.md", - "docs/ADR.md", - } - touched_policy_docs = policy_docs.intersection(paths) - if touched_policy_docs and "PROGRESS.md" not in paths: - return block( - "Architecture or conversion policy docs changed without PROGRESS.md. " - "Record the decision and handoff context in PROGRESS.md." - ) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.codex/hooks/handoff_policy.py b/.codex/hooks/handoff_policy.py deleted file mode 100644 index 523e839..0000000 --- a/.codex/hooks/handoff_policy.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -"""Require PLAN/PROGRESS handoff discipline for multi-agent work.""" - -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - - -TRACKED_PREFIXES = ( - ".agents/", - ".codex/", - "AGENTS.md", - "PLAN.md", - "docs/", - "phases/", - "plugins/", - "requirements.txt", - "scripts/", - "src/", - "tests/", -) - - -def git_status_names(root: Path) -> list[str]: - result = subprocess.run( - ["git", "status", "--porcelain"], - cwd=root, - capture_output=True, - text=True, - timeout=20, - ) - if result.returncode != 0: - return [] - - names: list[str] = [] - for line in result.stdout.splitlines(): - if not line.strip(): - continue - path = line[3:].replace("\\", "/") - if " -> " in path: - path = path.split(" -> ", 1)[1] - names.append(path) - return names - - -def is_coordination_relevant(path: str) -> bool: - return any(path == prefix or path.startswith(prefix) for prefix in TRACKED_PREFIXES) - - -def block(reason: str) -> int: - json.dump({"decision": "block", "reason": reason}, sys.stdout) - return 0 - - -def main() -> int: - try: - payload = json.load(sys.stdin) - except json.JSONDecodeError: - return 0 - - if payload.get("stop_hook_active"): - return 0 - - root = Path(payload.get("cwd") or ".").resolve() - plan = root / "PLAN.md" - progress = root / "PROGRESS.md" - - if not plan.exists() or not progress.exists(): - return block( - "Multi-agent coordination requires PLAN.md and PROGRESS.md. " - "Create or restore both files before ending the turn." - ) - - changed = git_status_names(root) - if not changed: - return 0 - - relevant = [path for path in changed if is_coordination_relevant(path)] - progress_changed = "PROGRESS.md" in changed - - if relevant and not progress_changed: - return block( - "Repository planning, docs, code, tests, requirements, or .codex files changed, " - "but PROGRESS.md was not updated. Add a concise handoff note so the next agent " - "can see what changed, what was verified, and what remains next." - ) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.codex/hooks/pre_tool_use_policy.py b/.codex/hooks/pre_tool_use_policy.py deleted file mode 100644 index da37d90..0000000 --- a/.codex/hooks/pre_tool_use_policy.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -"""Block obviously destructive shell commands before Codex runs them.""" - -from __future__ import annotations - -import json -import re -import sys - - -BLOCK_PATTERNS = ( - r"\brm\s+-rf\b", - r"\bgit\s+push\s+--force(?:-with-lease)?\b", - r"\bgit\s+reset\s+--hard\b", - r"\bgit\s+clean\s+-[a-zA-Z]*f[a-zA-Z]*[dx][a-zA-Z]*\b", - r"\bgit\s+checkout\s+--\s+\.\b", - r"\bDROP\s+TABLE\b", - r"\btruncate\s+table\b", - r"\bRemove-Item\b.*\b-Recurse\b", - r"\bdel\b\s+/s\b", - r"\bconda\s+(?:env\s+)?remove\b.*\b--all\b", -) - - -def main() -> int: - try: - payload = json.load(sys.stdin) - except json.JSONDecodeError: - return 0 - - command = payload.get("tool_input", {}).get("command", "") - for pattern in BLOCK_PATTERNS: - if re.search(pattern, command, re.IGNORECASE): - json.dump( - { - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "permissionDecision": "deny", - "permissionDecisionReason": "Harness guardrail blocked a risky shell command.", - } - }, - sys.stdout, - ) - return 0 - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.codex/hooks/stop_continue.py b/.codex/hooks/stop_continue.py deleted file mode 100644 index e61f2ae..0000000 --- a/.codex/hooks/stop_continue.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -"""Run repository validation when a Codex turn stops and request one more pass if it fails.""" - -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - - -def main() -> int: - try: - payload = json.load(sys.stdin) - except json.JSONDecodeError: - return 0 - - if payload.get("stop_hook_active"): - return 0 - - root = Path(payload.get("cwd") or ".").resolve() - validator = root / "scripts" / "validate_workspace.py" - if not validator.exists(): - return 0 - - result = subprocess.run( - [sys.executable, str(validator)], - cwd=root, - capture_output=True, - text=True, - timeout=240, - ) - - if result.returncode == 0: - return 0 - - summary = (result.stdout or result.stderr or "workspace validation failed").strip() - if len(summary) > 1200: - summary = summary[:1200].rstrip() + "..." - - json.dump( - { - "decision": "block", - "reason": ( - "Validation failed. Review the output, fix the repo, then continue.\n\n" - f"{summary}" - ), - }, - sys.stdout, - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.codex/skills/conversion-architecture/SKILL.md b/.codex/skills/conversion-architecture/SKILL.md deleted file mode 100644 index ee514d4..0000000 --- a/.codex/skills/conversion-architecture/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: conversion-architecture -description: Design PDFtoMD conversion architecture, parser boundaries, internal block models, chunk policy, renderer contracts, output structure, logging, and resume behavior. Use when planning or reviewing conversion engine design. ---- - -# Conversion Architecture - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `docs/ARCHITECTURE.md`, `docs/CONVERSION_POLICY.md`, and `docs/ADR.md`. -2. Keep responsibilities stable: - - Marker: layout, OCR, reading order, body, headings, tables, figures, captions - - Nougat: formula-only LaTeX parsing - - PyMuPDF: page pre-analysis, text-layer quality, page counts, chunk planning -3. Define interfaces and invariants before implementation. -4. Keep output deterministic and chunked under the documented output contract. -5. Record architecture changes in `docs/ADR.md` when decisions change. - -## Guardrails - -- Do not place conversion logic in a future PyQt UI. -- Do not add document sidecars unless explicitly requested. -- Do not let chunking split a paragraph, table, figure, or formula without a fallback plan. diff --git a/.codex/skills/conversion-architecture/agents/openai.yaml b/.codex/skills/conversion-architecture/agents/openai.yaml deleted file mode 100644 index 1c97bdc..0000000 --- a/.codex/skills/conversion-architecture/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Conversion Architecture" - short_description: "Plan parser and renderer boundaries" - default_prompt: "Use $conversion-architecture to design the next PDFtoMD engine phase." diff --git a/.codex/skills/formula-quality/SKILL.md b/.codex/skills/formula-quality/SKILL.md deleted file mode 100644 index 9a72442..0000000 --- a/.codex/skills/formula-quality/SKILL.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: formula-quality -description: Plan and review formula extraction quality for PDFtoMD. Use when Codex needs Nougat handoff rules, inline/block formula classification, LaTeX delimiter checks, equation numbering, reference anchors, or Marker fallback behavior. ---- - -# Formula Quality - -## Workflow - -1. Read `AGENTS.md`, `docs/CONVERSION_POLICY.md`, `docs/TOOLCHAIN.md`, and `docs/ADR.md`. -2. Identify formula candidates from Marker equation blocks or mathematical text patterns. -3. Classify formulas as inline or block based on layout context. -4. Validate: - - `$ ... $` and `$$ ... $$` balance - - `\begin{...}` / `\end{...}` pairs - - formula numbering - - body references such as `Eq. (3)` or Korean equation references -5. Use Marker source text as fallback when Nougat fails. - -## Guardrails - -- Do not pass whole documents through Nougat as the primary parser. -- Do not discard formula text on parse failure. -- Do not rewrite references as links unless the target confidence is sufficient. diff --git a/.codex/skills/formula-quality/agents/openai.yaml b/.codex/skills/formula-quality/agents/openai.yaml deleted file mode 100644 index 4212266..0000000 --- a/.codex/skills/formula-quality/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Formula Quality" - short_description: "Validate equations and LaTeX output" - default_prompt: "Use $formula-quality to design formula parsing tests and fallback behavior." diff --git a/.codex/skills/markdown-quality/SKILL.md b/.codex/skills/markdown-quality/SKILL.md deleted file mode 100644 index 285f6a6..0000000 --- a/.codex/skills/markdown-quality/SKILL.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: markdown-quality -description: Plan and review Markdown output quality for PDFtoMD. Use when Codex needs tests or policies for headings, tables, HTML fallback, image links, captions, frontmatter, chunk integrity, and deterministic output. ---- - -# Markdown Quality - -## Workflow - -1. Read `AGENTS.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, and `docs/CONVERSION_POLICY.md`. -2. Prefer focused assertions over full snapshots. -3. Validate: - - heading hierarchy - - table parseability - - limited HTML table fallback - - image link existence - - figure/table captions - - internal references - - chunk frontmatter - - deterministic filenames and anchors -4. Use Markdown or HTML parsers when practical. - -## Guardrails - -- Do not inject runtime warnings into generated Markdown. -- Do not rely only on brittle whole-file snapshots. -- Do not lose complex table content without linking a fallback asset. diff --git a/.codex/skills/markdown-quality/agents/openai.yaml b/.codex/skills/markdown-quality/agents/openai.yaml deleted file mode 100644 index 8f9c336..0000000 --- a/.codex/skills/markdown-quality/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Markdown Quality" - short_description: "Check chunk Markdown and assets" - default_prompt: "Use $markdown-quality to plan focused Markdown output validation." diff --git a/.codex/skills/pdf-toolchain/SKILL.md b/.codex/skills/pdf-toolchain/SKILL.md deleted file mode 100644 index 984b4c6..0000000 --- a/.codex/skills/pdf-toolchain/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: pdf-toolchain -description: Research and maintain PDFtoMD toolchain compatibility for Marker, Nougat, PyMuPDF, PyTorch/CUDA, model cache, and licensing. Use when Codex needs dependency pins, runtime compatibility checks, official-source research, or updates to docs/TOOLCHAIN.md and related ADRs. ---- - -# PDF Toolchain - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `docs/TOOLCHAIN.md`, `docs/ARCHITECTURE.md`, and `docs/ADR.md`. -2. Prefer official or primary sources for current facts. -3. Verify local facts with commands when relevant: - - `.\venv\python.exe -m pip check` - - `.\venv\python.exe -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())"` - - `.\venv\Scripts\nougat.exe --help` -4. Preserve the verified GTX 1070 Ti baseline unless a replacement is tested. -5. Update `docs/TOOLCHAIN.md` and `docs/ADR.md` when dependency decisions change. - -## Guardrails - -- Do not upgrade `torch`, `transformers`, `albumentations`, `pypdfium2`, `opencv-python-headless`, `Pillow`, or `fsspec` without re-running compatibility checks. -- Do not switch the primary parser away from Marker without an ADR update. -- Do not download model weights unless the user explicitly asks. diff --git a/.codex/skills/pdf-toolchain/agents/openai.yaml b/.codex/skills/pdf-toolchain/agents/openai.yaml deleted file mode 100644 index 35723e1..0000000 --- a/.codex/skills/pdf-toolchain/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "PDF Toolchain" - short_description: "PDF parser and CUDA dependency guidance" - default_prompt: "Use $pdf-toolchain to verify PDFtoMD dependency compatibility and update toolchain notes." diff --git a/.codex/skills/sample-corpus/SKILL.md b/.codex/skills/sample-corpus/SKILL.md deleted file mode 100644 index 590c290..0000000 --- a/.codex/skills/sample-corpus/SKILL.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: sample-corpus -description: Analyze and maintain the PDFtoMD samples corpus. Use when Codex needs to classify samples/ PDFs, design samples/metadata.json, identify OCR candidates, or connect corpus traits to focused regression tests. ---- - -# Sample Corpus - -## Workflow - -1. Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `docs/PRD.md`, and `docs/CONVERSION_POLICY.md`. -2. Inspect PDFs with PyMuPDF before proposing tests. -3. Track these traits per PDF: - - page count - - text-layer quality - - scanned or mixed pages - - multi-column layout - - formula density - - table density - - figure density - - Korean filename/path coverage -4. If writing metadata, use `samples/metadata.json` and update `PROGRESS.md`. - -## Guardrails - -- Preserve original sample PDFs. -- Do not rename Korean sample files unless the user explicitly asks. -- Do not treat first-page text length as the only OCR signal. diff --git a/.codex/skills/sample-corpus/agents/openai.yaml b/.codex/skills/sample-corpus/agents/openai.yaml deleted file mode 100644 index 686134c..0000000 --- a/.codex/skills/sample-corpus/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Sample Corpus" - short_description: "Classify PDF samples for quality tests" - default_prompt: "Use $sample-corpus to audit samples/ PDFs and propose regression metadata." diff --git a/.codex/skills/windows-runtime/SKILL.md b/.codex/skills/windows-runtime/SKILL.md deleted file mode 100644 index 820e4d1..0000000 --- a/.codex/skills/windows-runtime/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: windows-runtime -description: Maintain Windows-native PDFtoMD runtime behavior. Use when Codex needs guidance for repo-local venv, CUDA/OOM handling, Korean paths, long paths, model cache, offline operation, stderr logs, or resume cache behavior. ---- - -# Windows Runtime - -## Workflow - -1. Read `AGENTS.md`, `docs/TOOLCHAIN.md`, `docs/ARCHITECTURE.md`, and `docs/CONVERSION_POLICY.md`. -2. Verify environment health with: - - `.\venv\python.exe -m pip check` - - CUDA smoke test - - `.\venv\Scripts\nougat.exe --help` -3. Use `pathlib` for path design and tests. -4. Include Korean filenames, spaces, and long Windows paths in test plans. -5. Keep model cache and offline behavior explicit. - -## Guardrails - -- Do not silently fall back to CPU when the user explicitly requested CUDA. -- Do not choose batch sizes that assume more than 8 GB VRAM. -- Do not delete local environments or sample PDFs without explicit approval. diff --git a/.codex/skills/windows-runtime/agents/openai.yaml b/.codex/skills/windows-runtime/agents/openai.yaml deleted file mode 100644 index 682eb3a..0000000 --- a/.codex/skills/windows-runtime/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Windows Runtime" - short_description: "Windows, CUDA, paths, and offline checks" - default_prompt: "Use $windows-runtime to verify PDFtoMD local runtime assumptions." diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index d72fd52..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.pdf binary diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6ac1be9..0000000 --- a/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# Python environments and caches -venv/ -.venv/ -.models/ -__pycache__/ -*.py[cod] - -# Test and build artifacts -.pytest_cache/ -.mypy_cache/ -.ruff_cache/ -build/ -dist/ -*.egg-info/ - -# Conversion outputs -output/ - -# Local environment files -.env -*.env diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 829c9d7..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,235 +0,0 @@ -# Project: PDFtoMD - -## Repository Role -- 이 저장소는 PDF 문서를 AI Agent가 쉽게 탐색하고 읽을 수 있는 Markdown 문서 묶음으로 변환하는 저장소입니다. -- 목표는 단순 텍스트 추출이 아니라, 읽기 순서, 문단 흐름, 수식, 표, 이미지, 캡션, 본문 참조를 보존한 구조화 변환입니다. -- 텍스트 레이어 PDF, 스캔 PDF, 텍스트/스캔 혼합 PDF를 모두 지원 대상으로 둡니다. -- 1차 목표는 Windows native 환경에서 완전 로컬로 실행되는 CLI/라이브러리 변환 엔진입니다. -- 2차 목표는 PyQt 기반 Windows UI와 선택적 외부 API 연동입니다. -- 변환 결과는 chunk Markdown 파일과 필요한 이미지/표 asset 중심으로 구성합니다. -- 별도 문서 출력 sidecar 산출물은 명시 요청 전까지 범위에 넣지 않습니다. -- 다만 변환 재개를 위한 로컬 runtime cache/state 파일과 stderr/local log 파일은 문서 출력물이 아니므로 허용할 수 있습니다. -- Persistent repository instructions live in this `AGENTS.md`. -- Reusable repo-scoped workflows live in `.agents/skills/`. -- Project-scoped custom agents live in `.codex/agents/`. -- Experimental hooks live in `.codex/hooks.json`. - -## Read First -새 세션이나 새 agent 작업을 시작하면 다음 문서를 먼저 읽습니다. -- `AGENTS.md` -- `PLAN.md` -- `PROGRESS.md` -- `docs/PRD.md` -- `docs/ARCHITECTURE.md` -- `docs/CONVERSION_POLICY.md` -- `docs/HARNESS.md` -- `docs/IMPLEMENTATION_PLAN.md` -- `docs/ADR.md` -- `docs/TOOLCHAIN.md` -- `docs/UI_GUIDE.md` - -## 기술 스택 -- **Language**: Python 3.11+ -- **Environment**: repo-local single `venv` -- **Primary PDF Parser**: `Marker` -- **Primary Mathematical Expression Parser**: `Nougat` -- **PDF Analysis / Splitting**: `PyMuPDF` -- **OCR / Layout Support**: Marker OCR/layout 기능 우선 사용 -- **Pre-analysis**: PyMuPDF 등으로 페이지별 텍스트 레이어 품질과 OCR 필요 여부를 사전 판별 -- **Table Handling**: Markdown table 우선, 복잡한 표는 제한적 HTML table과 표 영역 이미지 fallback 허용 -- **Output Target**: Markdown -- **Runtime**: Windows native, local-first, GPU 기본 사용, VRAM 8GB 기준으로 batch/chunk 크기 제한 -- **Verified GPU Baseline**: NVIDIA GeForce GTX 1070 Ti 8GB, `torch==2.7.1+cu126` -- **UI**: `PyQt`는 2차 목표이며 CLI/라이브러리 API를 호출하는 thin client여야 함 - -## 아키텍처 규칙 -### Parser Engine Strategy -- Marker를 기본 PDF parser로 사용합니다. -- Marker는 전체 레이아웃 추적, OCR/layout, reading order, heading, 본문, 표, 그림, 캡션, semantic block role을 담당합니다. -- Nougat은 메인 PDF parser가 아니라 수식용 parser로 사용합니다. -- Nougat은 Marker의 equation block 또는 수식 패턴이 감지된 블록의 수식 문자열 생성에만 사용합니다. -- Nougat 변환 실패 시 정보 유실 방지를 위해 Marker가 추출한 원문 문자열을 fallback으로 사용합니다. -- PyMuPDF는 페이지 수, 텍스트 레이어 품질, chunk 계획, 저수준 PDF/page/image 작업에 사용합니다. -- 세부 변환 정책은 `docs/CONVERSION_POLICY.md`를 우선합니다. - -### Reading Order & Paragraph Flow Strategy -PDF는 텍스트를 좌표 기반으로 저장하므로, 사람이 읽는 논리적인 순서로 재구성하는 것이 핵심입니다. -- **Logical Reading Order**: 다단 문서나 삽입 문구가 있는 레이아웃에서 텍스트 흐름을 추적하여 Markdown의 선형 구조로 배치합니다. -- **Paragraph Stitching**: PDF 추출 시 발생하는 행 단위 분절을 제거하고, 문맥과 bounding box 정보를 이용해 완성된 문단으로 병합합니다. -- **Header/Footer Filtering**: 페이지 상/하단 반복 패턴, 페이지 번호, 머리말/꼬리말은 본문 흐름에서 분리하거나 제외합니다. -- **Semantic Mapping**: 제목, 본문, 인용구, 리스트, 표, 그림, 캡션, 수식 등의 의미 역할을 보존합니다. - -### Scientific, Mathematical, Table, Figure Strategy -- 수식은 Markdown math delimiter로 표현 가능한 LaTeX를 우선합니다. -- 인라인 수식은 `$ ... $`, 블록 수식은 `$$ ... $$` 형식을 사용합니다. -- 수식 번호와 본문 내 참조 관계는 가능한 한 보존하고 내부 Markdown 링크로 연결합니다. -- LaTeX는 delimiter 짝, `\begin{...}` / `\end{...}` 짝, 흔한 깨짐 패턴을 검증하고 렌더링이 깨지지 않도록 보정합니다. -- CLI 기본 수식 parser는 `nougat`입니다. -- 표는 Markdown table을 우선합니다. -- 병합 셀, 다중 header, 각주 포함 표 등 Markdown table 손실이 큰 경우 제한적으로 HTML ``을 사용합니다. -- 구조화 손실이 큰 표는 원본 보존용 표 영역 이미지 fallback을 함께 연결합니다. -- 이미지는 추출 파일, 캡션, figure 번호, 본문 참조를 함께 연결합니다. -- 이미지 중복 저장을 줄이기 위해 hash 기반 deduplication을 고려합니다. - -### Chunk Strategy -- 긴 PDF는 기본적으로 20페이지 단위 chunk로 나누어 변환합니다. -- chunk 경계는 page count보다 논리 block integrity를 우선합니다. -- 문단, 표, 그림, 수식 중간에서 잘림이 발생하면 해당 block을 이전 또는 다음 chunk로 이동해 온전하게 보존합니다. -- 각 chunk Markdown 최상단에는 문서 제목, page range, chunk 번호 등 최소 문맥을 간결한 frontmatter로 포함할 수 있습니다. - -### Output Structure -```text -output/ -└── document-slug/ - ├── document-slug_001.md - ├── document-slug_002.md - ├── document-slug_003.md - └── images/ - ├── document-slug_fig-001.png - └── document-slug_fig-003.png -``` - -세부 규칙: -- chunk Markdown 파일명은 `_.md` -- image asset은 `images/` -- figure 번호가 있으면 `{document-slug}_fig-{figure-number}.png`를 우선합니다. -- figure 번호가 없거나 충돌 가능성이 있으면 chunk/page/block identifier를 포함한 결정적 파일명을 사용합니다. -- 같은 입력과 같은 옵션은 같은 output path, anchor, asset naming을 생성해야 합니다. - -### Runtime Policy -- 기본 runtime은 `cuda`입니다. -- explicit `--runtime cuda` 또는 `--device cuda`에서 CUDA가 준비되지 않았으면 fail-fast 처리합니다. -- `--runtime auto`는 필요 시 경고 후 CPU fallback을 허용합니다. -- GTX 1070 Ti 8GB 기준 기본 batch size는 1~2 수준으로 보수적으로 잡습니다. -- GPU OOM 발생 시 가능한 경우 batch/page 단위를 줄여 재시도합니다. -- 모델 cache는 명시적 로컬 경로를 사용해 최초 다운로드 이후 offline 실행을 지원해야 합니다. - -### Logging And Resume Policy -- CLI는 chunk 단위 진행률과 성공/실패 상태를 표시합니다. -- 경고와 오류는 stderr 및 로컬 log 파일에 기록합니다. -- 생성된 Markdown 내부에는 오류 로그를 삽입하지 않습니다. -- 성공한 chunk는 재실행 시 건너뛰고 실패한 chunk만 resume할 수 있도록 runtime state/cache를 둘 수 있습니다. -- runtime state/cache는 문서 출력 contract가 아니며, 별도 sidecar 문서 산출물과 구분합니다. - -### Licensing Policy -- 현재 사용 맥락은 개인용입니다. -- 배포나 상업적 사용 가능성이 생기면 Marker GPL 및 model weight license 조건을 다시 검토해야 합니다. -- 프로세스/API 분리는 라이선스 위험 완화 후보일 뿐이며 법적 결론으로 취급하지 않습니다. - -### Git -- 로컬 git을 사용합니다. -- 주소: `C:\git\PDFToMDWithMath` -- 변경사항이 생길 때마다 커밋합니다. -- 커밋 메시지는 conventional commits 형식을 따릅니다. - -## 개발 프로세스 -- CRITICAL: 새 기능 구현 시 반드시 테스트를 먼저 작성하고, 테스트가 통과하는 구현을 작성합니다. (TDD) -- 1차 구현은 CLI/라이브러리 변환 엔진을 먼저 안정화하고, PyQt UI는 2차 목표로 분리합니다. -- 변환 품질 테스트는 전체 Markdown snapshot 비교보다 heading, 수식, 이미지, 표, 캡션, 링크, 예외 여부 등 부분 검증을 우선합니다. -- 정규식만으로 복잡한 문서를 처리하지 말고, 가능한 경우 Markdown/HTML parser나 구조화된 parser API를 사용합니다. -- `samples/` 폴더의 PDF는 회귀 테스트와 품질 평가용 corpus로 사용합니다. -- `samples/` PDF의 특성은 sample metadata mapping 파일로 관리합니다. -- 연구/기획 요청에서는 구현 코드, phase 파일, custom agent 파일을 만들지 않습니다. -- `scripts/execute.py`는 step 완료 후 결과 커밋을 정리하므로, step 프롬프트 안에서 별도 커밋을 만들 필요는 없습니다. - -## Multi-Agent Coordination -- 여러 agent가 일을 나눠서 할 때 **작업 계획의 단일 출처는 repo root의 `PLAN.md`**, **진행 상태의 단일 출처는 repo root의 `PROGRESS.md`**입니다. -- 새 세션이나 새 agent 작업을 시작할 때는 반드시 `AGENTS.md`, `PLAN.md`, `PROGRESS.md`를 먼저 읽어야 합니다. -- 새 agent는 작업을 시작하기 전에 다음 질문에 답할 수 있어야 합니다. - - 현재 프로젝트 목표는 무엇인가? - - 내 담당 범위와 하지 말아야 할 범위는 무엇인가? - - 이미 완료된 작업은 무엇인가? - - 현재 진행 중인 작업과 막힌 점은 무엇인가? - - 바로 이어서 해야 할 다음 작업은 무엇인가? - - 다른 agent와 충돌할 가능성이 있는 파일이나 책임 영역은 무엇인가? -- `PLAN.md`는 앞으로 해야 할 일의 단일 작업 계획 문서로 사용합니다. -- `PLAN.md`에는 목표, 범위, 우선순위, 작업 분해, 담당 agent 또는 역할, 의존성, 수락 기준, 명시적 제외 범위를 기록합니다. -- `PLAN.md`에는 agent가 새로 시작해도 자신의 담당 작업을 선택할 수 있도록 작업 항목을 구체적으로 적습니다. -- `PROGRESS.md`는 어디까지 진행됐는지의 단일 진행 상태 문서로 사용합니다. -- `PROGRESS.md`에는 완료한 작업, 진행 중 작업, 막힌 점, 주요 결정, 검증 결과, 다음에 이어서 할 작업을 기록합니다. -- `PROGRESS.md`의 "Next Work"는 다음 agent가 실제로 이어받을 수 있는 작업 단위로 유지합니다. -- 작업 시작 전 `PROGRESS.md`의 현재 상태와 `PLAN.md`의 담당 범위를 확인하고, 중복 작업이나 충돌 가능성이 있으면 먼저 정리합니다. -- 작업 중 계획이 바뀌면 `PLAN.md`를 먼저 갱신하고, 실제 진행 결과는 `PROGRESS.md`에 반영합니다. -- 작업을 마치거나 중단할 때는 다음 agent가 이어받을 수 있도록 `PROGRESS.md`를 갱신합니다. -- 여러 agent가 병렬로 작업할 경우 각 agent의 담당 파일, 책임 영역, 의존성을 `PLAN.md`에 명확히 기록합니다. -- 병렬 작업 중 완료/실패/차단 상태, 검증 결과, 다음 handoff 내용은 `PROGRESS.md`에 기록합니다. -- `PLAN.md`와 `PROGRESS.md`가 없고 다중 agent 작업이 필요한 경우, 구현 작업을 시작하기 전에 두 파일의 초안을 먼저 만들거나 사용자에게 생성 승인을 요청합니다. -- Harness의 `phases/{phase}/index.json`은 phase 실행 상태용이고, 다중 agent 작업의 전체 계획과 진행 기록은 `PLAN.md`와 `PROGRESS.md`를 우선합니다. - -## Custom Agent Planning -- custom agent 파일은 `.codex/agents/.toml`에 둡니다. -- agent 이름은 snake_case를 사용합니다. -- agent 설계 작업은 기본적으로 read-only agent부터 시작합니다. -- 사용자가 명시적으로 승인한 agent만 하나씩 생성합니다. -- 연구/기획 요청에서는 구현 코드, phase 파일, agent 파일을 만들지 않습니다. - -## Codex Project Extensions -Project-scoped Codex extensions live under `.codex/`. - -### Agents -- `.codex/agents/phase-planner.toml`: Harness phase planning. -- `.codex/agents/harness-reviewer.toml`: Harness repository review. -- `.codex/agents/pdf-toolchain-researcher.toml`: PDF toolchain compatibility and licensing research. -- `.codex/agents/sample-corpus-analyst.toml`: sample PDF corpus analysis. -- `.codex/agents/conversion-architect.toml`: conversion pipeline architecture. -- `.codex/agents/quality-evaluator.toml`: focused quality and regression strategy. -- `.codex/agents/formula-pipeline-specialist.toml`: Nougat/formula pipeline analysis. -- `.codex/agents/layout-table-figure-specialist.toml`: reading order, table, figure, and caption analysis. - -### Commands -- `.codex/commands/status.md`: summarize plan/progress/blockers/next work. -- `.codex/commands/env-check.md`: verify Python environment, CUDA, and Nougat CLI. -- `.codex/commands/sample-audit.md`: inspect `samples/` PDF traits. -- `.codex/commands/quality-plan.md`: draft focused pytest strategy. -- `.codex/commands/conversion-policy-review.md`: review policy/architecture/ADR consistency. -- `.codex/commands/model-cache-check.md`: inspect model cache and offline readiness. -- `.codex/commands/phase-draft.md`: draft Harness phase steps. -- `.codex/commands/sprint-contract.md`: draft or review step-level generator/evaluator contracts. - -### Skills -- `.codex/skills/pdf-toolchain`: dependency, CUDA, model cache, and license workflow. -- `.codex/skills/sample-corpus`: sample metadata and corpus classification workflow. -- `.codex/skills/conversion-architecture`: parser/renderer/chunk architecture workflow. -- `.codex/skills/formula-quality`: formula parsing and LaTeX validation workflow. -- `.codex/skills/markdown-quality`: Markdown output validation workflow. -- `.codex/skills/windows-runtime`: Windows path, CUDA, model cache, and resume workflow. - -### Hooks -- `.codex/hooks/pre_tool_use_policy.py`: blocks high-risk shell commands. -- `.codex/hooks/stop_continue.py`: runs repository validation at stop. -- `.codex/hooks/handoff_policy.py`: enforces `PLAN.md`/`PROGRESS.md` handoff discipline. -- `.codex/hooks/drift_policy.py`: catches high-confidence docs/toolchain/sample metadata drift. -- Hooks are configured in `.codex/hooks.json`; native Windows hook execution may depend on the active Codex surface. - -## Harness Workflow -- Harness 운영 규칙의 세부 기준은 `docs/HARNESS.md`를 우선합니다. -- Anthropic식 장기 작업 흐름은 `planner -> generator -> evaluator` 역할 분리를 기본으로 합니다. -- Planner는 phase/step을 만들고, Generator는 한 번에 하나의 step만 수행하며, Evaluator는 구현 agent와 독립된 관점으로 hard threshold를 적용합니다. -- 각 구현 step은 코드 작성 전에 "Sprint Contract"를 명시해야 합니다. 이 contract에는 완료 정의, hard threshold, 담당 파일, 의존성, 검증 명령이 포함되어야 합니다. -- Generator와 Evaluator의 합의나 검토 결과는 대화에만 남기지 말고 파일에 남깁니다. 기본 위치는 `phases/{phase}/stepN.md`, `phases/{phase}/index.json`, `PROGRESS.md`입니다. -- Hook은 보조 장치이며 `stepN.md`의 Acceptance Criteria와 Evaluator 판단을 대체하지 않습니다. -- Harness 자체는 단순하게 유지합니다. 새 agent, hook, command는 실제 실패 모드를 줄일 때만 추가합니다. -- 먼저 `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/CONVERSION_POLICY.md`, `docs/ADR.md`, `docs/TOOLCHAIN.md`를 읽고 기획/설계 의도를 파악합니다. -- 단계별 실행 계획이 필요하면 repo skill `harness-workflow`를 사용해 `phases/` 아래 파일을 설계합니다. -- 변경사항 리뷰가 필요하면 repo skill `harness-review` 또는 Codex의 `/review`를 사용합니다. -- `phases/{phase}/index.json`은 phase 진행 상태의 단일 진실 공급원으로 취급합니다. -- 각 `stepN.md`는 독립된 Codex 세션에서도 실행 가능하도록 자기완결적으로 작성합니다. - -## 검증 -- 기본 검증 스크립트는 `python scripts/validate_workspace.py`. -- Python 검증은 최소한 다음 항목을 포함해야 합니다. - - `.\venv\python.exe -m pip check` - - `.\venv\python.exe -m pytest` - - CUDA runtime/import smoke test - - `.\venv\Scripts\nougat.exe --help` -- Node 프로젝트면 `package.json`의 `lint`, `build`, `test` 스크립트를 자동 탐지해 순서대로 실행합니다. -- 다른 스택이면 `HARNESS_VALIDATION_COMMANDS` 환경 변수에 줄바꿈 기준으로 검증 커맨드를 지정합니다. - -## 명령어 -- `conda create -p .\venv python=3.11 -y`: repo-local Python 3.11 환경 생성 -- `.\venv\python.exe -m pip install -r requirements.txt`: 검증된 단일 환경 의존성 설치 -- `python scripts/validate_workspace.py`: 저장소 검증 -- `python scripts/execute.py `: Codex 기반 phase 순차 실행 -- `python scripts/execute.py --push`: phase 완료 후 브랜치 push -- `python -m pdftomd --formula-parser nougat --nougat-command .\venv\Scripts\nougat.exe`: Nougat 수식 parser를 사용하는 기본 변환 경로 -- `python -m pdftomd --formula-parser marker`: Nougat 없이 Marker 수식 문자열을 유지하는 compatibility 변환 경로 diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index acce681..0000000 --- a/PLAN.md +++ /dev/null @@ -1,151 +0,0 @@ -# PDFtoMD Multi-Agent Plan - -## Goal -Build a Windows-native, local-first PDF-to-Markdown conversion engine that preserves logical reading order, paragraph flow, formulas, tables, figures, captions, and chunked output for AI-agent consumption. - -## Current Scope -- Primary deliverable: CLI/library conversion engine. -- Primary PDF parser: Marker. -- Formula parser: Nougat, isolated from the main parser path and used only for mathematical expressions/formulas. -- PDF analysis and chunk planning: PyMuPDF. -- Output: chunked Markdown files plus image/table assets under a document slug directory. -- Default chunk size: 20 pages. -- Runtime target: Windows 10, local GPU first, GTX 1070 Ti with 8 GB VRAM. -- User context: personal use. -- Python environment target: one repo-local Python 3.11 environment. - -## Out of Scope For Now -- PyQt UI implementation. -- Hosted conversion API. -- Default LLM correction path. -- Sidecar metadata/log output unless explicitly requested. -- Custom agent file creation until the user approves one agent at a time. -- Engine implementation outside an approved Harness phase. - -## Current Inputs -- Repository instructions: `AGENTS.md`. -- Product/design documents: `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, `docs/TOOLCHAIN.md`, `docs/UI_GUIDE.md`. -- Conversion policy decisions: `docs/CONVERSION_POLICY.md`. -- Harness operating guide: `docs/HARNESS.md`. -- Full implementation roadmap: `docs/IMPLEMENTATION_PLAN.md`. -- Executable phase registry: `phases/index.json`. -- Sample corpus: - - `samples/2007쉘구조물의유한요소해석에대하여.pdf` - - `samples/FourNodeQuadrilateralShellElementMITC4.pdf` - - `samples/MITC공부.pdf` - - `samples/유한요소해석법을이용한쉘구조물의동적좌굴해석.pdf` - -## Research Tracks -1. Toolchain research - - Verify Marker, Nougat, PyMuPDF, PyTorch/CUDA, Pandas, and Markdown/math-rendering constraints. - - Track licensing risks. Current use is personal, but revisit if distribution or commercial use becomes relevant. - - Compare Marker-first architecture against PyMuPDF4LLM, Docling, and MinerU as quality baselines only. - - Keep `docs/TOOLCHAIN.md` updated when dependency pins or compatibility findings change. - -2. Conversion architecture - - Define stable internal document/block types. - - Keep Marker document/block structure as the main source for headings, body text, reading order, figures, tables, and captions. - - Treat Nougat output as formula text input subject to validation and fallback policy. - - Keep PyMuPDF responsible for page counts, chunk planning, and low-level PDF/page operations. - - Follow `docs/CONVERSION_POLICY.md` for OCR decisions, parser handoff rules, fallback behavior, chunk boundary handling, logging, and resume policy. - -3. Quality and regression strategy - - Prefer focused assertions over full Markdown snapshots. - - Validate headings, formula delimiters, begin/end pairs, table shape, image links, captions, and no-exception conversion. - - Include Korean filenames and Windows paths in regression coverage. - - Include VRAM pressure and long-document chunking scenarios. - -4. Runtime strategy - - Use repo-local Python environments. - - Use a single `venv` for Marker/PyMuPDF/Pandas/tests and Nougat. - - Use CUDA-enabled PyTorch compatible with the installed NVIDIA driver and GTX 1070 Ti. - - Current verified PyTorch choice is `torch==2.7.1+cu126`, because newer `torch==2.11.0+cu128` does not support GTX 1070 Ti `sm_61`. - - Keep Nougat dependency pins explicit inside the unified environment, especially `transformers==4.57.6`, `albumentations==1.3.1`, `pypdfium2==4.30.0`, `opencv-python-headless==4.11.0.86`, `Pillow==10.4.0`, and `fsspec==2026.2.0`. - -## Created Project Agent Roles -The user approved creating the project-scoped Codex extensions on 2026-04-30. These read-only agents now live under `.codex/agents/`. - -1. `pdf_toolchain_researcher` - - Read-only. - - Owns official-doc research for Marker, Nougat, PyMuPDF, PyTorch/CUDA, Markdown math, and comparison tools. - - Outputs compatibility notes, licensing notes, and recommended dependency constraints. - -2. `conversion_architect` - - Read-only at first. - - Owns engine boundaries, internal data contracts, chunk policy, adapter interfaces, and output contract. - - Outputs phase-ready architecture notes and acceptance criteria. - -3. `quality_evaluator` - - Read-only at first. - - Owns sample-corpus classification, focused quality checks, regression fixtures, and failure taxonomy. - - Outputs test strategy before implementation begins. - -4. `formula_pipeline_specialist` - - Read-only at first. - - Owns Nougat integration assumptions, formula extraction boundaries, LaTeX delimiter validation, and fallback policy. - -5. `layout_table_figure_specialist` - - Read-only at first. - - Owns reading order, paragraph stitching, table rendering, figure extraction, caption linking, and cross-reference preservation. - -6. `sample_corpus_analyst` - - Read-only at first. - - Owns sample PDF corpus analysis, OCR-candidate identification, metadata schema suggestions, and regression implications. - -## Future Agent Roles -- `marker_adapter_worker`: implementation worker for Marker adapter code, after TDD phase approval. -- `markdown_renderer_worker`: implementation worker for Markdown renderer and output contract, after TDD phase approval. -- `runtime_cli_worker`: implementation worker for CLI/runtime/device behavior, after TDD phase approval. -- `test_fixture_worker`: implementation worker for sample metadata and focused pytest fixtures, after TDD phase approval. - -## Harness Execution Model -This project now follows a file-based planner/generator/evaluator workflow for long-running work. - -1. Planner creates or updates `phases/` steps from `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, and `docs/*.md`. -2. Generator executes one `stepN.md` at a time and stays inside that step's owned files and Do Not list. -3. Evaluator reviews the result against the step's Sprint Contract and hard thresholds before the work is considered complete. -4. Communication and handoff happen through files, not only chat: - - `PLAN.md` for overall work plan. - - `PROGRESS.md` for current state and next handoff. - - `phases/{phase}/index.json` for step execution status. - - `docs/HARNESS.md` for role and contract rules. - -## Active Phase Plan -Phase registry: -- `phases/index.json` - -Full phase roadmap: -1. `0-harness-foundation`: sample metadata, core models, PyMuPDF pre-analysis contract, Markdown quality gates. -2. `1-core-runtime-contracts`: input normalization, conversion options, output bundle contract, runtime cache policy. -3. `2-marker-adapter`: Marker invocation, OCR plan handoff, block normalization, parser failure reporting. -4. `3-formula-pipeline`: formula candidate detection, Nougat command adapter, LaTeX validation/repair, formula reference links. -5. `4-semantic-enrichment`: reading-order checks, paragraph stitching, header/footer filtering, figure/table/formula reference indexing. -6. `5-markdown-rendering-assets`: block renderer, table renderer/fallbacks, figure asset writer, chunk renderer. -7. `6-cli-runtime-resume`: CLI options, progress/logging, resume state, CUDA/OOM policy, model cache/offline support. -8. `7-mvp-quality-hardening`: sample smoke conversions, quality metrics, regression thresholds, MVP fix sweep. -9. `8-release-docs-packaging`: usage docs, environment bootstrap docs, license checkpoint, local release checklist. -10. `9-pyqt-thin-client`: UI API contract, PyQt shell, UI progress/resume, UI packaging notes. - -Detailed phase goals and dependencies are recorded in `docs/IMPLEMENTATION_PLAN.md`. Executable step contracts live under each `phases/{phase}/stepN.md`. - -## Priority Order -1. Execute `phases/0-harness-foundation/step0.md` only after the user wants implementation to begin. -2. Keep each implementation step inside its Sprint Contract and TDD requirements. -3. Review each completed phase before starting the next phase. -4. Treat PyQt and external API work as post-MVP unless the user explicitly changes scope. - -## Acceptance Criteria For Planning Stage -- `PLAN.md` and `PROGRESS.md` exist and reflect the current goal. -- `docs/CONVERSION_POLICY.md` records parser, OCR, formula, table, figure, chunk, runtime, logging, resume, and quality-test policy decisions. -- `docs/TOOLCHAIN.md` records verified local dependency and compatibility decisions. -- Environment decision is recorded. -- `requirements.txt` records the verified single-environment dependency pins. -- `docs/HARNESS.md` records the planner/generator/evaluator workflow. -- `docs/IMPLEMENTATION_PLAN.md` records the full phase roadmap. -- `phases/index.json` and `phases/*/stepN.md` provide executable self-contained tickets. -- No custom agent file is created without explicit user approval. -- Repository validation command remains runnable: - -```bash -python scripts/validate_workspace.py -``` diff --git a/PROGRESS.md b/PROGRESS.md deleted file mode 100644 index 2826da3..0000000 --- a/PROGRESS.md +++ /dev/null @@ -1,177 +0,0 @@ -# PDFtoMD Progress - -## Current Status -- Date: 2026-04-30. -- Mode: Phase 1 implementation complete; ready for Phase 1 review or Phase 2 handoff. -- Implementation status: Phase 0 and Phase 1 complete. -- Custom agent creation: project-scoped read-only agents created after user approval. -- Persistent multi-agent coordination files were approved by the user. - -## Completed -- Read repository instructions and project documents: - - `AGENTS.md` - - `docs/PRD.md` - - `docs/ARCHITECTURE.md` - - `docs/ADR.md` - - `docs/UI_GUIDE.md` -- Confirmed project direction: - - Windows-native local CLI/library engine first. - - Marker for document structure, reading order, tables, figures, headings, and captions. - - Nougat only for mathematical expressions/formulas. - - PyMuPDF for PDF analysis and chunk planning. - - Markdown chunk output plus image/table assets. -- Recorded detailed conversion policy decisions in `docs/CONVERSION_POLICY.md`. -- Strengthened project documentation with current research and decisions: - - `AGENTS.md` - - `README.md` - - `docs/PRD.md` - - `docs/ARCHITECTURE.md` - - `docs/ADR.md` - - `docs/TOOLCHAIN.md` - - `docs/UI_GUIDE.md` -- Strengthened `AGENTS.md` multi-agent coordination rules so every new agent reads `PLAN.md` and `PROGRESS.md` first and can identify current goals, assigned scope, completed work, blockers, next work, and conflict risks. -- Created project-scoped Codex extensions under `.codex/`: - - Agents: `pdf-toolchain-researcher`, `sample-corpus-analyst`, `conversion-architect`, `quality-evaluator`, `formula-pipeline-specialist`, `layout-table-figure-specialist`. - - Commands: `status`, `env-check`, `sample-audit`, `quality-plan`, `conversion-policy-review`, `model-cache-check`, `phase-draft`. - - Skills: `pdf-toolchain`, `sample-corpus`, `conversion-architecture`, `formula-quality`, `markdown-quality`, `windows-runtime`. - - Hooks: strengthened risky command guard, added handoff policy and drift policy hooks. -- Validated `.codex` extension formats: - - Agent TOML files parsed successfully. - - `.codex/hooks.json` parsed successfully. - - Hook Python scripts compiled successfully. - - All `.codex/skills/*/SKILL.md` files passed `skill-creator` quick validation. -- Confirmed user environment: - - Windows 10. - - NVIDIA GeForce GTX 1070 Ti. - - 8 GB VRAM. - - NVIDIA driver 577.00. - - `nvidia-smi` reports CUDA runtime capability 12.9. - - User reports CUDA 12.4 installed. - - Current detected Python: Miniforge Python 3.12.7. - - Conda is available. - - `uv` is not available. -- Created repo-local environment: - - `venv`: Python 3.11.15, unified Marker/PyMuPDF/Pandas/test/Nougat environment. -- Removed previous experimental `venv-nougat` directory after unified `venv` validation passed. -- Verified unified environment: - - `torch==2.7.1+cu126` - - `torchvision==0.22.1+cu126` - - `marker-pdf==1.10.2` - - `nougat-ocr==0.1.17` - - `transformers==4.57.6` - - `albumentations==1.3.1` - - `fsspec==2026.2.0` - - `pymupdf==1.27.2.3` - - `pandas==3.0.2` - - `pytest==9.0.3` - - `Pillow==10.4.0` - - `pypdfium2==4.30.0` - - `opencv-python-headless==4.11.0.86` - - `pip check`: passed. - - CUDA tensor operation on GTX 1070 Ti: passed. - - `venv\Scripts\nougat.exe --help`: passed. -- Ran earlier repository validation before default Python test discovery was added: - - `python scripts/validate_workspace.py`: passed at that time with no configured validation commands. -- Confirmed sample PDFs: - - `samples/2007쉘구조물의유한요소해석에대하여.pdf`: 13 pages, first page text length 3523, first page images 0. - - `samples/FourNodeQuadrilateralShellElementMITC4.pdf`: 7 pages, first page text length 3269, first page images 0. - - `samples/MITC공부.pdf`: 13 pages, first page text length 226, first page images 2. - - `samples/유한요소해석법을이용한쉘구조물의동적좌굴해석.pdf`: 76 pages, first page text length 446, first page images 10. -- Strengthened the project for Anthropic-style Harness Engineering: - - Added `docs/HARNESS.md` with planner/generator/evaluator roles, file protocol, Sprint Contract template, evaluator hard thresholds, and simplification rules. - - Added executable phase registry `phases/index.json`. - - Added first self-contained phase `phases/0-harness-foundation/` with four pending steps: - - `sample-metadata-contract` - - `core-package-skeleton` - - `page-preanalysis-contract` - - `markdown-quality-gates` - - Updated `AGENTS.md`, `PLAN.md`, `README.md`, `docs/ARCHITECTURE.md`, and `docs/ADR.md` to reference the Harness workflow. - - Added `.codex/commands/sprint-contract.md`. - - Strengthened Harness workflow/review skill guidance to require Sprint Contracts. - - Updated hooks for simpler Windows-friendly command paths and expanded handoff checks to include `phases/`, `scripts/`, `.agents/`, and `plugins/`. - - Made `scripts/validate_workspace.py` discover repo-local Python validation by default. - - Added `scripts/test_validate_workspace.py` and fixed `scripts/test_execute.py` UTF-8 fixture handling on Windows. -- Established the full phase-by-phase implementation roadmap before starting engine implementation: - - Added `docs/IMPLEMENTATION_PLAN.md`. - - Expanded `phases/index.json` from Phase 0 only to Phases 0 through 9. - - Added executable pending step contracts for: - - `1-core-runtime-contracts` - - `2-marker-adapter` - - `3-formula-pipeline` - - `4-semantic-enrichment` - - `5-markdown-rendering-assets` - - `6-cli-runtime-resume` - - `7-mvp-quality-hardening` - - `8-release-docs-packaging` - - `9-pyqt-thin-client` - - Updated `PLAN.md`, `AGENTS.md`, and `README.md` to point new agents to the full implementation roadmap. -- Implemented Phase 0 Harness foundation: - - Step 0 `sample-metadata-contract`: added deterministic `samples/metadata.json` and metadata contract tests. - - Step 1 `core-package-skeleton`: added `pyproject.toml`, importable `src/pdftomd` package, typed model contracts, and model tests. - - Step 2 `page-preanalysis-contract`: added PyMuPDF-only `analyze_pdf()` preanalysis, deterministic OCR candidate logic, and chunk candidate tests. - - Step 3 `markdown-quality-gates`: added focused Markdown quality gates and tests for math delimiters, LaTeX environments, image links, tables, chunk frontmatter, and anchors. - - Parallel work was split by disjoint write scopes: sample metadata/model contracts first, then preanalysis/quality gates. -- Reviewed Phase 0 with `harness-review` criteria: - - No blocking findings. - - Architecture boundary remained intact: Marker and Nougat are not invoked in foundation contracts, and PyMuPDF is limited to page pre-analysis. - - `python scripts\validate_workspace.py` passed before Phase 1 work started. -- Implemented Phase 1 core runtime contracts: - - Step 0 `input-normalization-slug`: added deterministic PDF path normalization, document identity creation, anchors, and output bundle path contracts. - - Step 1 `conversion-options-config`: added typed conversion options, runtime modes, and formula parser options without CLI parsing. - - Step 2 `output-bundle-contract`: added deterministic document bundle paths while keeping runtime artifacts separate from document output. - - Step 3 `runtime-cache-policy`: added explicit `.models/` default cache policy, `PDFTOMD_MODEL_CACHE` override, Hugging Face offline environment mappings, and runtime artifact paths. - - Updated `docs/TOOLCHAIN.md` and `.gitignore` for model cache policy. - -## Web Research Notes -- Marker currently supports Markdown/JSON/chunks/HTML output and includes tables, equations, inline math, image extraction, layout, and reading-order functionality. -- Nougat is the intended isolated formula parser candidate; Windows GPU use depends on a correct PyTorch install. -- PyMuPDF remains appropriate for page counting, PDF splitting/chunk planning, and low-level image/page operations. -- PyMuPDF4LLM, Docling, and MinerU are useful comparison baselines but are not the primary parser under the current architecture. -- MathJax notes that `$...$` inline math can conflict with ordinary dollar signs, so delimiter validation is required. - -## In Progress -- None. - -## Blockers -- None yet. - -## Decisions -- Personal-use context lowers immediate licensing risk, but Marker GPL/model license implications must be revisited before redistribution or commercial use. -- Mixed text/scanned PDFs are in scope, with page-level OCR intervention decisions based on lightweight text-layer quality analysis. -- Marker owns layout, reading order, body text, headings, tables, figures, captions, and OCR/layout handling. -- Nougat owns only mathematical expressions and formula blocks, with Marker text fallback on failure. -- Markdown tables are preferred, but limited HTML tables and table-region screenshot fallbacks are allowed for complex tables. -- Figure/table/formula numbers and body references should become internal Markdown links when confidence is sufficient. -- Chunking should prefer logical block boundaries over strict 20-page boundaries when a block would be split. -- Chunk Markdown may include concise frontmatter with core context, but document-output sidecars remain out of scope by default. -- CLI should write warnings/errors to stderr and local logs, not into generated Markdown. -- Resume support may use local runtime state/cache files to skip successful chunks. -- Custom agents will be created later, only one at a time after explicit user approval. -- Planning files are the source of truth for multi-agent coordination. -- Harness phase files now exist. `PLAN.md` remains the overall plan, `PROGRESS.md` remains the handoff state, and `phases/{phase}/index.json` is the phase execution status. -- Each future implementation step should use the `docs/HARNESS.md` planner/generator/evaluator workflow and include a Sprint Contract before code changes. -- Full implementation sequencing is recorded in `docs/IMPLEMENTATION_PLAN.md`; phase files are pending tickets and should not be executed out of dependency order. -- Phase 0 and Phase 1 are complete. `phases/index.json` marks both `0-harness-foundation` and `1-core-runtime-contracts` as completed. -- Main and Nougat dependencies can share one environment when Nougat's loose dependencies are pinned explicitly. -- `torch==2.11.0+cu128` was rejected for this machine because it does not support GTX 1070 Ti `sm_61`. -- `torch==2.7.1+cu126` was selected because it satisfies Marker `torch>=2.7.0` and successfully runs CUDA tensor operations on GTX 1070 Ti. -- `nougat-ocr==0.1.17` requires dependency pins: - - `transformers==4.57.6`, because `transformers 5.7.0` breaks Nougat imports. - - `albumentations==1.3.1`, because `albumentations 2.x` breaks Nougat transform initialization. - - `fsspec==2026.2.0`, because newer `fsspec` conflicts with `datasets`. - - `pypdfium2==4.30.0`, `opencv-python-headless==4.11.0.86`, and `Pillow==10.4.0`, because Marker/Surya depend on these versions and Nougat can operate with them. - -## Next Work -1. Review Phase 1 output with `harness-review` before moving to Phase 2. -2. If review passes, start `phases/2-marker-adapter/step0.md`. -3. Execute phases in order unless `PLAN.md` and `docs/IMPLEMENTATION_PLAN.md` are updated with a clear dependency rationale. -4. Do not create new custom agents unless the user explicitly approves another agent. - -## Latest Validation -- `.\venv\python.exe -m pytest scripts\test_validate_workspace.py`: passed, 7 tests. -- `.\venv\python.exe -m py_compile scripts\execute.py scripts\validate_workspace.py .codex\hooks\*.py`: passed. -- JSON parse check for `phases/index.json`, `phases/0-harness-foundation/index.json`, and `.codex/hooks.json`: passed. -- Phase structure check for all `stepN.md` files: passed. -- `.codex/commands/*.md` frontmatter check: passed. -- `python scripts\validate_workspace.py`: passed, 103 tests after Phase 1 implementation. -- `.\venv\python.exe -c "import pdftomd; print(pdftomd.__name__)"`: passed after adding editable package metadata. diff --git a/README.md b/README.md deleted file mode 100644 index 6ac2eff..0000000 --- a/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# PDFtoMD - -PDFtoMD는 수학, 공학, 역학 중심 PDF를 AI Agent가 읽기 쉬운 Markdown 문서 묶음으로 변환하는 로컬 우선 변환 엔진입니다. - -목표는 단순 텍스트 추출이 아니라 원문 문서의 읽기 순서, 문단 흐름, 수식, 표, 그림, 캡션, 본문 참조를 보존한 구조화 변환입니다. - -## Status -- Current phase: Harness foundation planning. -- Implementation: not started. -- Primary target: Windows 10 native CLI/library engine. -- UI: future PyQt thin client. - -## Core Direction -- Marker handles document structure, reading order, OCR/layout, body text, tables, figures, headings, and captions. -- Nougat handles only mathematical expressions and formula blocks. -- PyMuPDF handles lightweight page analysis, text-layer quality checks, page counts, chunk planning, and low-level PDF operations. -- Mixed text/scanned PDFs are in scope. -- Output is chunked Markdown plus image/table assets under a document slug directory. - -## Environment -Use one repo-local Python 3.11 environment. - -```powershell -conda create -p .\venv python=3.11 -y -.\venv\python.exe -m pip install -r requirements.txt -``` - -Verified local baseline: -- Windows 10 -- NVIDIA GeForce GTX 1070 Ti, 8 GB VRAM -- NVIDIA driver 577.00 -- PyTorch `2.7.1+cu126` -- Marker `1.10.2` -- Nougat OCR `0.1.17` - -## Verification -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pip check -.\venv\python.exe -c "import torch; x=torch.ones((1,), device='cuda'); print(torch.__version__, torch.version.cuda, x.item())" -.\venv\Scripts\nougat.exe --help -``` - -`scripts/validate_workspace.py` now discovers repo-local Python validation by default. It prefers `.\venv\python.exe`, compiles Harness scripts, and runs `scripts/test_*.py` with pytest unless `HARNESS_VALIDATION_COMMANDS` or npm scripts override discovery. - -## Important Documents -- `AGENTS.md`: persistent repository instructions. -- `PLAN.md`: multi-agent planning state. -- `PROGRESS.md`: multi-agent progress state. -- `phases/`: executable Harness phase tickets. -- `docs/PRD.md`: product requirements. -- `docs/ARCHITECTURE.md`: engine architecture. -- `docs/CONVERSION_POLICY.md`: detailed conversion decisions. -- `docs/HARNESS.md`: planner/generator/evaluator Harness workflow. -- `docs/IMPLEMENTATION_PLAN.md`: full phase-by-phase implementation roadmap. -- `docs/ADR.md`: architecture decision records. -- `docs/TOOLCHAIN.md`: toolchain and dependency notes. -- `docs/UI_GUIDE.md`: future PyQt UI guidance. - -## Sample Corpus -The `samples/` directory is used for quality evaluation and regression tests. Current sample PDFs include Korean filenames, engineering/mechanics documents, formulas, figures, and a long 76-page document. - -Before implementation, create a sample metadata mapping file that tags each PDF by text-layer quality, scanned pages, multi-column layout, formula density, table density, figure density, and Korean filename coverage. diff --git a/docs/ADR.md b/docs/ADR.md deleted file mode 100644 index b74768b..0000000 --- a/docs/ADR.md +++ /dev/null @@ -1,142 +0,0 @@ -# Architecture Decision Records - -## 철학 -프로젝트의 핵심 가치관: -- 정확한 수식 변환 -- 로컬 작동 -- 메모리 최적 사용 -- AI Agent가 탐색하기 쉬운 deterministic Markdown bundle -- 원문 구조와 참조 관계 보존 - ---- - -## ADR-001: Marker-first document parsing -**결정**: Marker를 기본 PDF parser로 사용한다. - -**이유**: -- Marker는 layout, OCR, reading order, table, figure, caption, heading을 포함한 문서 구조 추적에 적합하다. -- 프로젝트 목표는 단순 텍스트 추출이 아니라 원문 논리 구조를 Markdown으로 재구성하는 것이다. - -**트레이드오프**: -- Marker 의존성 및 model weight 관리가 필요하다. -- 배포 가능성이 생기면 GPL 및 model license 검토가 필요하다. - ---- - -## ADR-002: Nougat as formula-only parser -**결정**: Nougat은 전체 PDF parser가 아니라 수식 및 수학적 표현 parser로만 사용한다. - -**이유**: -- Nougat은 학술 문서의 수식/LaTeX 변환에 강점이 있다. -- 전체 문서 구조는 Marker가 담당해야 reading order, 표, 그림, caption 경로가 일관된다. - -**트레이드오프**: -- Marker block과 Nougat 결과를 연결하는 handoff/fallback 계층이 필요하다. -- Nougat 실패 시 Marker 원문 문자열을 fallback으로 사용해야 한다. - ---- - -## ADR-003: PyMuPDF page pre-analysis and chunk planning -**결정**: PyMuPDF를 페이지 수, 텍스트 레이어 품질, OCR 필요 여부, chunk 계획, 저수준 PDF 작업에 사용한다. - -**이유**: -- 무거운 parser 실행 전에 빠른 page-level 분석이 필요하다. -- 혼합 PDF는 페이지별 OCR 개입 여부를 판단해야 한다. -- 긴 PDF는 20페이지 목표 chunk로 나누되 논리 block 경계를 고려해야 한다. - -**트레이드오프**: -- PyMuPDF 분석 결과와 Marker layout 결과를 조정하는 adapter가 필요하다. - ---- - -## ADR-004: Single Python 3.11 environment -**결정**: repo-local 단일 Python 3.11 `venv`를 사용한다. - -**이유**: -- 개발과 실행 경로를 단순화한다. -- Marker와 Nougat은 명시적 dependency pin을 두면 하나의 환경에서 함께 동작한다. - -**검증된 주요 pin**: -- `torch==2.7.1+cu126` -- `torchvision==0.22.1+cu126` -- `marker-pdf==1.10.2` -- `nougat-ocr==0.1.17` -- `transformers==4.57.6` -- `albumentations==1.3.1` -- `pypdfium2==4.30.0` -- `opencv-python-headless==4.11.0.86` -- `Pillow==10.4.0` -- `fsspec==2026.2.0` - -**트레이드오프**: -- Nougat의 느슨한 dependency bounds 때문에 requirements pin을 엄격히 유지해야 한다. -- 최신 PyTorch를 무조건 사용할 수 없다. GTX 1070 Ti `sm_61` 지원 때문에 `torch==2.7.1+cu126`을 사용한다. - ---- - -## ADR-005: Markdown bundle output without document sidecars by default -**결정**: 기본 출력은 chunk Markdown 파일과 asset directory로 제한한다. - -**이유**: -- AI Agent가 읽고 탐색하기 쉬운 산출물을 우선한다. -- 별도 sidecar 산출물은 사용자가 명시적으로 요청하기 전까지 범위를 넓히지 않는다. - -**트레이드오프**: -- 변환 diagnostics를 문서 출력과 분리해야 한다. -- runtime log/state/cache는 허용하되 문서 output contract와 구분해야 한다. - ---- - -## ADR-006: Focused quality assertions over full snapshots -**결정**: 전체 Markdown snapshot 비교보다 focused assertions를 우선한다. - -**이유**: -- PDF 변환 결과는 줄바꿈, spacing, parser version에 민감하다. -- 품질 핵심은 heading, 수식, 표, 이미지, caption, 링크, chunk integrity, 예외 여부다. - -**트레이드오프**: -- 테스트 설계가 더 세분화된다. -- sample metadata mapping이 필요하다. - ---- - -## ADR-007: Runtime fallback policy -**결정**: -- explicit `--runtime cuda` 또는 `--device cuda`는 CUDA 실패 시 fail-fast. -- `--runtime auto`는 경고 후 CPU fallback 허용. -- GPU OOM은 가능한 경우 batch/page 단위를 줄여 재시도. - -**이유**: -- 사용자가 CUDA를 명시한 경우 조용한 CPU 전환은 예측 불가능한 지연을 만든다. -- auto mode는 유연한 실행을 제공해야 한다. - -**트레이드오프**: -- runtime state와 오류 reporting이 필요하다. - ---- - -## ADR-008: Future PyQt UI as thin client -**결정**: PyQt UI는 변환 엔진을 직접 구현하지 않고 CLI/라이브러리 API를 호출하는 thin client로 둔다. - -**이유**: -- 1차 목표는 CLI/library 엔진 안정화다. -- UI와 core engine의 책임을 분리해야 테스트와 유지보수가 쉽다. - -**트레이드오프**: -- UI 설계 전에 core API contract를 안정화해야 한다. - ---- - -## ADR-009: File-based planner/generator/evaluator Harness -**결정**: 장기 작업은 `planner -> generator -> evaluator` 역할 분리와 파일 기반 handoff를 사용하는 Harness workflow로 관리한다. - -**이유**: -- PDF 변환 엔진은 parser, OCR, 수식, 표, 그림, runtime, 테스트가 얽힌 장기 작업이므로 단일 대화에서 일관성을 유지하기 어렵다. -- 작은 self-contained phase step은 새 agent가 fresh context로 작업을 이어받기 쉽게 한다. -- 구현 agent와 평가 agent를 분리하면 자기 평가 편향을 줄이고, hard threshold 기반 검증을 강제할 수 있다. -- `PLAN.md`, `PROGRESS.md`, `phases/` 파일을 통한 handoff는 대화 밖에서도 현재 상태를 재구성할 수 있게 한다. - -**트레이드오프**: -- 각 step마다 Sprint Contract와 검증 기준을 작성하는 비용이 생긴다. -- 너무 많은 agent, hook, command를 추가하면 Harness 자체가 유지보수 대상이 될 수 있으므로 `docs/HARNESS.md`의 단순화 규칙을 따른다. -- Hook은 보조 장치일 뿐이며, evaluator 검토와 acceptance criteria를 대체하지 않는다. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 8272252..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,152 +0,0 @@ -# Architecture - -## Scope -현재 구현 목표는 1차 목표인 Windows native, local-first CLI/library 변환 엔진입니다. - -- 기본 parser: `Marker` -- 기본 수식 parser: `Nougat` -- PDF 분석과 chunk 계획: `PyMuPDF` -- 출력: Markdown chunk files plus assets -- 기본 chunk 목표: 20페이지 -- 기본 runtime: CUDA -- UI, hosted API, 기본 LLM 보정 경로는 1차 목표 범위 밖입니다. - -## Architecture Principles -- Marker-first architecture를 유지합니다. -- Nougat은 전체 문서 parser가 아니라 수식 parser입니다. -- PyMuPDF는 무거운 변환 전에 빠른 page-level 분석과 chunk 계획을 담당합니다. -- 출력은 AI Agent가 탐색하기 쉬운 deterministic Markdown bundle이어야 합니다. -- 복잡한 table/figure/formula 손실 가능성은 fallback과 품질 검증으로 다룹니다. -- 생성 Markdown은 원문 문서 내용 중심이어야 하며 경고/오류 로그로 오염시키지 않습니다. - -## Pipeline -1. Input normalization - - PDF path를 `pathlib` 기반으로 정규화합니다. - - 한글, 공백, 긴 Windows 경로를 지원합니다. - - document slug를 결정적으로 생성합니다. - -2. Page pre-analysis - - PyMuPDF로 page count, text length, image count, text-layer quality를 확인합니다. - - 페이지별 OCR 필요 여부를 추정합니다. - - 긴 문서는 20페이지 목표 chunk 계획을 세우되 logical block boundary 보존을 고려합니다. - -3. Marker parse - - Marker가 layout, OCR, reading order, body text, headings, tables, figures, captions, semantic blocks를 담당합니다. - - Marker Document Model 또는 이에 준하는 구조화 출력을 내부 block model로 매핑합니다. - -4. Formula handoff - - Marker equation block 또는 수식 패턴이 감지된 block만 Nougat에 전달합니다. - - Nougat 결과는 LaTeX 문자열 후보로 취급하며 validation과 fallback 정책을 통과해야 합니다. - - Nougat 실패 시 Marker 원문 수식 문자열을 사용합니다. - -5. Semantic enrichment - - 수식 번호, figure 번호, table 번호, caption, 본문 참조를 식별합니다. - - 식별 confidence가 충분하면 내부 Markdown link로 연결합니다. - - header/footer/page-number 반복 패턴은 본문 흐름에서 제거하거나 분리합니다. - -6. Markdown rendering - - heading, paragraph, list, blockquote, table, figure, equation block을 Markdown으로 렌더링합니다. - - Markdown table을 우선하되 복잡한 표는 제한적 HTML table 또는 이미지 fallback을 사용합니다. - - 각 chunk에는 문서 제목, page range, chunk 번호 등 최소 frontmatter를 넣을 수 있습니다. - -7. Asset writing - - 이미지는 `images/` 아래 결정적 파일명으로 저장합니다. - - figure 번호가 있으면 `{document-slug}_fig-{figure-number}.png`를 우선합니다. - - 충돌 또는 번호 부재 시 chunk/page/block identifier를 사용합니다. - - hash 기반 deduplication으로 중복 asset 저장을 줄입니다. - -8. Validation and reporting - - math delimiter balance, LaTeX environment pairs, table parseability, image link existence, caption matching, chunk boundary integrity를 검증합니다. - - CLI는 progress bar와 chunk별 성공/실패를 표시합니다. - - 오류와 경고는 stderr와 local log에 기록합니다. - -## Planned Layout -```text -samples/ # regression and quality corpus -tests/ # focused pytest coverage -scripts/ # validation / harness helpers -phases/ # executable Harness phase tickets -src/ # source package, planned -venv/ # repo-local Windows virtual environment, ignored by git -output/ # conversion output, ignored by git -``` - -## Harness Boundary -- `docs/HARNESS.md` defines the planner/generator/evaluator workflow for long-running work. -- `phases/` files are execution tickets, not architecture policy. Architecture policy remains in `docs/ARCHITECTURE.md`, `docs/CONVERSION_POLICY.md`, and `docs/ADR.md`. -- Each implementation phase must keep parser, formula, pre-analysis, renderer, runtime, and UI responsibilities separated according to this document. -- Evaluator checks should use hard thresholds from each step's Sprint Contract and the focused quality strategy below. - -## Output Contract -출력은 문서 slug 디렉터리 아래에 묶입니다. - -```text -output/ -└── document-slug/ - ├── document-slug_001.md - ├── document-slug_002.md - └── images/ - ├── document-slug_fig-001.png - └── document-slug_fig-003.png -``` - -세부 규칙: -- chunk Markdown 파일명은 `_.md` -- image asset은 `images/` -- 같은 입력과 같은 옵션은 같은 output path를 생성해야 합니다. -- 별도 문서 sidecar metadata/log 산출물은 기본 output contract에 포함하지 않습니다. -- local log와 resume state/cache는 runtime artifact이며 문서 출력 contract와 구분합니다. - -## Runtime Policy -- 기본 runtime은 `cuda` -- explicit `--runtime cuda` 또는 `--device cuda`에서 CUDA가 준비되지 않았으면 빠르게 실패 -- `--runtime auto`는 필요 시 CPU fallback 경고를 출력 -- GTX 1070 Ti 8GB 기준 batch size는 1~2 수준에서 시작 -- GPU OOM 시 가능한 경우 batch/page 단위를 줄여 재시도 -- 수식 parser 기본값은 `nougat` -- verified PyTorch baseline은 `torch==2.7.1+cu126` - -## Environment -단일 repo-local Python 3.11 `venv`를 사용합니다. - -```powershell -conda create -p .\venv python=3.11 -y -.\venv\python.exe -m pip install -r requirements.txt -``` - -주요 pin: -- `torch==2.7.1+cu126` -- `torchvision==0.22.1+cu126` -- `marker-pdf==1.10.2` -- `nougat-ocr==0.1.17` -- `transformers==4.57.6` -- `albumentations==1.3.1` -- `pypdfium2==4.30.0` -- `opencv-python-headless==4.11.0.86` -- `Pillow==10.4.0` -- `fsspec==2026.2.0` - -## Model Cache And Offline Mode -- 모델 cache 위치는 명시적으로 관리해야 합니다. -- 최초 다운로드 이후 offline 실행 시 이미 받은 weight를 우선 사용해야 합니다. -- README에는 model download와 offline 실행 절차를 별도로 추가해야 합니다. - -## Quality Strategy -- 전체 Markdown snapshot 비교는 주요 검증 방식으로 사용하지 않습니다. -- focused assertions를 우선합니다. -- 검증 대상: - - heading hierarchy - - math delimiter balance - - LaTeX `\begin` / `\end` pairs - - image link existence - - figure/table/formula caption matching - - table parseability - - chunk boundary integrity - - Windows path and Korean filename handling - - no-exception conversion - -## Out of Scope for the First Goal -- PyQt UI 구현 -- hosted conversion API 기본 경로화 -- LLM 보정 모드 기본 경로화 -- 생성 문서와 함께 배포되는 별도 sidecar metadata/log 산출물 diff --git a/docs/CONVERSION_POLICY.md b/docs/CONVERSION_POLICY.md deleted file mode 100644 index 2f9d28a..0000000 --- a/docs/CONVERSION_POLICY.md +++ /dev/null @@ -1,91 +0,0 @@ -# Conversion Policy - -This document records implementation decisions for the PDF-to-Markdown conversion engine. It is planning guidance, not implementation code. - -## Input Classification -- Support mixed PDFs by default: text-layer pages, scanned pages, and mixed pages can appear in the same document. -- Use PyMuPDF or equivalent lightweight page analysis before heavy parsing to estimate text-layer quality per page. -- Decide OCR intervention per page instead of treating the entire PDF as text-only or scan-only. -- Prefer Marker's OCR/layout functionality for scanned or weak text-layer pages. - -## Parser Responsibilities -- Marker owns overall layout tracking, reading order, body extraction, table structure, image extraction, headings, captions, and semantic block roles. -- Nougat owns only mathematical expressions and formula block parsing. -- Do not use Nougat as the main document parser. -- Send a block to Nougat when Marker identifies it as an equation area or when text-pattern detection marks it as mathematical content. -- If Nougat conversion fails, preserve information by falling back to Marker's extracted source text. - -## Formula Handling -- Treat formulas embedded inside a sentence without independent line spacing as inline formulas. -- Treat formulas occupying independent line space or vertical whitespace as block formulas. -- Preserve formula numbers detected near the right or bottom side of a formula region. -- Attach anchors to extracted formula numbers and rewrite body references such as `Eq. (3)` or `식 (5)` as internal Markdown links when confidence is sufficient. -- Validate Markdown math delimiters by counting opening and closing `$ ... $` and `$$ ... $$` pairs across each chunk. -- Validate common LaTeX environments by checking matching `\begin{...}` and `\end{...}` names and counts. -- If delimiter or environment validation fails, repair the closest logical location in a way that keeps Markdown rendering intact. - -## Tables -- Prefer Markdown tables when structure can be represented without major loss. -- Use limited HTML `
` output for tables with merged cells, multi-row headers, or structures that exceed GitHub Flavored Markdown table expressiveness. -- Preserve table footnotes as regular text immediately below the table. -- Preserve top or bottom captions as text and create internal links from body references such as `Table 1`. -- If structured table extraction loses too much information, also save a screenshot of the table region as a fallback asset and link it near the structured output. - -## Figures And Images -- Use deterministic image asset naming such as `{document-slug}_fig-{figure-number}.png` when a figure number is available. -- Include chunk/page/block identifiers in names or anchors when needed to avoid collisions. -- Place extracted image assets in the document `images/` directory. -- Add figure captions below Markdown image links. -- Rewrite body references such as `Fig. 2` to internal Markdown links when the figure target can be identified. -- Deduplicate extracted images by hash and let repeated references share one asset and anchor. - -## Reading Order And Paragraph Flow -- Stitch lines into paragraphs when a line does not end with terminal punctuation and the next line begins like a continuation, or when bounding-box line spacing matches intra-paragraph spacing. -- Join hyphenated line breaks when a line-ending hyphen is followed by a lowercase continuation without whitespace. -- Preserve hyphens for known compounds, identifiers, or proper nouns when confidence is low. -- Use Marker bounding boxes to validate that the linearized text flow matches expected reading order in sample PDFs. -- Detect repeated header/footer/page-number patterns in stable top/bottom page regions and exclude them from body Markdown, or separate them from the main body flow. - -## Chunking -- Use 20 pages as the default chunk target. -- Prefer logical block boundaries over strict page boundaries when a paragraph, formula, table, or figure would be cut in the middle. -- If a block crosses a chunk boundary, keep the block intact by moving it to the previous or next chunk according to the least damaging boundary. -- Add minimal context at the top of each chunk, including document title, page range, and chunk number. -- Avoid sidecar metadata by default; put only core metadata in concise Markdown frontmatter. - -## Determinism And Paths -- Ensure the same PDF and same options produce stable output structure and filenames. -- Use deterministic slug, anchor, asset, and chunk naming rules. -- Prefer `pathlib` for filesystem paths. -- Test Korean filenames, paths with spaces, and long Windows paths. - -## Runtime And Recovery -- Use conservative batch sizes, usually 1 or 2, for GTX 1070 Ti 8 GB VRAM. -- If a GPU out-of-memory error occurs, retry with a smaller batch or smaller page unit where possible. -- If the user explicitly requests `--device cuda` or `--runtime cuda`, fail fast instead of silently switching to CPU. -- If the user requests `--runtime auto`, warn and fall back to CPU when CUDA initialization fails. -- Keep model cache locations explicit, preferably under a local project or user-configured model cache directory, so offline operation can reuse already-downloaded weights. - -## Logging And Resume -- Show chunk-level progress and success/failure status in the CLI. -- Print warnings and errors to stderr and a local log file. -- Do not inject warnings or error logs into generated Markdown because they reduce document readability and integrity. -- Support resuming failed conversions by skipping already successful chunks when a local state/cache file is available. -- Sidecar outputs are still out of scope unless explicitly requested; a resume state file is a runtime cache, not part of the document output contract. - -## Quality Tests -- Prefer focused assertions over full Markdown snapshots. -- Validate heading structure, formula delimiter balance, LaTeX environment pairs, image links, caption matching, table parseability, and no-exception conversion. -- Use regex and Markdown/HTML parsers where practical instead of ad hoc string checks. -- Maintain a sample metadata mapping file for `samples/` that tags each PDF by traits such as text-layer quality, scanned pages, multi-column layout, formula density, table density, figure density, and Korean filename coverage. -- Use engineering/mechanics PDFs with multi-column layout, formulas, graphs, and tables as the MVP acceptance corpus. - -## Licensing -- Current use is personal, which lowers immediate distribution risk. -- If redistribution or commercial use becomes relevant, revisit Marker GPL and model-weight license implications before packaging. -- Process or service isolation can be considered as a licensing risk-mitigation strategy, but it is not a legal conclusion and should be reviewed before distribution. - -## UI Boundary -- Keep the core conversion engine as a Python API/CLI package. -- Future PyQt UI should remain a thin client over the same API and must not duplicate conversion logic. - diff --git a/docs/HARNESS.md b/docs/HARNESS.md deleted file mode 100644 index 3eed81b..0000000 --- a/docs/HARNESS.md +++ /dev/null @@ -1,114 +0,0 @@ -# Harness Engineering Guide - -이 문서는 PDFtoMD 프로젝트에서 장기 agent 작업을 관리하는 Harness 운영 규칙입니다. 기준은 Anthropic의 "Harness design for long-running application development" 글에서 강조한 planner, generator, evaluator 분리, 파일 기반 handoff, sprint contract, 독립 평가 루프입니다. - -## Purpose -- 긴 변환 엔진 개발을 작은 self-contained step으로 나눕니다. -- 새 agent가 이전 대화 맥락 없이도 `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, `phases/` 파일만 읽고 일을 이어받게 합니다. -- 구현 agent와 평가 agent를 분리해 자기 평가 편향을 줄입니다. -- 각 step의 성공 조건을 코드 작성 전에 파일로 고정합니다. -- Harness 자체는 단순하게 유지하고, 복잡성은 필요한 검증 기준과 step 경계에만 둡니다. - -## Roles - -### Planner -- 제품 목표와 아키텍처 문서를 읽고 phase와 step을 작성합니다. -- 구현 세부를 과도하게 지정하지 않고 산출물, 책임 범위, 수락 기준, 금지 범위를 명확히 합니다. -- 산출물: - - `PLAN.md` 업데이트 - - `phases/index.json` - - `phases/{phase}/index.json` - - `phases/{phase}/stepN.md` - -### Generator -- 한 번에 하나의 `stepN.md`만 수행합니다. -- 작업 전 step의 "Sprint Contract"를 읽고, 애매하면 구현 전에 `PROGRESS.md`에 blocker로 남깁니다. -- TDD가 필요한 구현 step에서는 테스트를 먼저 작성합니다. -- 산출물: - - step 범위 내 코드, 테스트, 문서 변경 - - `phases/{phase}/index.json` step status 업데이트 - - `PROGRESS.md` handoff 업데이트 - -### Evaluator -- generator가 만든 결과를 독립적으로 검토합니다. -- 합의된 기준 중 하나라도 hard threshold를 넘지 못하면 step을 통과시키지 않습니다. -- 통과 여부만 보지 않고, 재작업 가능한 구체적 실패 원인을 남깁니다. -- 산출물: - - review finding 또는 pass 기록 - - 필요한 경우 `phases/{phase}/index.json`의 `error_message` 또는 `blocked_reason` - - `PROGRESS.md` 검증 결과 - -## File Protocol -- `AGENTS.md`: 변하지 않는 저장소 규칙. -- `PLAN.md`: 전체 작업 계획의 단일 출처. -- `PROGRESS.md`: 현재 진행 상태와 handoff의 단일 출처. -- `docs/*.md`: 제품, 아키텍처, 결정, 도구 체인, Harness 운영 지식. -- `phases/index.json`: 실행 가능한 phase registry. -- `phases/{phase}/index.json`: 해당 phase step 상태의 단일 출처. -- `phases/{phase}/stepN.md`: 새 agent가 독립 실행할 수 있는 ticket. - -## Step Contract Template -각 `stepN.md`는 다음 정보를 포함해야 합니다. - -````markdown -# Step N: step-name - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/CONVERSION_POLICY.md - -## Task -이 step에서 만들어야 하는 산출물과 수정 가능한 파일을 구체적으로 적습니다. - -## Sprint Contract -- Done means: 사용자가 관찰할 수 있거나 테스트로 확인 가능한 완료 조건. -- Hard thresholds: 하나라도 실패하면 step 실패로 보는 기준. -- Files owned: 이 step에서 수정할 수 있는 파일 또는 디렉터리. -- Dependencies: 이전 step 산출물 또는 필요한 문서. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. 테스트와 검증 명령을 실행합니다. -2. `PROGRESS.md`에 결과와 다음 handoff를 기록합니다. -3. `phases/{phase}/index.json`의 해당 step을 `completed`, `blocked`, `error` 중 하나로 갱신합니다. - -## Do Not -- step 범위 밖 기능을 구현하지 않습니다. -- 새 parser나 외부 API를 도입하지 않습니다. -- 생성 Markdown 출력 contract를 임의로 넓히지 않습니다. -```` - -## Evaluation Criteria -PDFtoMD의 evaluator는 다음 hard threshold를 우선 적용합니다. - -| Area | Hard Threshold | -| --- | --- | -| Architecture | Marker, Nougat, PyMuPDF 책임 경계를 깨지 않는다. | -| TDD | 구현 step은 실패하는 테스트가 먼저 추가되거나, 테스트가 필요 없는 이유가 step에 명시된다. | -| Determinism | 같은 입력과 옵션은 같은 slug, asset path, anchor, Markdown 구조를 만든다. | -| Markdown quality | heading, math delimiter, table, image link, caption, chunk frontmatter 검증이 가능하다. | -| Runtime | Windows path, Korean filename, CUDA/CPU runtime 정책을 훼손하지 않는다. | -| Scope | PyQt UI, hosted API, LLM correction, sidecar output을 1차 구현에 끌어오지 않는다. | -| Handoff | `PROGRESS.md`와 phase index가 다음 agent에게 충분한 상태를 제공한다. | - -## When To Use The Full Loop -- Full planner/generator/evaluator loop를 사용합니다: - - 새 phase를 시작할 때 - - parser adapter, chunk planner, renderer, quality validator처럼 실패 비용이 큰 작업 - - sample corpus나 runtime 정책처럼 여러 파일과 문서가 동시에 바뀌는 작업 -- 단순한 문서 오타, 작은 command 설명, 명확한 단일 테스트 수정은 일반 Codex 작업으로 처리해도 됩니다. 그래도 `PROGRESS.md`는 갱신합니다. - -## Simplification Rule -Harness 구성 요소는 실제로 품질을 높일 때만 유지합니다. -- 같은 검증을 두 곳에서 반복하면 하나로 줄입니다. -- hook은 보조 장치로 취급하고, step의 acceptance criteria와 evaluator 판단을 대체하지 않습니다. -- agent에게 너무 많은 컨텍스트를 주지 말고, step에 필요한 문서와 파일만 지정합니다. diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md deleted file mode 100644 index 82d4cd2..0000000 --- a/docs/IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,121 +0,0 @@ -# Implementation Phase Plan - -이 문서는 PDFtoMD 구현 전체를 phase 단위로 나눈 실행 계획입니다. 각 phase의 상세 실행 티켓은 `phases/{phase}/stepN.md`에 둡니다. - -## Planning Principles -- 1차 목표는 Windows native, local-first CLI/library 변환 엔진입니다. -- PyQt UI는 core API와 CLI가 안정화된 뒤 thin client로 구현합니다. -- 각 phase는 이전 phase의 산출물을 전제로 하며, phase 안의 step은 하나의 agent가 독립 실행할 수 있어야 합니다. -- 구현 phase는 TDD를 기본으로 합니다. -- Parser 책임 경계는 유지합니다: Marker는 문서 구조, Nougat은 수식, PyMuPDF는 사전 분석과 저수준 PDF 작업입니다. - -## Phase Overview - -| Phase | Goal | Primary Output | Depends On | -| --- | --- | --- | --- | -| 0. Harness foundation | 실행 가능한 Harness 기반과 최소 품질 토대 | sample metadata, core models, preanalysis contract, quality gates | current docs | -| 1. Core runtime contracts | 변환 옵션, 입력 정규화, 출력 bundle 계약, path/cache 정책 | stable API contracts and tests | Phase 0 | -| 2. Marker adapter | Marker 실행과 block normalization 경계 구현 | Marker adapter, OCR handoff, block mapping tests | Phase 1 | -| 3. Formula pipeline | Nougat formula-only handoff와 LaTeX 검증/fallback | formula detector, Nougat adapter, repair/fallback tests | Phase 2 | -| 4. Semantic enrichment | 문단, reading order, header/footer, 참조 관계 보강 | enrichment pipeline and reference index | Phase 2, 3 | -| 5. Markdown rendering and assets | Markdown chunk, table, figure, asset writer 구현 | deterministic Markdown bundle writer | Phase 4 | -| 6. CLI runtime and resume | CLI, progress/logging, runtime, OOM, resume 구현 | user-facing local CLI | Phase 5 | -| 7. MVP quality hardening | samples 기반 end-to-end 품질 검증과 회귀 안정화 | MVP acceptance suite | Phase 6 | -| 8. Release docs and packaging | 설치, 모델 cache, offline, release 문서 정리 | local release-ready docs/scripts | Phase 7 | -| 9. PyQt thin client | CLI/library를 호출하는 Windows UI | optional PyQt UI | Phase 8 | - -## Phase 0: Harness Foundation -- Directory: `phases/0-harness-foundation` -- Purpose: 구현 전 공통 모델, sample metadata, PyMuPDF pre-analysis contract, Markdown quality gates를 만든다. -- Steps: - 1. `sample-metadata-contract` - 2. `core-package-skeleton` - 3. `page-preanalysis-contract` - 4. `markdown-quality-gates` - -## Phase 1: Core Runtime Contracts -- Directory: `phases/1-core-runtime-contracts` -- Purpose: parser 실행 전에 모든 phase가 공유할 입력, 옵션, path, output contract를 안정화한다. -- Steps: - 1. `input-normalization-slug` - 2. `conversion-options-config` - 3. `output-bundle-contract` - 4. `runtime-cache-policy` - -## Phase 2: Marker Adapter -- Directory: `phases/2-marker-adapter` -- Purpose: Marker를 primary parser로 연결하고, OCR/page plan과 Marker 구조화 출력을 내부 block model로 매핑한다. -- Steps: - 1. `marker-invocation-adapter` - 2. `ocr-plan-handoff` - 3. `marker-block-normalization` - 4. `marker-failure-reporting` - -## Phase 3: Formula Pipeline -- Directory: `phases/3-formula-pipeline` -- Purpose: Nougat을 formula-only parser로 연결하고, 수식 delimiter, numbering, fallback을 안정화한다. -- Steps: - 1. `formula-block-detection` - 2. `nougat-command-adapter` - 3. `latex-validation-repair` - 4. `formula-reference-links` - -## Phase 4: Semantic Enrichment -- Directory: `phases/4-semantic-enrichment` -- Purpose: Marker block을 Markdown에 적합한 논리 구조로 보강한다. -- Steps: - 1. `reading-order-checks` - 2. `paragraph-stitching` - 3. `header-footer-filtering` - 4. `reference-indexing` - -## Phase 5: Markdown Rendering And Assets -- Directory: `phases/5-markdown-rendering-assets` -- Purpose: chunked Markdown bundle과 image/table asset 출력을 결정적으로 생성한다. -- Steps: - 1. `markdown-block-renderer` - 2. `table-renderer-fallbacks` - 3. `figure-asset-writer` - 4. `chunk-renderer` - -## Phase 6: CLI Runtime And Resume -- Directory: `phases/6-cli-runtime-resume` -- Purpose: 변환 엔진을 사용자가 실행할 수 있는 CLI로 묶고 runtime/recovery 정책을 구현한다. -- Steps: - 1. `cli-entrypoint-options` - 2. `progress-logging` - 3. `resume-state` - 4. `device-oom-policy` - 5. `model-cache-offline` - -## Phase 7: MVP Quality Hardening -- Directory: `phases/7-mvp-quality-hardening` -- Purpose: sample corpus 기준으로 end-to-end 품질을 고정하고 MVP 수락 기준을 통과시킨다. -- Steps: - 1. `sample-smoke-conversions` - 2. `quality-metrics-report` - 3. `regression-thresholds` - 4. `mvp-fix-sweep` - -## Phase 8: Release Docs And Packaging -- Directory: `phases/8-release-docs-packaging` -- Purpose: 개인용 로컬 실행 기준으로 설치, 모델 다운로드, offline 실행, release checklist를 정리한다. -- Steps: - 1. `readme-usage-flow` - 2. `environment-bootstrap-docs` - 3. `license-checkpoint` - 4. `release-checklist` - -## Phase 9: PyQt Thin Client -- Directory: `phases/9-pyqt-thin-client` -- Purpose: core engine을 중복 구현하지 않는 Windows UI를 만든다. -- Steps: - 1. `ui-api-contract` - 2. `pyqt-shell` - 3. `ui-progress-resume` - 4. `ui-packaging-notes` - -## Deferred Backlog -- Hosted conversion API는 현재 phase plan에 포함하지 않습니다. -- LLM correction mode는 기본 경로가 아니며, MVP 이후 별도 ADR과 phase 계획이 필요합니다. -- 배포/상업적 사용이 현실화되면 Marker GPL과 model weight license를 별도 법적 검토 대상으로 둡니다. diff --git a/docs/PRD.md b/docs/PRD.md deleted file mode 100644 index bb73c62..0000000 --- a/docs/PRD.md +++ /dev/null @@ -1,88 +0,0 @@ -# PRD: PDFtoMD - -## 목표 -PDFtoMD는 수학, 공학, 역학 중심의 PDF 문서를 AI Agent가 쉽게 접근하고 읽을 수 있는 Markdown 문서 묶음으로 변환하는 프로그램입니다. - -이 프로젝트의 목표는 PDF의 텍스트를 단순 추출하는 것이 아니라, 원문 문서의 논리 구조를 보존하면서 AI가 읽기 쉬운 지식 자료로 재구성하는 것입니다. - -## 문제 정의 -- PDF는 텍스트, 이미지, 수식, 표, 캡션을 좌표 기반으로 저장하므로 원문 읽기 순서가 쉽게 깨집니다. -- 논문과 공학 문서에는 다단 레이아웃, 수식 번호, 그림/표 참조, 복잡한 표가 자주 등장합니다. -- 스캔 PDF와 텍스트 레이어 PDF가 섞인 문서는 OCR 여부를 문서 전체 단위가 아니라 페이지 단위로 판단해야 합니다. -- AI Agent와 RAG 도구는 긴 PDF 하나보다 논리적으로 나뉜 Markdown chunk와 연결된 asset을 더 안정적으로 탐색합니다. - -## 사용자 -- PDF 문서를 Markdown으로 변환해 AI Agent, RAG, 개인 지식 관리 도구에 활용하고 싶은 사용자 -- 수식, 표, 이미지가 많은 논문/공학 문서를 Markdown으로 읽고 관리하고 싶은 사용자 -- 긴 PDF를 여러 Markdown 파일로 나누어 부분 탐색하고 싶은 사용자 -- Windows native 환경에서 외부 서비스 없이 로컬로 변환하고 싶은 사용자 - -## 1차 MVP 범위 -- Windows native 환경에서 완전 로컬 실행 -- GPU 기본 사용, VRAM 8GB 환경을 기준으로 안정적인 chunk 처리 -- repo-local Python 3.11 단일 `venv` 환경 사용 -- PDF parser는 `Marker`를 기본 엔진으로 사용 -- 본문 구조, OCR/layout, reading order, 표, 그림, heading, caption은 Marker 경로를 유지 -- 수학적 표현이나 수식은 `Nougat` parser를 사용 -- PyMuPDF로 페이지 수, 텍스트 레이어 품질, OCR 필요 여부, chunk 계획을 사전 분석 -- PDF 텍스트를 Markdown 문단과 heading 구조로 변환 -- PDF 내 수식을 Markdown math delimiter를 사용하는 LaTeX로 변환 -- Nougat 실패 시 Marker 원문 수식 문자열을 fallback으로 보존 -- PDF 내 이미지를 추출하고 Markdown에서 연결 -- 이미지의 figure 번호와 캡션을 가능한 한 보존 -- PDF 내 표를 구조화하고 Markdown table로 출력 -- Markdown table 손실이 큰 표는 제한적 HTML table 또는 표 영역 이미지 fallback으로 보존 -- 페이지 수가 많은 문서를 20페이지 목표 chunk로 분할하되 논리 block 경계 보존 -- CLI 진행률, chunk 단위 성공/실패 요약, stderr/local log 기록 -- 실패 chunk 재개를 위한 runtime cache/state 기반 resume 옵션 -- `samples/` PDF 기반 품질 검증과 회귀 테스트 지원 - -## 2차 범위 -- PyQt 기반 Windows UI -- UI는 CLI/라이브러리 계층을 호출하는 thin client로 구현 -- 선택적 외부 API 연동은 변환 엔진 안정화 이후 검토 - -## 제외 범위 -- hosted conversion API 기본 경로화 -- LLM 보정 모드 기본 경로화 -- 생성 문서와 함께 배포되는 별도 sidecar metadata/log 산출물 -- 변환 엔진 로직을 PyQt UI 안에 중복 구현하는 방식 - -## 핵심 기능 -1. PDF 문서를 Markdown 문서 묶음으로 변환 -2. 텍스트 PDF, 스캔 PDF, 혼합 PDF를 페이지별 OCR 판단으로 처리 -3. 수식을 `$ ... $` 또는 `$$ ... $$` 형식의 LaTeX로 보존 -4. 수식 번호와 본문 내 수식 참조를 가능한 한 내부 링크로 연결 -5. 논문에서 자주 쓰이는 다중 컬럼 문서를 Markdown의 선형 구조로 재배치 -6. 이미지 추출 및 Markdown 연결 -7. figure 번호, caption, 본문 내 figure 참조 연결 -8. 표 구조화 및 표 유형별 Markdown/HTML/fallback 이미지 출력 -9. 긴 PDF를 여러 chunk Markdown 파일로 분할 변환 -10. 한글 파일명, 긴 Windows 경로, 공백 포함 경로 지원 -11. GTX 1070 Ti 8GB VRAM 기준 batch 크기 제어와 OOM 재시도 -12. offline 실행을 위한 명시적 model cache 정책 - -## 품질 기준 -- 원문 읽기 순서가 Markdown에서 자연스럽게 유지되어야 합니다. -- heading, 본문, 리스트, 인용, 표, 그림, 캡션, 수식의 의미 역할이 구분되어야 합니다. -- 수식 delimiter와 기본 LaTeX 구조가 깨지지 않아야 합니다. -- 수식 번호와 본문 참조가 가능한 한 연결되어야 합니다. -- 이미지와 캡션, figure 번호, 본문 참조가 가능한 한 연결되어야 합니다. -- 표는 구조 손실을 최소화하는 형식으로 저장되어야 합니다. -- chunk 경계가 문단, 표, 그림, 수식을 중간에서 깨뜨리지 않아야 합니다. -- 같은 입력 PDF와 같은 옵션은 같은 파일명, anchor, asset 구조를 생성해야 합니다. -- Windows 경로, 한글 파일명, 긴 문서, GPU 메모리 부족 상황을 고려해야 합니다. -- 오류와 경고는 Markdown 본문을 오염시키지 않고 stderr/local log에 남겨야 합니다. - -## Acceptance Criteria -- `python scripts/validate_workspace.py`가 성공해야 합니다. -- `.\venv\python.exe -m pip check`가 성공해야 합니다. -- CUDA smoke test가 GTX 1070 Ti에서 성공해야 합니다. -- `.\venv\Scripts\nougat.exe --help`가 성공해야 합니다. -- sample metadata mapping 파일이 각 sample PDF의 특성을 설명해야 합니다. -- focused pytest가 heading, 수식 delimiter, LaTeX environment pair, image link, caption matching, table parseability, chunk boundary, no-exception conversion을 검증해야 합니다. - -## UI -- UI는 2차 목표로 PyQt를 사용합니다. -- UI는 변환 엔진을 직접 구현하지 않고 CLI/라이브러리 계층을 호출하는 thin client로 둡니다. -- 미니멀하고 깔끔한 Windows 표준 디자인을 따릅니다. diff --git a/docs/TOOLCHAIN.md b/docs/TOOLCHAIN.md deleted file mode 100644 index 3909277..0000000 --- a/docs/TOOLCHAIN.md +++ /dev/null @@ -1,90 +0,0 @@ -# Toolchain Notes - -This document summarizes the researched toolchain choices and local compatibility decisions. - -## Verified Environment -- OS: Windows 10 -- GPU: NVIDIA GeForce GTX 1070 Ti -- VRAM: 8 GB -- NVIDIA driver: 577.00 -- `nvidia-smi` CUDA runtime capability: 12.9 -- User-installed CUDA toolkit: 12.4 -- Python: 3.11.15 in repo-local `venv` -- Environment manager: Conda / Miniforge - -## Python Dependencies -Use one repo-local `venv` and install from `requirements.txt`. - -Key pins: -- `torch==2.7.1+cu126` -- `torchvision==0.22.1+cu126` -- `marker-pdf==1.10.2` -- `nougat-ocr==0.1.17` -- `transformers==4.57.6` -- `albumentations==1.3.1` -- `pymupdf==1.27.2.3` -- `pandas==3.0.2` -- `pytest==9.0.3` -- `pypdfium2==4.30.0` -- `opencv-python-headless==4.11.0.86` -- `Pillow==10.4.0` -- `fsspec==2026.2.0` - -## PyTorch / CUDA Decision -- `torch==2.11.0+cu128` imports on this machine but does not support GTX 1070 Ti `sm_61` at runtime. -- `torch==2.7.1+cu126` satisfies Marker `torch>=2.7.0` and successfully runs CUDA tensor operations on GTX 1070 Ti. -- Keep this pin unless a newer official PyTorch wheel is verified to support `sm_61`. - -## Marker -- Marker is the primary document parser. -- It handles layout, OCR/layout, reading order, body text, headings, tables, figures, captions, and semantic block roles. -- It should be consumed through structured output or adapter APIs where possible, not by scraping final Markdown text. - -## Nougat -- Nougat is used only for formulas and mathematical expressions. -- `nougat-ocr==0.1.17` has loose dependency bounds, so the project pins compatible versions. -- `transformers 5.x` breaks Nougat imports. -- `albumentations 2.x` breaks Nougat transform initialization. -- Nougat failure must fall back to Marker source text. - -## PyMuPDF -- PyMuPDF is used for lightweight page analysis, page counts, text-layer quality checks, OCR intervention planning, chunk planning, and low-level PDF/page operations. -- It is not the primary document parser. - -## Comparison Baselines -These tools are useful for research or quality comparison but are not the primary architecture: -- PyMuPDF4LLM -- Docling -- MinerU -- MarkItDown - -Do not switch the primary parser without updating `docs/ADR.md`, `docs/ARCHITECTURE.md`, and `docs/CONVERSION_POLICY.md`. - -## Reference Links -- Marker PyPI: https://pypi.org/project/marker-pdf/ -- Nougat GitHub: https://github.com/facebookresearch/nougat -- PyMuPDF documentation: https://pymupdf.readthedocs.io/ -- PyTorch previous versions: https://docs.pytorch.org/get-started/previous-versions/ -- GitHub Flavored Markdown spec: https://github.github.io/gfm/ -- MathJax TeX delimiters: https://docs.mathjax.org/en/latest/input/tex/delimiters.html -- Docling GitHub: https://github.com/docling-project/docling -- MinerU GitHub: https://github.com/opendatalab/MinerU - -## Markdown And Math Rendering -- Markdown table output should target GitHub Flavored Markdown where possible. -- Complex tables may use limited HTML `
`. -- Math output uses `$ ... $` for inline formulas and `$$ ... $$` for block formulas. -- `$...$` can conflict with ordinary dollar signs, so delimiter validation and repair are required. - -## Model Cache -- Use explicit local cache paths for Marker/Nougat/Hugging Face model downloads. -- README should include model pre-download and offline execution instructions before the engine is released. -- Default project-local model cache path is `.models/`. -- `PDFTOMD_MODEL_CACHE` can override the default cache root. -- The runtime cache policy exposes Hugging Face cache environment variables from that root without downloading models during validation. -- Runtime logs and resume state are runtime artifacts under `output/.pdftomd-runtime//`, not generated document sidecars. - -## Licensing Notes -- Current user context is personal use. -- Before redistribution or commercial use, revisit Marker GPL and model-weight license implications. -- Process or API isolation can reduce coupling risk, but it is not a substitute for legal review. diff --git a/docs/UI_GUIDE.md b/docs/UI_GUIDE.md deleted file mode 100644 index 64f0b64..0000000 --- a/docs/UI_GUIDE.md +++ /dev/null @@ -1,39 +0,0 @@ -# UI 디자인 가이드 - -UI는 2차 목표입니다. 1차 MVP에서는 CLI/라이브러리 변환 엔진을 먼저 안정화합니다. - -## 디자인 원칙 -1. 표준 Windows 환경에 맞는 미니멀한 UI를 따른다. -2. 변환 엔진 로직을 UI에 중복 구현하지 않는다. -3. PyQt UI는 core Python API 또는 CLI를 호출하는 thin client로 둔다. -4. 긴 문서 변환 중 사용자가 현재 상태를 파악할 수 있어야 한다. -5. 오류와 경고는 읽기 쉬운 방식으로 보여주되, 생성 Markdown을 오염시키지 않는다. - -## 주요 화면 -- PDF 선택 -- 출력 폴더 선택 -- runtime 선택: `cuda`, `auto`, `cpu` -- formula parser 선택: `nougat`, `marker` -- chunk size 표시 및 기본값 유지 -- 진행률과 chunk별 상태 표시 -- 실패 chunk 요약 -- resume 실행 버튼 -- local log 열기 - -## Interaction Rules -- 기본값은 CLI 기본값과 동일해야 한다. -- `cuda` 명시 실행에서 CUDA 초기화 실패 시 CPU fallback을 자동으로 하지 않고 명확히 실패를 표시한다. -- `auto` 실행에서 CUDA 실패 시 경고 후 CPU fallback 상태를 표시한다. -- 변환 중 취소가 가능해야 한다. -- 성공한 chunk와 실패한 chunk가 구분되어야 한다. - -## Visual Style -- Windows native에 어울리는 절제된 색상과 간격을 사용한다. -- 작업 도구 UI이므로 marketing hero나 장식적 layout은 사용하지 않는다. -- 긴 파일명과 한글 경로가 잘리지 않도록 middle ellipsis 또는 tooltip을 제공한다. -- 로그와 결과 경로는 복사 가능한 텍스트로 제공한다. - -## Boundary -- UI는 `src/` core package의 public API 또는 CLI만 호출한다. -- UI에서 Marker/Nougat/PyMuPDF를 직접 조합하지 않는다. -- UI 테스트는 core conversion quality test와 분리한다. diff --git a/phases/0-harness-foundation/index.json b/phases/0-harness-foundation/index.json deleted file mode 100644 index 96651a1..0000000 --- a/phases/0-harness-foundation/index.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "0-harness-foundation", - "steps": [ - { - "step": 0, - "name": "sample-metadata-contract", - "status": "completed", - "summary": "Created deterministic samples/metadata.json and metadata contract tests for current sample PDFs." - }, - { - "step": 1, - "name": "core-package-skeleton", - "status": "completed", - "summary": "Created importable pdftomd package skeleton, pyproject metadata, and typed core models." - }, - { - "step": 2, - "name": "page-preanalysis-contract", - "status": "completed", - "summary": "Added PyMuPDF-only PDF preanalysis with page facts, OCR candidates, and 20-page chunk ranges." - }, - { - "step": 3, - "name": "markdown-quality-gates", - "status": "completed", - "summary": "Added focused Markdown quality gates for math, LaTeX, tables, image links, frontmatter, and anchors." - } - ] -} diff --git a/phases/0-harness-foundation/step0.md b/phases/0-harness-foundation/step0.md deleted file mode 100644 index fcd8e35..0000000 --- a/phases/0-harness-foundation/step0.md +++ /dev/null @@ -1,63 +0,0 @@ -# Step 0: sample-metadata-contract - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /docs/ADR.md -- /docs/TOOLCHAIN.md - -## Task -Create the first sample corpus metadata contract without implementing the conversion engine. - -The metadata must classify every PDF currently under `samples/` by traits that future regression tests can use: -- text layer quality -- scanned or mixed scanned/text pages -- multi-column or complex layout risk -- formula density -- table density -- figure density -- Korean filename/path coverage -- target regression focus - -Use deterministic JSON so future agents can update it with minimal diff noise. - -## Sprint Contract -- Done means: `samples/metadata.json` exists, includes every current PDF by exact relative path, and has enough structured fields for future tests to select OCR, layout, formula, table, figure, and Korean-path cases. -- Hard thresholds: - - Every current `samples/*.pdf` appears exactly once. - - Metadata is valid UTF-8 JSON. - - Tests fail if a sample PDF is added without metadata. - - Tests fail if duplicate sample paths exist in the metadata. - - No conversion engine code is introduced in this step. -- Files owned: - - `samples/metadata.json` - - `tests/test_sample_metadata.py` - - `PROGRESS.md` - - `phases/0-harness-foundation/index.json` -- Dependencies: - - Existing sample PDFs under `samples/` - - PyMuPDF may be used only for lightweight page count/text/image inspection if needed. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests\test_sample_metadata.py -``` - -## Verification -1. Run the acceptance commands. -2. Confirm `samples/metadata.json` paths match `samples/*.pdf`. -3. Confirm Korean filenames remain readable in JSON. -4. Update `PROGRESS.md` with completed work, validation output, and next handoff. -5. Update this phase index step to `completed` with a one-line `summary`, or to `blocked`/`error` with a concrete reason. - -## Do Not -- Do not create `src/` or conversion engine modules in this step. -- Do not rename, delete, compress, or rewrite sample PDFs. -- Do not add sidecar output files for converted documents. -- Do not add a new custom agent. diff --git a/phases/0-harness-foundation/step1.md b/phases/0-harness-foundation/step1.md deleted file mode 100644 index 74b4c67..0000000 --- a/phases/0-harness-foundation/step1.md +++ /dev/null @@ -1,59 +0,0 @@ -# Step 1: core-package-skeleton - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /docs/ADR.md -- /phases/0-harness-foundation/step0.md -- /phases/0-harness-foundation/index.json - -## Task -Create the minimal Python package skeleton and internal data contracts needed by later parser, pre-analysis, and renderer steps. - -The skeleton should establish importable modules and typed models only. It should not call Marker, Nougat, PyMuPDF, OCR, CUDA, or the filesystem-heavy conversion path yet. - -Suggested module boundary: -- `src/pdftomd/__init__.py` -- `src/pdftomd/models.py` -- `tests/test_models.py` - -The exact type names may differ if the local design suggests better names, but the contracts must represent document identity, page ranges, block roles, bounding boxes, assets, formulas, tables, figures, and chunk metadata. - -## Sprint Contract -- Done means: future steps have stable importable types for page analysis, block modeling, chunk metadata, and output assets. -- Hard thresholds: - - Tests cover model construction, deterministic slug/path-relevant fields, and page range invariants. - - Models do not depend on Marker, Nougat, PyMuPDF, torch, pandas, or PyQt. - - The package imports on Windows with `.\venv\python.exe`. - - Public contracts are documented by tests or clear docstrings. -- Files owned: - - `src/pdftomd/` - - `tests/test_models.py` - - `PROGRESS.md` - - `phases/0-harness-foundation/index.json` -- Dependencies: - - Step 0 metadata should be complete or explicitly blocked. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests\test_models.py -``` - -## Verification -1. Run the acceptance commands. -2. Confirm package imports with `.\venv\python.exe -c "import pdftomd; print(pdftomd.__name__)"`. -3. Confirm no heavy parser/model imports are introduced. -4. Update `PROGRESS.md` with completed work, validation output, and next handoff. -5. Update this phase index step to `completed` with a one-line `summary`, or to `blocked`/`error` with a concrete reason. - -## Do Not -- Do not implement actual PDF parsing. -- Do not run Marker or Nougat. -- Do not add CLI commands. -- Do not add PyQt UI code. -- Do not widen the output contract beyond `docs/ARCHITECTURE.md`. diff --git a/phases/0-harness-foundation/step2.md b/phases/0-harness-foundation/step2.md deleted file mode 100644 index c66496f..0000000 --- a/phases/0-harness-foundation/step2.md +++ /dev/null @@ -1,63 +0,0 @@ -# Step 2: page-preanalysis-contract - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /docs/ADR.md -- /docs/TOOLCHAIN.md -- /phases/0-harness-foundation/step0.md -- /phases/0-harness-foundation/step1.md -- /phases/0-harness-foundation/index.json - -## Task -Implement the lightweight page pre-analysis contract that decides what later conversion steps need to know before Marker runs. - -This step should use PyMuPDF only for fast document/page inspection: -- page count -- text length or text density per page -- image count per page -- OCR candidate flag per page -- basic long-document chunk candidates using the 20-page target - -The output should be typed using the models from Step 1. - -## Sprint Contract -- Done means: given a PDF path, the pre-analysis API returns deterministic page-level facts and chunk candidates without running Marker, Nougat, OCR, or GPU code. -- Hard thresholds: - - Tests cover at least one text-heavy sample and one mixed/scanned-risk sample from `samples/metadata.json`. - - Tests cover Korean path handling through `pathlib`. - - OCR candidate logic is deterministic and documented by tests. - - Chunk candidates never exceed the document page count. - - Explicit conversion or Markdown rendering is not implemented here. -- Files owned: - - `src/pdftomd/preanalysis.py` - - model additions in `src/pdftomd/models.py` only if required - - `tests/test_preanalysis.py` - - `PROGRESS.md` - - `phases/0-harness-foundation/index.json` -- Dependencies: - - Step 0 sample metadata - - Step 1 package skeleton and models - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests\test_preanalysis.py -``` - -## Verification -1. Run the acceptance commands. -2. Confirm PyMuPDF is the only PDF inspection dependency used in this step. -3. Confirm the sample metadata traits and test expectations are consistent. -4. Update `PROGRESS.md` with completed work, validation output, and next handoff. -5. Update this phase index step to `completed` with a one-line `summary`, or to `blocked`/`error` with a concrete reason. - -## Do Not -- Do not call Marker, Nougat, Surya, torch, or OCR. -- Do not write conversion output under `output/`. -- Do not create resume cache or runtime state files. -- Do not implement reading-order reconstruction in this step. diff --git a/phases/0-harness-foundation/step3.md b/phases/0-harness-foundation/step3.md deleted file mode 100644 index 23433cf..0000000 --- a/phases/0-harness-foundation/step3.md +++ /dev/null @@ -1,63 +0,0 @@ -# Step 3: markdown-quality-gates - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /docs/ADR.md -- /phases/0-harness-foundation/step0.md -- /phases/0-harness-foundation/step1.md -- /phases/0-harness-foundation/step2.md -- /phases/0-harness-foundation/index.json - -## Task -Create focused Markdown quality gate functions that later renderer and conversion steps can call. - -This step should validate generated Markdown-like strings and asset references without requiring a full PDF conversion. It should prefer structured checks over full snapshot comparison. - -Quality gates should cover: -- math delimiter balance for `$...$` and `$$...$$` -- LaTeX `\begin{...}` / `\end{...}` pairs -- image link path existence or modeled asset reference existence -- table parseability for simple Markdown tables -- chunk frontmatter fields required by the output contract -- caption/reference anchor shape where confidence is sufficient - -## Sprint Contract -- Done means: later renderer steps have reusable validation functions and focused pytest coverage for Markdown output risks. -- Hard thresholds: - - Tests include passing and failing examples for math delimiter checks. - - Tests include a complex table case where Markdown limitations are represented as an allowed HTML/fallback decision. - - Tests do not rely on full Markdown snapshot equality. - - Validation functions do not mutate generated Markdown silently unless an explicit repair function is named and tested. - - No PDF parsing or renderer implementation is introduced here. -- Files owned: - - `src/pdftomd/quality.py` - - model additions in `src/pdftomd/models.py` only if required - - `tests/test_quality.py` - - `PROGRESS.md` - - `phases/0-harness-foundation/index.json` -- Dependencies: - - Step 1 package skeleton and models - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests\test_quality.py -``` - -## Verification -1. Run the acceptance commands. -2. Confirm quality gates are focused assertions, not whole-document snapshots. -3. Confirm failures return actionable messages for evaluator use. -4. Update `PROGRESS.md` with completed work, validation output, and next handoff. -5. Update this phase index step to `completed` with a one-line `summary`, or to `blocked`/`error` with a concrete reason. - -## Do Not -- Do not implement Marker/Nougat adapters. -- Do not implement the full Markdown renderer. -- Do not introduce an LLM correction path. -- Do not write warning/error messages into generated Markdown content. diff --git a/phases/1-core-runtime-contracts/index.json b/phases/1-core-runtime-contracts/index.json deleted file mode 100644 index 5b4aaf8..0000000 --- a/phases/1-core-runtime-contracts/index.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "1-core-runtime-contracts", - "steps": [ - { - "step": 0, - "name": "input-normalization-slug", - "status": "completed", - "summary": "Added deterministic PDF path normalization, document identity creation, anchors, and output bundle path contracts." - }, - { - "step": 1, - "name": "conversion-options-config", - "status": "completed", - "summary": "Added typed conversion options with runtime mode and formula parser defaults matching project policy." - }, - { - "step": 2, - "name": "output-bundle-contract", - "status": "completed", - "summary": "Added deterministic output bundle paths and separated runtime artifact paths from document output." - }, - { - "step": 3, - "name": "runtime-cache-policy", - "status": "completed", - "summary": "Added model cache and runtime artifact path policies with explicit offline environment mappings." - } - ] -} diff --git a/phases/1-core-runtime-contracts/step0.md b/phases/1-core-runtime-contracts/step0.md deleted file mode 100644 index 8bf9443..0000000 --- a/phases/1-core-runtime-contracts/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: input-normalization-slug - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/0-harness-foundation/index.json - -## Task -Implement deterministic input normalization and document slug generation for local PDF paths. - -Cover `pathlib` handling for Korean filenames, spaces, relative paths, absolute paths, and long Windows paths. The API should not invoke Marker, Nougat, PyMuPDF, or any conversion logic. - -## Sprint Contract -- Done means: the core package has a tested function or small module that normalizes input PDF paths and produces stable document slugs. -- Hard thresholds: same input path and options produce the same slug; non-PDF paths fail clearly; Korean and spaced paths are tested; no parser import is introduced. -- Files owned: `src/pdftomd/`, `tests/`, `PROGRESS.md`, `phases/1-core-runtime-contracts/index.json`. -- Dependencies: Phase 0 package skeleton and model contracts. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm `PROGRESS.md` records the handoff and validation result. -3. Update this phase index step to `completed`, `blocked`, or `error`. - -## Do Not -- Do not implement PDF parsing. -- Do not write conversion output. -- Do not add UI code. diff --git a/phases/1-core-runtime-contracts/step1.md b/phases/1-core-runtime-contracts/step1.md deleted file mode 100644 index 5ac93e8..0000000 --- a/phases/1-core-runtime-contracts/step1.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 1: conversion-options-config - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /phases/1-core-runtime-contracts/step0.md - -## Task -Define the typed conversion options and runtime configuration used by CLI, library, parser adapters, renderer, and UI. - -Include runtime mode, device behavior, chunk target pages, formula parser mode, Nougat command path, output directory, model cache location, and resume/log options. - -## Sprint Contract -- Done means: conversion options have defaults matching project policy and can be constructed by tests without CLI parsing. -- Hard thresholds: explicit `cuda` fail-fast semantics and `auto` fallback semantics are represented; Nougat remains formula-only; PyQt and hosted API options are not introduced. -- Files owned: `src/pdftomd/`, `tests/`, `PROGRESS.md`, `phases/1-core-runtime-contracts/index.json`. -- Dependencies: Step 0 normalized path/slug contract. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm defaults align with `docs/ARCHITECTURE.md` and `docs/CONVERSION_POLICY.md`. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not add command-line parsing yet. -- Do not initialize CUDA, Marker, or Nougat. -- Do not add external API settings. diff --git a/phases/1-core-runtime-contracts/step2.md b/phases/1-core-runtime-contracts/step2.md deleted file mode 100644 index 39c48c4..0000000 --- a/phases/1-core-runtime-contracts/step2.md +++ /dev/null @@ -1,39 +0,0 @@ -# Step 2: output-bundle-contract - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/1-core-runtime-contracts/step0.md -- /phases/1-core-runtime-contracts/step1.md - -## Task -Define deterministic output bundle path rules for chunk Markdown files, image assets, anchors, and runtime artifacts. - -This is a contract step. It may include lightweight path helpers and tests, but it should not render Markdown or write parsed document content. - -## Sprint Contract -- Done means: output directory, chunk file names, image asset names, and runtime log/state locations are modeled and tested. -- Hard thresholds: document output sidecars remain out of scope; runtime logs/state are separated from Markdown bundle output; asset naming is deterministic. -- Files owned: `src/pdftomd/`, `tests/`, `PROGRESS.md`, `phases/1-core-runtime-contracts/index.json`. -- Dependencies: Steps 0 and 1. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm generated path contracts match `docs/ARCHITECTURE.md`. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not implement the renderer. -- Do not write files under `output/` in tests unless using a temp directory. -- Do not create sidecar metadata output. diff --git a/phases/1-core-runtime-contracts/step3.md b/phases/1-core-runtime-contracts/step3.md deleted file mode 100644 index 3ed3602..0000000 --- a/phases/1-core-runtime-contracts/step3.md +++ /dev/null @@ -1,39 +0,0 @@ -# Step 3: runtime-cache-policy - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/TOOLCHAIN.md -- /docs/CONVERSION_POLICY.md -- /phases/1-core-runtime-contracts/step1.md -- /phases/1-core-runtime-contracts/step2.md - -## Task -Establish model cache, log path, and resume state policy as typed contracts and documented path helpers. - -The result should prepare later CLI/runtime phases to use local model cache paths and offline-preferred model loading. - -## Sprint Contract -- Done means: model cache and runtime cache path contracts are tested and documented without downloading models. -- Hard thresholds: no network download is triggered; logs/state remain outside generated Markdown content; environment variable overrides are deterministic. -- Files owned: `src/pdftomd/`, `tests/`, `docs/TOOLCHAIN.md`, `PROGRESS.md`, `phases/1-core-runtime-contracts/index.json`. -- Dependencies: Steps 1 and 2. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm `docs/TOOLCHAIN.md` stays consistent with any cache path decisions. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not download Marker or Nougat weights. -- Do not add hosted storage or cloud cache behavior. -- Do not write warnings into Markdown output. diff --git a/phases/2-marker-adapter/index.json b/phases/2-marker-adapter/index.json deleted file mode 100644 index 5e9530a..0000000 --- a/phases/2-marker-adapter/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "2-marker-adapter", - "steps": [ - { - "step": 0, - "name": "marker-invocation-adapter", - "status": "pending" - }, - { - "step": 1, - "name": "ocr-plan-handoff", - "status": "pending" - }, - { - "step": 2, - "name": "marker-block-normalization", - "status": "pending" - }, - { - "step": 3, - "name": "marker-failure-reporting", - "status": "pending" - } - ] -} diff --git a/phases/2-marker-adapter/step0.md b/phases/2-marker-adapter/step0.md deleted file mode 100644 index 1c3816d..0000000 --- a/phases/2-marker-adapter/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: marker-invocation-adapter - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/TOOLCHAIN.md -- /phases/1-core-runtime-contracts/index.json - -## Task -Implement the first Marker adapter boundary that invokes Marker through a small internal interface. - -Keep this adapter isolated so tests can use fakes without loading large models. Real Marker invocation should be smoke-testable but not required for every unit test. - -## Sprint Contract -- Done means: Marker invocation is behind a narrow interface and can return structured parse results or clear failures. -- Hard thresholds: Marker remains the primary document parser; Nougat is not used here; unit tests avoid mandatory model downloads; parser errors are structured. -- Files owned: `src/pdftomd/marker_adapter.py`, related tests, `PROGRESS.md`, `phases/2-marker-adapter/index.json`. -- Dependencies: Phase 1 runtime contracts. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm the adapter can be tested without external services. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not parse formulas with Nougat. -- Do not implement Markdown rendering. -- Do not make every test load Marker models. diff --git a/phases/2-marker-adapter/step1.md b/phases/2-marker-adapter/step1.md deleted file mode 100644 index e28c315..0000000 --- a/phases/2-marker-adapter/step1.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 1: ocr-plan-handoff - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/0-harness-foundation/step2.md -- /phases/2-marker-adapter/step0.md - -## Task -Connect PyMuPDF page pre-analysis results to the Marker adapter as an OCR/layout handoff plan. - -The goal is to preserve page-level OCR decisions without making the entire document scan-only or text-only. - -## Sprint Contract -- Done means: the adapter accepts page-level OCR candidates and passes the relevant intent into Marker configuration or records an explicit unsupported-path fallback. -- Hard thresholds: OCR decisions stay page-aware; PyMuPDF remains pre-analysis only; no OCR logs are inserted into Markdown. -- Files owned: `src/pdftomd/marker_adapter.py`, `src/pdftomd/preanalysis.py` if needed, tests, `PROGRESS.md`, phase index. -- Dependencies: Phase 0 pre-analysis and Step 0 Marker adapter. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm mixed text/scanned sample traits are represented in tests. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not force document-wide OCR when only selected pages need OCR. -- Do not implement reading-order fixes here. -- Do not add a second primary parser. diff --git a/phases/2-marker-adapter/step2.md b/phases/2-marker-adapter/step2.md deleted file mode 100644 index 373e0bc..0000000 --- a/phases/2-marker-adapter/step2.md +++ /dev/null @@ -1,39 +0,0 @@ -# Step 2: marker-block-normalization - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/0-harness-foundation/step1.md -- /phases/2-marker-adapter/step0.md - -## Task -Map Marker structured output into the internal block model for headings, paragraphs, lists, tables, figures, captions, and equation candidates. - -Prefer structured Marker APIs or JSON-like structures over scraping final Markdown. - -## Sprint Contract -- Done means: fake Marker structures and at least one real or recorded sample shape map into internal block types. -- Hard thresholds: semantic block roles are preserved; bounding boxes and page numbers survive where available; formula blocks are only marked as candidates for Phase 3. -- Files owned: `src/pdftomd/marker_adapter.py`, model additions if required, tests, `PROGRESS.md`, phase index. -- Dependencies: Phase 0 models and Step 0 adapter. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm no final Markdown scraping is required for normal block mapping. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not perform Nougat conversion. -- Do not render Markdown. -- Do not discard page or bounding-box metadata without a documented reason. diff --git a/phases/2-marker-adapter/step3.md b/phases/2-marker-adapter/step3.md deleted file mode 100644 index 158c40c..0000000 --- a/phases/2-marker-adapter/step3.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 3: marker-failure-reporting - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/2-marker-adapter/step0.md -- /phases/2-marker-adapter/step2.md - -## Task -Define structured Marker failure reporting for parser errors, unsupported pages, timeout-like failures, and recoverable partial output. - -This prepares later CLI and resume behavior without writing CLI code. - -## Sprint Contract -- Done means: Marker adapter failures are typed, testable, and do not corrupt generated Markdown content. -- Hard thresholds: failures include page/chunk context where available; errors go to runtime reporting paths, not document body; fallback eligibility is explicit. -- Files owned: `src/pdftomd/marker_adapter.py`, error/reporting models, tests, `PROGRESS.md`, phase index. -- Dependencies: Steps 0 and 2. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm failure messages are actionable for CLI and evaluator use. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not silently swallow Marker failures. -- Do not implement resume state here. -- Do not write errors into Markdown chunks. diff --git a/phases/3-formula-pipeline/index.json b/phases/3-formula-pipeline/index.json deleted file mode 100644 index 316fac4..0000000 --- a/phases/3-formula-pipeline/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "3-formula-pipeline", - "steps": [ - { - "step": 0, - "name": "formula-block-detection", - "status": "pending" - }, - { - "step": 1, - "name": "nougat-command-adapter", - "status": "pending" - }, - { - "step": 2, - "name": "latex-validation-repair", - "status": "pending" - }, - { - "step": 3, - "name": "formula-reference-links", - "status": "pending" - } - ] -} diff --git a/phases/3-formula-pipeline/step0.md b/phases/3-formula-pipeline/step0.md deleted file mode 100644 index 686511e..0000000 --- a/phases/3-formula-pipeline/step0.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 0: formula-block-detection - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/2-marker-adapter/step2.md - -## Task -Implement formula candidate detection from normalized Marker blocks. - -Detect Marker equation blocks and text-pattern candidates while classifying inline versus block formulas based on block role and layout hints. - -## Sprint Contract -- Done means: formula candidates are represented as internal objects ready for Nougat or Marker fallback. -- Hard thresholds: ordinary currency-like dollar text is not blindly treated as math; inline/block distinction is tested; no Nougat invocation occurs yet. -- Files owned: `src/pdftomd/formulas.py`, tests, `PROGRESS.md`, `phases/3-formula-pipeline/index.json`. -- Dependencies: Phase 2 block normalization. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm tests include inline and block formula candidates. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not call Nougat. -- Do not render Markdown math. -- Do not make regex the only source when structured block role exists. diff --git a/phases/3-formula-pipeline/step1.md b/phases/3-formula-pipeline/step1.md deleted file mode 100644 index 15763a2..0000000 --- a/phases/3-formula-pipeline/step1.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 1: nougat-command-adapter - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/TOOLCHAIN.md -- /docs/CONVERSION_POLICY.md -- /phases/3-formula-pipeline/step0.md - -## Task -Implement the Nougat formula-only adapter boundary. - -The adapter should accept formula candidates and return LaTeX candidates or structured failure results. It should support a configured Nougat command path and be mockable in unit tests. - -## Sprint Contract -- Done means: Nougat execution is isolated behind a testable command adapter and never becomes the primary document parser. -- Hard thresholds: failures preserve Marker fallback text; tests do not require GPU/model execution by default; command path handling works on Windows. -- Files owned: `src/pdftomd/formulas.py`, optional `src/pdftomd/nougat_adapter.py`, tests, `PROGRESS.md`, phase index. -- Dependencies: Step 0 formula candidates and Phase 1 options. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm `.\venv\Scripts\nougat.exe --help` remains documented as an environment check, not a unit-test requirement. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not parse whole PDFs with Nougat. -- Do not require model downloads for normal unit tests. -- Do not discard Marker source text on failure. diff --git a/phases/3-formula-pipeline/step2.md b/phases/3-formula-pipeline/step2.md deleted file mode 100644 index 2f9a70d..0000000 --- a/phases/3-formula-pipeline/step2.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 2: latex-validation-repair - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/0-harness-foundation/step3.md -- /phases/3-formula-pipeline/step1.md - -## Task -Implement LaTeX and Markdown math validation for formula outputs, plus explicit repair helpers for safe cases. - -Validation should cover delimiter balance and common `\begin{...}` / `\end{...}` pairs. - -## Sprint Contract -- Done means: formula output validation returns actionable diagnostics and tested repairs for narrow, deterministic cases. -- Hard thresholds: validation does not silently mutate math; unrepairable failures fall back to Marker text; delimiter tests include both inline and block math. -- Files owned: `src/pdftomd/formulas.py`, `src/pdftomd/quality.py`, tests, `PROGRESS.md`, phase index. -- Dependencies: Phase 0 quality gates and Step 1 Nougat adapter. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm broken delimiter and environment examples are covered. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not build a broad LaTeX parser from scratch. -- Do not use LLM repair. -- Do not hide validation failures. diff --git a/phases/3-formula-pipeline/step3.md b/phases/3-formula-pipeline/step3.md deleted file mode 100644 index 2ce2b23..0000000 --- a/phases/3-formula-pipeline/step3.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 3: formula-reference-links - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/3-formula-pipeline/step2.md - -## Task -Preserve formula numbering and body references as internal Markdown link targets when confidence is sufficient. - -Support common English and Korean reference patterns such as `Eq. (3)` and `식 (5)`. - -## Sprint Contract -- Done means: formula anchors and reference rewrites are modeled and tested independently from final Markdown rendering. -- Hard thresholds: low-confidence matches remain plain text; duplicate formula numbers do not create unstable anchors; references never point to missing anchors. -- Files owned: `src/pdftomd/formulas.py`, reference model/tests, `PROGRESS.md`, phase index. -- Dependencies: Steps 0 through 2. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm duplicate and missing reference cases are tested. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not rewrite ambiguous references. -- Do not render final Markdown chunks. -- Do not remove the original formula number text. diff --git a/phases/4-semantic-enrichment/index.json b/phases/4-semantic-enrichment/index.json deleted file mode 100644 index e9f0daa..0000000 --- a/phases/4-semantic-enrichment/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "4-semantic-enrichment", - "steps": [ - { - "step": 0, - "name": "reading-order-checks", - "status": "pending" - }, - { - "step": 1, - "name": "paragraph-stitching", - "status": "pending" - }, - { - "step": 2, - "name": "header-footer-filtering", - "status": "pending" - }, - { - "step": 3, - "name": "reference-indexing", - "status": "pending" - } - ] -} diff --git a/phases/4-semantic-enrichment/step0.md b/phases/4-semantic-enrichment/step0.md deleted file mode 100644 index 8d30f39..0000000 --- a/phases/4-semantic-enrichment/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: reading-order-checks - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/2-marker-adapter/step2.md - -## Task -Create reading-order verification helpers over normalized blocks. - -Use page numbers and bounding boxes to detect obvious ordering anomalies in multi-column or inserted-text layouts. - -## Sprint Contract -- Done means: reading-order checks produce diagnostics that later enrichment and evaluator steps can use. -- Hard thresholds: checks are deterministic; tests include a multi-column-like fixture; helpers do not reorder content silently. -- Files owned: `src/pdftomd/enrichment.py`, tests, `PROGRESS.md`, `phases/4-semantic-enrichment/index.json`. -- Dependencies: Phase 2 normalized block model. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm diagnostics are actionable and tied to page/block ids. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not override Marker ordering without tests. -- Do not render Markdown. -- Do not call Marker or Nougat. diff --git a/phases/4-semantic-enrichment/step1.md b/phases/4-semantic-enrichment/step1.md deleted file mode 100644 index 76bde52..0000000 --- a/phases/4-semantic-enrichment/step1.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 1: paragraph-stitching - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/4-semantic-enrichment/step0.md - -## Task -Implement paragraph stitching for line-fragmented PDF text blocks. - -Handle continuation lines and hyphenated line breaks while preserving likely compound words or identifiers when confidence is low. - -## Sprint Contract -- Done means: paragraph stitching turns line fragments into coherent paragraph blocks with focused tests. -- Hard thresholds: hyphen joins are tested; low-confidence hyphen cases are preserved; list items and headings are not merged into paragraphs. -- Files owned: `src/pdftomd/enrichment.py`, tests, `PROGRESS.md`, phase index. -- Dependencies: Step 0 checks and normalized block model. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm Korean and English text fixtures remain stable. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not rely only on punctuation rules when bounding-box hints exist. -- Do not merge across tables, figures, or formulas. -- Do not modify source PDF files. diff --git a/phases/4-semantic-enrichment/step2.md b/phases/4-semantic-enrichment/step2.md deleted file mode 100644 index 8ac4d5d..0000000 --- a/phases/4-semantic-enrichment/step2.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 2: header-footer-filtering - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/4-semantic-enrichment/step1.md - -## Task -Detect repeated page headers, footers, and page numbers and separate them from the main Markdown body flow. - -The implementation should mark or remove repetitive boilerplate according to policy while keeping enough diagnostics for review. - -## Sprint Contract -- Done means: repeated top/bottom page-region text can be identified and excluded from main content in tests. -- Hard thresholds: unique body text is not removed; page number patterns are tested; removal decisions are deterministic. -- Files owned: `src/pdftomd/enrichment.py`, tests, `PROGRESS.md`, phase index. -- Dependencies: Paragraph and block model from earlier steps. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm false-positive protections are tested. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not delete content without a confidence rule. -- Do not write filtered text into sidecar document outputs. -- Do not implement CLI reporting here. diff --git a/phases/4-semantic-enrichment/step3.md b/phases/4-semantic-enrichment/step3.md deleted file mode 100644 index 4c42866..0000000 --- a/phases/4-semantic-enrichment/step3.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 3: reference-indexing - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/3-formula-pipeline/step3.md -- /phases/4-semantic-enrichment/step2.md - -## Task -Build a reference index for figures, tables, formulas, captions, and body references. - -The index should support later Markdown rendering by providing stable anchors and high-confidence link targets. - -## Sprint Contract -- Done means: table, figure, and formula references can be resolved or left plain with reasons. -- Hard thresholds: anchors are deterministic; duplicate labels are handled; missing targets do not produce broken links. -- Files owned: `src/pdftomd/enrichment.py`, reference models/tests, `PROGRESS.md`, phase index. -- Dependencies: Formula links and semantic block enrichment. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm figure/table/formula reference fixtures are covered. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not rewrite ambiguous references. -- Do not make anchors depend on nondeterministic ordering. -- Do not render final Markdown here. diff --git a/phases/5-markdown-rendering-assets/index.json b/phases/5-markdown-rendering-assets/index.json deleted file mode 100644 index c6fed0e..0000000 --- a/phases/5-markdown-rendering-assets/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "5-markdown-rendering-assets", - "steps": [ - { - "step": 0, - "name": "markdown-block-renderer", - "status": "pending" - }, - { - "step": 1, - "name": "table-renderer-fallbacks", - "status": "pending" - }, - { - "step": 2, - "name": "figure-asset-writer", - "status": "pending" - }, - { - "step": 3, - "name": "chunk-renderer", - "status": "pending" - } - ] -} diff --git a/phases/5-markdown-rendering-assets/step0.md b/phases/5-markdown-rendering-assets/step0.md deleted file mode 100644 index 22e7317..0000000 --- a/phases/5-markdown-rendering-assets/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: markdown-block-renderer - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/4-semantic-enrichment/index.json - -## Task -Implement block-level Markdown rendering for headings, paragraphs, lists, blockquotes, formulas, captions, and simple references. - -Renderer tests should use internal block fixtures, not live PDF parsing. - -## Sprint Contract -- Done means: core block types render to deterministic Markdown strings with focused tests. -- Hard thresholds: math delimiter validation is applied; renderer does not inject warnings/errors into Markdown; output is stable across runs. -- Files owned: `src/pdftomd/renderer.py`, tests, `PROGRESS.md`, `phases/5-markdown-rendering-assets/index.json`. -- Dependencies: Phase 4 enriched blocks and Phase 3 formula outputs. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm renderer tests are focused, not full snapshots. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not invoke Marker or Nougat. -- Do not implement table/asset file writing in this step. -- Do not add sidecar document outputs. diff --git a/phases/5-markdown-rendering-assets/step1.md b/phases/5-markdown-rendering-assets/step1.md deleted file mode 100644 index 2440197..0000000 --- a/phases/5-markdown-rendering-assets/step1.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 1: table-renderer-fallbacks - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/5-markdown-rendering-assets/step0.md - -## Task -Implement table rendering policy for Markdown tables, limited HTML tables, and image fallback links. - -Use structured table objects and avoid ad hoc string parsing for complex cases where possible. - -## Sprint Contract -- Done means: simple tables render as Markdown, complex tables can render as limited HTML or fallback references, and table captions/footnotes are preserved. -- Hard thresholds: tests cover merged-cell-like structures, footnotes, captions, and table fallback decisions; invalid table output is detected by quality gates. -- Files owned: `src/pdftomd/renderer.py`, table models/tests, `PROGRESS.md`, phase index. -- Dependencies: Step 0 renderer and Phase 0 quality gates. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm fallback images are linked but not generated unless a table asset exists. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not fake table content that was not extracted. -- Do not discard captions or footnotes. -- Do not implement full HTML sanitizer scope beyond limited table output. diff --git a/phases/5-markdown-rendering-assets/step2.md b/phases/5-markdown-rendering-assets/step2.md deleted file mode 100644 index da667f2..0000000 --- a/phases/5-markdown-rendering-assets/step2.md +++ /dev/null @@ -1,39 +0,0 @@ -# Step 2: figure-asset-writer - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/1-core-runtime-contracts/step2.md -- /phases/5-markdown-rendering-assets/step0.md - -## Task -Implement deterministic image/figure asset writing and Markdown image reference generation. - -Use hash-based deduplication when asset bytes are available and preserve figure captions and reference anchors. - -## Sprint Contract -- Done means: figure assets can be written to temp output bundles with deterministic names and Markdown references. -- Hard thresholds: duplicate images share stored assets where configured; Korean path output is tested; missing assets produce validation failures, not broken silent links. -- Files owned: `src/pdftomd/assets.py`, renderer integration/tests, `PROGRESS.md`, phase index. -- Dependencies: Output bundle contract and renderer. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm tests write only to temporary directories. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not write into real `output/` during tests. -- Do not rename source PDFs. -- Do not drop figure captions. diff --git a/phases/5-markdown-rendering-assets/step3.md b/phases/5-markdown-rendering-assets/step3.md deleted file mode 100644 index b7ede5f..0000000 --- a/phases/5-markdown-rendering-assets/step3.md +++ /dev/null @@ -1,39 +0,0 @@ -# Step 3: chunk-renderer - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /phases/1-core-runtime-contracts/step2.md -- /phases/5-markdown-rendering-assets/step2.md - -## Task -Implement chunk planning and chunk Markdown bundle writing over enriched blocks. - -Chunk boundaries should target 20 pages but preserve logical block integrity for paragraphs, tables, figures, and formulas. - -## Sprint Contract -- Done means: chunk files with frontmatter can be written deterministically from internal document fixtures. -- Hard thresholds: block integrity is preserved at chunk boundaries; chunk frontmatter includes minimum context; quality gates run on rendered chunks. -- Files owned: `src/pdftomd/chunking.py`, `src/pdftomd/renderer.py`, tests, `PROGRESS.md`, phase index. -- Dependencies: Renderer, assets, and output bundle contracts. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm long-document chunk fixtures cover boundary behavior. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not split blocks in the middle to satisfy exact 20-page counts. -- Do not create document sidecar metadata files. -- Do not implement CLI orchestration here. diff --git a/phases/6-cli-runtime-resume/index.json b/phases/6-cli-runtime-resume/index.json deleted file mode 100644 index f5389c5..0000000 --- a/phases/6-cli-runtime-resume/index.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "6-cli-runtime-resume", - "steps": [ - { - "step": 0, - "name": "cli-entrypoint-options", - "status": "pending" - }, - { - "step": 1, - "name": "progress-logging", - "status": "pending" - }, - { - "step": 2, - "name": "resume-state", - "status": "pending" - }, - { - "step": 3, - "name": "device-oom-policy", - "status": "pending" - }, - { - "step": 4, - "name": "model-cache-offline", - "status": "pending" - } - ] -} diff --git a/phases/6-cli-runtime-resume/step0.md b/phases/6-cli-runtime-resume/step0.md deleted file mode 100644 index 90b8e44..0000000 --- a/phases/6-cli-runtime-resume/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: cli-entrypoint-options - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /phases/1-core-runtime-contracts/index.json -- /phases/5-markdown-rendering-assets/index.json - -## Task -Implement the `python -m pdftomd` CLI entrypoint and option parsing over the existing library API. - -Expose input PDF, output directory, formula parser mode, Nougat command, runtime/device, chunk size, logging, and resume options. - -## Sprint Contract -- Done means: CLI options map into typed conversion options and can run against a mocked pipeline in tests. -- Hard thresholds: CLI does not duplicate conversion logic; defaults match docs; explicit `cuda` and `auto` modes are represented. -- Files owned: `src/pdftomd/__main__.py`, CLI modules/tests, `README.md` if command docs change, `PROGRESS.md`, phase index. -- Dependencies: Core contracts and renderer pipeline. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm CLI help text shows documented options. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not put parser logic inside CLI parsing code. -- Do not implement PyQt UI. -- Do not silently CPU fallback for explicit CUDA mode. diff --git a/phases/6-cli-runtime-resume/step1.md b/phases/6-cli-runtime-resume/step1.md deleted file mode 100644 index 23c738d..0000000 --- a/phases/6-cli-runtime-resume/step1.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 1: progress-logging - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/6-cli-runtime-resume/step0.md - -## Task -Implement progress reporting and stderr/local log behavior for chunk-level conversion. - -Progress should summarize chunk success/failure without writing warnings or errors into Markdown content. - -## Sprint Contract -- Done means: CLI/runtime tests can observe progress events and log file output in temp locations. -- Hard thresholds: Markdown chunks remain free of warning/error logs; failure summaries include chunk ids; logs use deterministic local paths from Phase 1. -- Files owned: `src/pdftomd/runtime.py`, CLI integration/tests, `PROGRESS.md`, phase index. -- Dependencies: CLI entrypoint and output/cache contracts. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm stderr/log behavior is tested separately from Markdown output. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not write runtime logs inside generated Markdown. -- Do not require a real PDF conversion for progress unit tests. -- Do not create persistent logs outside temp dirs in tests. diff --git a/phases/6-cli-runtime-resume/step2.md b/phases/6-cli-runtime-resume/step2.md deleted file mode 100644 index f6abf7b..0000000 --- a/phases/6-cli-runtime-resume/step2.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 2: resume-state - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/CONVERSION_POLICY.md -- /phases/6-cli-runtime-resume/step1.md - -## Task -Implement runtime resume state for successful and failed chunks. - -Resume state is a runtime artifact, not a document output sidecar. - -## Sprint Contract -- Done means: conversion can skip completed chunks and retry failed chunks using a local state file in tests. -- Hard thresholds: state format is deterministic; stale state is detected; resume does not skip chunks when input/options changed materially. -- Files owned: `src/pdftomd/resume.py`, runtime integration/tests, `PROGRESS.md`, phase index. -- Dependencies: Progress/logging and chunk renderer contracts. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm state files are written only under temp/runtime cache paths in tests. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not treat resume state as part of generated document output. -- Do not skip chunks after parser/version-relevant option changes. -- Do not create hidden global state. diff --git a/phases/6-cli-runtime-resume/step3.md b/phases/6-cli-runtime-resume/step3.md deleted file mode 100644 index e310669..0000000 --- a/phases/6-cli-runtime-resume/step3.md +++ /dev/null @@ -1,39 +0,0 @@ -# Step 3: device-oom-policy - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ARCHITECTURE.md -- /docs/CONVERSION_POLICY.md -- /docs/TOOLCHAIN.md -- /phases/1-core-runtime-contracts/step1.md - -## Task -Implement runtime device selection, CUDA fail-fast behavior, auto CPU fallback behavior, and OOM retry policy hooks. - -This step should be tested with mocks and small CUDA smoke checks only where safe. - -## Sprint Contract -- Done means: runtime policy enforces explicit CUDA fail-fast, auto fallback warning, and configurable OOM retry reductions. -- Hard thresholds: no silent CPU fallback for explicit CUDA; tests do not require exhausting VRAM; GTX 1070 Ti constraints remain documented. -- Files owned: `src/pdftomd/runtime.py`, tests, `docs/TOOLCHAIN.md` if behavior changes, `PROGRESS.md`, phase index. -- Dependencies: Runtime config options. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm CUDA smoke test instructions still work separately. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not intentionally trigger real GPU OOM in tests. -- Do not change PyTorch pins without updating `docs/TOOLCHAIN.md`. -- Do not hide runtime warnings. diff --git a/phases/6-cli-runtime-resume/step4.md b/phases/6-cli-runtime-resume/step4.md deleted file mode 100644 index f61f68b..0000000 --- a/phases/6-cli-runtime-resume/step4.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 4: model-cache-offline - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/TOOLCHAIN.md -- /docs/ARCHITECTURE.md -- /phases/6-cli-runtime-resume/step3.md - -## Task -Document and wire model cache/offline behavior for Marker, Nougat, and Hugging Face cache paths. - -Add CLI/runtime hooks for environment variables or explicit cache paths without downloading models during tests. - -## Sprint Contract -- Done means: users can see how to pre-download models and run offline, and runtime cache paths are configurable. -- Hard thresholds: no test performs network download; docs include Windows commands; cache path policy matches Phase 1. -- Files owned: `src/pdftomd/runtime.py`, `README.md`, `docs/TOOLCHAIN.md`, tests, `PROGRESS.md`, phase index. -- Dependencies: Device/runtime policy and cache contracts. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm offline instructions are clear and do not imply bundled weights. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not download model weights as part of tests. -- Do not commit model caches. -- Do not make online access mandatory for already-cached models. diff --git a/phases/7-mvp-quality-hardening/index.json b/phases/7-mvp-quality-hardening/index.json deleted file mode 100644 index b1283ef..0000000 --- a/phases/7-mvp-quality-hardening/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "7-mvp-quality-hardening", - "steps": [ - { - "step": 0, - "name": "sample-smoke-conversions", - "status": "pending" - }, - { - "step": 1, - "name": "quality-metrics-report", - "status": "pending" - }, - { - "step": 2, - "name": "regression-thresholds", - "status": "pending" - }, - { - "step": 3, - "name": "mvp-fix-sweep", - "status": "pending" - } - ] -} diff --git a/phases/7-mvp-quality-hardening/step0.md b/phases/7-mvp-quality-hardening/step0.md deleted file mode 100644 index 7248a4c..0000000 --- a/phases/7-mvp-quality-hardening/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: sample-smoke-conversions - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/PRD.md -- /docs/CONVERSION_POLICY.md -- /phases/6-cli-runtime-resume/index.json - -## Task -Create controlled sample smoke conversion tests for the MVP corpus. - -The tests should exercise the end-to-end pipeline on a small selected subset or page range first, then document which full documents are suitable for manual or slower regression runs. - -## Sprint Contract -- Done means: at least one text-layer sample and one mixed/scanned-risk sample can be converted in a controlled test path. -- Hard thresholds: tests have runtime bounds; sample selection comes from `samples/metadata.json`; generated output is checked with quality gates. -- Files owned: `tests/`, sample metadata updates if needed, `PROGRESS.md`, `phases/7-mvp-quality-hardening/index.json`. -- Dependencies: CLI/runtime and renderer phases complete. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Record sample coverage and any skipped slow tests in `PROGRESS.md`. -3. Update this phase index. - -## Do Not -- Do not make every validation run process all long PDFs if runtime becomes impractical. -- Do not commit generated `output/` bundles. -- Do not weaken quality gates to pass broken output. diff --git a/phases/7-mvp-quality-hardening/step1.md b/phases/7-mvp-quality-hardening/step1.md deleted file mode 100644 index e46d738..0000000 --- a/phases/7-mvp-quality-hardening/step1.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 1: quality-metrics-report - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/PRD.md -- /phases/7-mvp-quality-hardening/step0.md - -## Task -Add focused quality metrics for converted Markdown bundles. - -Metrics should cover headings, math delimiter balance, LaTeX environment pairs, image links, captions, table parseability, chunk frontmatter, and no-exception conversion. - -## Sprint Contract -- Done means: evaluator-friendly quality metrics can be run on sample outputs and produce actionable failure messages. -- Hard thresholds: metrics do not rely on full Markdown snapshots; failures identify file/chunk/block context; reports stay out of generated Markdown. -- Files owned: `src/pdftomd/quality.py`, `tests/`, optional scripts under `scripts/`, `PROGRESS.md`, phase index. -- Dependencies: Step 0 sample smoke conversions and quality gates. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm metrics can be used by `harness-review`. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not create broad snapshot baselines as the primary quality gate. -- Do not write quality reports inside Markdown chunks. -- Do not hide per-chunk failures. diff --git a/phases/7-mvp-quality-hardening/step2.md b/phases/7-mvp-quality-hardening/step2.md deleted file mode 100644 index adc3441..0000000 --- a/phases/7-mvp-quality-hardening/step2.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 2: regression-thresholds - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/PRD.md -- /phases/7-mvp-quality-hardening/step1.md - -## Task -Define MVP regression thresholds for the sample corpus. - -Thresholds should distinguish mandatory fast validation from slower/manual quality checks. - -## Sprint Contract -- Done means: MVP pass/fail criteria are encoded in tests or documented commands and tied to sample metadata traits. -- Hard thresholds: mandatory validation remains runnable on the local machine; slow tests are opt-in; failed quality areas are not masked. -- Files owned: `tests/`, `scripts/`, sample metadata updates if needed, `PROGRESS.md`, phase index. -- Dependencies: Quality metrics report. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm slow tests are documented separately if needed. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not make local validation unusably slow. -- Do not turn all failures into warnings. -- Do not remove sample coverage for Korean paths or formulas. diff --git a/phases/7-mvp-quality-hardening/step3.md b/phases/7-mvp-quality-hardening/step3.md deleted file mode 100644 index 781256c..0000000 --- a/phases/7-mvp-quality-hardening/step3.md +++ /dev/null @@ -1,37 +0,0 @@ -# Step 3: mvp-fix-sweep - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/PRD.md -- /phases/7-mvp-quality-hardening/step2.md - -## Task -Run a focused MVP stabilization pass based on failing quality metrics and sample smoke tests. - -This step should fix only defects revealed by prior acceptance criteria and should avoid feature expansion. - -## Sprint Contract -- Done means: MVP fast validation and selected sample smoke conversions pass with documented residual risks. -- Hard thresholds: fixes are test-backed; no new primary parser is introduced; out-of-scope UI/API/LLM features remain out of scope. -- Files owned: failing modules and tests identified by prior phase output, `PROGRESS.md`, phase index. -- Dependencies: Regression thresholds and quality reports. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Record remaining quality risks in `PROGRESS.md`. -3. Update this phase index. - -## Do Not -- Do not use this as a broad refactor step. -- Do not add new major features. -- Do not bypass failed quality gates without recording a blocker. diff --git a/phases/8-release-docs-packaging/index.json b/phases/8-release-docs-packaging/index.json deleted file mode 100644 index 51de5aa..0000000 --- a/phases/8-release-docs-packaging/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "8-release-docs-packaging", - "steps": [ - { - "step": 0, - "name": "readme-usage-flow", - "status": "pending" - }, - { - "step": 1, - "name": "environment-bootstrap-docs", - "status": "pending" - }, - { - "step": 2, - "name": "license-checkpoint", - "status": "pending" - }, - { - "step": 3, - "name": "release-checklist", - "status": "pending" - } - ] -} diff --git a/phases/8-release-docs-packaging/step0.md b/phases/8-release-docs-packaging/step0.md deleted file mode 100644 index 551afb8..0000000 --- a/phases/8-release-docs-packaging/step0.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 0: readme-usage-flow - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /README.md -- /phases/7-mvp-quality-hardening/index.json - -## Task -Update README usage flow for the MVP CLI. - -Document install, validation, basic conversion, formula parser modes, runtime modes, output layout, resume, and logs. - -## Sprint Contract -- Done means: a user can follow README instructions to run the local CLI on Windows after environment setup. -- Hard thresholds: commands match implemented CLI; docs do not promise PyQt or hosted API as MVP; generated output contract is accurate. -- Files owned: `README.md`, docs as needed, `PROGRESS.md`, `phases/8-release-docs-packaging/index.json`. -- Dependencies: MVP quality hardening complete. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm documented commands are copy-pasteable in PowerShell. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not document unimplemented features as available. -- Do not add marketing-style content. -- Do not include model weights in the repository. diff --git a/phases/8-release-docs-packaging/step1.md b/phases/8-release-docs-packaging/step1.md deleted file mode 100644 index a794350..0000000 --- a/phases/8-release-docs-packaging/step1.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 1: environment-bootstrap-docs - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/TOOLCHAIN.md -- /requirements.txt - -## Task -Document and optionally script the repo-local environment bootstrap flow. - -Cover Conda Python 3.11, requirements install, CUDA smoke test, `pip check`, and Nougat help check. - -## Sprint Contract -- Done means: environment setup instructions reflect the verified GTX 1070 Ti / torch 2.7.1+cu126 baseline. -- Hard thresholds: dependency pins remain consistent across README, TOOLCHAIN, and requirements; no unverified torch upgrade is introduced. -- Files owned: `README.md`, `docs/TOOLCHAIN.md`, optional `scripts/`, `PROGRESS.md`, phase index. -- Dependencies: MVP CLI docs. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pip check -.\venv\Scripts\nougat.exe --help -``` - -## Verification -1. Run the acceptance commands where local environment is available. -2. Explain any skipped environment command in `PROGRESS.md`. -3. Update this phase index. - -## Do Not -- Do not replace the single `venv` policy. -- Do not require `uv`. -- Do not change pins without official compatibility verification. diff --git a/phases/8-release-docs-packaging/step2.md b/phases/8-release-docs-packaging/step2.md deleted file mode 100644 index c6a25e2..0000000 --- a/phases/8-release-docs-packaging/step2.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 2: license-checkpoint - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/ADR.md -- /docs/TOOLCHAIN.md - -## Task -Add a licensing checkpoint document or section for current personal use and future redistribution/commercial review. - -This is not legal advice. It should identify Marker GPL/model-weight concerns and when to revisit them. - -## Sprint Contract -- Done means: docs clearly state current personal-use context and future review triggers. -- Hard thresholds: docs do not claim legal conclusions; process/API isolation is described only as a risk mitigation candidate; model weights are not redistributed. -- Files owned: `docs/TOOLCHAIN.md`, `docs/ADR.md`, optional `README.md`, `PROGRESS.md`, phase index. -- Dependencies: Release docs context. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm license notes are cautious and consistent. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not provide legal advice. -- Do not mark the project commercially safe without review. -- Do not vendor model weights. diff --git a/phases/8-release-docs-packaging/step3.md b/phases/8-release-docs-packaging/step3.md deleted file mode 100644 index 35d3d81..0000000 --- a/phases/8-release-docs-packaging/step3.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 3: release-checklist - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /README.md -- /docs/TOOLCHAIN.md - -## Task -Create the local MVP release checklist. - -Include validation, sample smoke conversion, environment checks, offline cache readiness, known limitations, and next phase entry conditions. - -## Sprint Contract -- Done means: the repository has a concise checklist for deciding whether the local MVP is ready for personal use. -- Hard thresholds: checklist references real commands; known limitations are explicit; PyQt phase remains separate. -- Files owned: `README.md`, optional `docs/RELEASE_CHECKLIST.md`, `PROGRESS.md`, phase index. -- Dependencies: Prior release docs steps. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm checklist can be followed by a fresh agent or user. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not claim production readiness. -- Do not include hosted API release work. -- Do not start PyQt implementation. diff --git a/phases/9-pyqt-thin-client/index.json b/phases/9-pyqt-thin-client/index.json deleted file mode 100644 index d2c54fd..0000000 --- a/phases/9-pyqt-thin-client/index.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "project": "PDFtoMD", - "phase": "9-pyqt-thin-client", - "steps": [ - { - "step": 0, - "name": "ui-api-contract", - "status": "pending" - }, - { - "step": 1, - "name": "pyqt-shell", - "status": "pending" - }, - { - "step": 2, - "name": "ui-progress-resume", - "status": "pending" - }, - { - "step": 3, - "name": "ui-packaging-notes", - "status": "pending" - } - ] -} diff --git a/phases/9-pyqt-thin-client/step0.md b/phases/9-pyqt-thin-client/step0.md deleted file mode 100644 index 6515767..0000000 --- a/phases/9-pyqt-thin-client/step0.md +++ /dev/null @@ -1,38 +0,0 @@ -# Step 0: ui-api-contract - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/UI_GUIDE.md -- /docs/ARCHITECTURE.md -- /phases/8-release-docs-packaging/index.json - -## Task -Define the stable core API contract that PyQt will call. - -This step should verify that the UI does not need to import Marker, Nougat, PyMuPDF, or renderer internals directly. - -## Sprint Contract -- Done means: UI-facing API functions/classes are documented and tested without building UI screens. -- Hard thresholds: UI remains a thin client; conversion logic is not duplicated; progress/resume events are available through the API. -- Files owned: `src/pdftomd/`, `tests/`, `docs/UI_GUIDE.md`, `PROGRESS.md`, `phases/9-pyqt-thin-client/index.json`. -- Dependencies: Local MVP release-ready CLI/library. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -.\venv\python.exe -m pytest tests -``` - -## Verification -1. Run the acceptance commands. -2. Confirm UI boundary in `docs/UI_GUIDE.md` remains accurate. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not create UI widgets yet. -- Do not import parser internals in UI-facing code. -- Do not change CLI behavior unless tests require it. diff --git a/phases/9-pyqt-thin-client/step1.md b/phases/9-pyqt-thin-client/step1.md deleted file mode 100644 index a2780b3..0000000 --- a/phases/9-pyqt-thin-client/step1.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 1: pyqt-shell - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/UI_GUIDE.md -- /phases/9-pyqt-thin-client/step0.md - -## Task -Create the minimal PyQt application shell for selecting a PDF, choosing output directory, and configuring runtime/formula options. - -Keep the interface quiet and utilitarian for repeated local conversion work. - -## Sprint Contract -- Done means: a minimal PyQt window can launch and bind controls to the core API config without running conversion by default. -- Hard thresholds: UI does not duplicate conversion engine logic; long Korean paths remain visible via tooltip or equivalent; defaults match CLI. -- Files owned: UI package/modules, tests if practical, `docs/UI_GUIDE.md`, `PROGRESS.md`, phase index. -- Dependencies: UI API contract. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. If UI launch cannot be automated, record manual verification steps in `PROGRESS.md`. -3. Update this phase index. - -## Do Not -- Do not create a marketing landing page. -- Do not call Marker/Nougat directly from widgets. -- Do not make UI the only way to run conversion. diff --git a/phases/9-pyqt-thin-client/step2.md b/phases/9-pyqt-thin-client/step2.md deleted file mode 100644 index 25849c3..0000000 --- a/phases/9-pyqt-thin-client/step2.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 2: ui-progress-resume - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/UI_GUIDE.md -- /phases/9-pyqt-thin-client/step1.md - -## Task -Connect UI progress, cancellation, error summary, log opening, and resume controls to the core API. - -The UI should show chunk success/failure and respect CUDA/auto runtime semantics. - -## Sprint Contract -- Done means: UI can display progress events and expose resume behavior without corrupting generated Markdown. -- Hard thresholds: explicit CUDA failures are shown clearly; auto fallback warnings are visible; cancellation does not leave inconsistent runtime state. -- Files owned: UI modules, tests/manual verification notes, `PROGRESS.md`, phase index. -- Dependencies: PyQt shell and core runtime events. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Record UI smoke test steps and screenshots only if the user asks for them. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not write logs into Markdown content. -- Do not silently fallback from explicit CUDA to CPU. -- Do not implement a separate UI-only resume system. diff --git a/phases/9-pyqt-thin-client/step3.md b/phases/9-pyqt-thin-client/step3.md deleted file mode 100644 index 5d7034e..0000000 --- a/phases/9-pyqt-thin-client/step3.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 3: ui-packaging-notes - -## Read First -- /AGENTS.md -- /PLAN.md -- /PROGRESS.md -- /docs/HARNESS.md -- /docs/IMPLEMENTATION_PLAN.md -- /docs/UI_GUIDE.md -- /phases/9-pyqt-thin-client/step2.md - -## Task -Document PyQt local packaging and known limitations for Windows. - -This step may add packaging notes or scripts only if they are consistent with the verified environment. - -## Sprint Contract -- Done means: UI packaging limitations and local execution commands are documented without changing the core engine contract. -- Hard thresholds: docs do not promise standalone redistribution without license review; model weights remain external/cache-based; CLI remains supported. -- Files owned: `README.md`, `docs/UI_GUIDE.md`, optional packaging docs/scripts, `PROGRESS.md`, phase index. -- Dependencies: UI progress/resume behavior. - -## Acceptance Criteria -```powershell -python scripts\validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm packaging notes mention license/model cache constraints. -3. Update `PROGRESS.md` and this phase index. - -## Do Not -- Do not build an installer unless explicitly requested. -- Do not bundle model weights. -- Do not drop CLI documentation. diff --git a/phases/index.json b/phases/index.json deleted file mode 100644 index 0bc97f7..0000000 --- a/phases/index.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "phases": [ - { - "dir": "0-harness-foundation", - "status": "completed" - }, - { - "dir": "1-core-runtime-contracts", - "status": "completed" - }, - { - "dir": "2-marker-adapter", - "status": "pending" - }, - { - "dir": "3-formula-pipeline", - "status": "pending" - }, - { - "dir": "4-semantic-enrichment", - "status": "pending" - }, - { - "dir": "5-markdown-rendering-assets", - "status": "pending" - }, - { - "dir": "6-cli-runtime-resume", - "status": "pending" - }, - { - "dir": "7-mvp-quality-hardening", - "status": "pending" - }, - { - "dir": "8-release-docs-packaging", - "status": "pending" - }, - { - "dir": "9-pyqt-thin-client", - "status": "pending" - } - ] -} diff --git a/plugins/harness-engineering/.codex-plugin/plugin.json b/plugins/harness-engineering/.codex-plugin/plugin.json deleted file mode 100644 index 60b9951..0000000 --- a/plugins/harness-engineering/.codex-plugin/plugin.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "harness-engineering", - "version": "1.0.0", - "description": "Repo-local Harness Engineering slash commands for Codex.", - "interface": { - "displayName": "Harness Engineering", - "shortDescription": "Harness planning and review prompts for this repo", - "longDescription": "Optional local plugin that exposes Harness Engineering slash commands while the core workflow remains in repo-native AGENTS, skills, custom agents, and hooks.", - "developerName": "Local Repository", - "category": "Productivity", - "capabilities": [ - "Interactive", - "Read", - "Write" - ], - "defaultPrompt": [ - "Use Harness Engineering to plan a new phase for this repository.", - "Review my changes against the Harness docs and rules." - ], - "brandColor": "#2563EB" - } -} diff --git a/plugins/harness-engineering/agents/openai.yaml b/plugins/harness-engineering/agents/openai.yaml deleted file mode 100644 index 2671cba..0000000 --- a/plugins/harness-engineering/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Harness Engineering" - short_description: "Use Harness slash commands in this repository" - default_prompt: "Use Harness Engineering to plan a phase or review changes in this repository." diff --git a/plugins/harness-engineering/commands/harness.md b/plugins/harness-engineering/commands/harness.md deleted file mode 100644 index 2a77317..0000000 --- a/plugins/harness-engineering/commands/harness.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -description: Run the Harness Engineering planning workflow for this repository. ---- - -# /harness - -## Preflight - -- Read `/AGENTS.md`, `/docs/PRD.md`, `/docs/ARCHITECTURE.md`, `/docs/HARNESS.md`, `/docs/ADR.md`, and `/docs/UI_GUIDE.md` if they exist. -- Confirm whether the user wants discussion only, a draft plan, or file generation under `phases/`. -- Note whether the user explicitly asked for subagents; only then consider `phase_planner` or built-in explorers/workers. - -## Plan - -- State what will be created or updated before editing files. -- If a plan already exists under `phases/`, say whether you are extending it or replacing part of it. -- Keep each proposed step small, self-contained, independently executable, and backed by a Sprint Contract. - -## Commands - -- Invoke `$harness-workflow` and follow it. -- When file generation is requested, create or update: - - `phases/index.json` - - `phases/{phase}/index.json` - - `phases/{phase}/stepN.md` -- Use `python scripts/execute.py ` as the runtime target when you need to reference execution. - -## Verification - -- Re-read the generated phase files for consistency. -- Check that step numbering, phase names, and acceptance commands line up. -- Check that each step has done criteria, hard thresholds, owned files, dependencies, and explicit prohibitions. -- If the repo has a validator, prefer `python scripts/validate_workspace.py` as the default acceptance command unless the user specified a narrower command. - -## Summary - -## Result -- **Action**: planned or generated Harness phase files -- **Status**: success | partial | failed -- **Details**: phase name, step count, and any blockers - -## Next Steps - -- Suggest the next natural command, usually `python scripts/execute.py ` or a focused edit to one generated step. diff --git a/plugins/harness-engineering/commands/review.md b/plugins/harness-engineering/commands/review.md deleted file mode 100644 index 65a1359..0000000 --- a/plugins/harness-engineering/commands/review.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: Review local changes against Harness repository rules and docs. ---- - -# /review - -## Preflight - -- Read `/AGENTS.md`, `/docs/ARCHITECTURE.md`, `/docs/HARNESS.md`, `/docs/ADR.md`, and `/docs/UI_GUIDE.md` if they exist. -- Identify the changed files or generated `phases/` artifacts that need review. -- If the user wants a delegated review, use the read-only custom agent `harness_reviewer` only when they explicitly asked for subagents. - -## Plan - -- State what evidence will be checked: docs, changed files, generated phase files, and validation output if available. -- Prioritize correctness, architecture drift, missing Sprint Contracts, CRITICAL rule violations, and missing tests over style commentary. - -## Commands - -- Invoke `$harness-review`. -- Use Codex built-in `/review` when the user specifically wants a code-review style pass over the working tree or git diff. -- If validation is relevant, run `python scripts/validate_workspace.py` or explain why it was not run. - -## Verification - -- Confirm that every finding is tied to a file and an actual rule or behavioral risk. -- If no findings remain, say so explicitly and mention residual risks or missing evidence. - -## Summary - -## Result -- **Action**: reviewed Harness changes -- **Status**: success | partial | failed -- **Details**: findings, docs checked, and validation status - -## Next Steps - -- Suggest the smallest follow-up: fix the top finding, rerun validation, or execute a pending phase. diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 11874aa..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[build-system] -requires = ["setuptools>=68"] -build-backend = "setuptools.build_meta" - -[project] -name = "pdftomd" -version = "0.1.0" -description = "Local-first PDF to Markdown conversion engine for AI-agent-readable document bundles." -requires-python = ">=3.11" - -[tool.setuptools.packages.find] -where = ["src"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4445e4b..0000000 --- a/requirements.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Unified PDFtoMD environment. -# Create with Python 3.11, then install with: -# .\venv\python.exe -m pip install -r requirements.txt -# -# GTX 1070 Ti requires a PyTorch build that still supports sm_61. -# torch 2.11.0+cu128 imports, but does not support sm_61 at runtime. ---extra-index-url https://download.pytorch.org/whl/cu126 - -torch==2.7.1+cu126 -torchvision==0.22.1+cu126 - -# Marker / main conversion path. -marker-pdf==1.10.2 -pymupdf==1.27.2.3 -pandas==3.0.2 -pytest==9.0.3 -Pillow==10.4.0 -pypdfium2==4.30.0 -opencv-python-headless==4.11.0.86 - -# Nougat formula parser path. -# nougat-ocr 0.1.17 has loose dependency bounds. These pins avoid -# incompatible modern releases while preserving Marker's dependency pins. -nougat-ocr==0.1.17 -transformers==4.57.6 -albumentations==1.3.1 -fsspec==2026.2.0 diff --git a/samples/2007쉘구조물의유한요소해석에대하여.pdf b/samples/2007쉘구조물의유한요소해석에대하여.pdf deleted file mode 100644 index b2bdbc0..0000000 --- a/samples/2007쉘구조물의유한요소해석에대하여.pdf +++ /dev/null @@ -1,33600 +0,0 @@ -%PDF-1.4 % -5239 0 obj <> endobj -xref -5239 761 -0000000016 00000 n -0000018013 00000 n -0000018102 00000 n -0000018304 00000 n -0000018803 00000 n -0000019341 00000 n -0000019589 00000 n -0000019816 00000 n -0000020335 00000 n -0000020579 00000 n -0000021038 00000 n -0000021693 00000 n -0000021958 00000 n -0000022211 00000 n -0000022501 00000 n -0000022729 00000 n -0000022768 00000 n -0000028524 00000 n -0000029094 00000 n -0000029621 00000 n -0000030074 00000 n -0000030189 00000 n -0000030268 00000 n -0000030575 00000 n -0000030644 00000 n -0000031668 00000 n -0000031755 00000 n -0000032004 00000 n -0000032476 00000 n -0000032710 00000 n -0000032947 00000 n -0000034554 00000 n -0000035029 00000 n -0000035576 00000 n -0000035818 00000 n -0000036471 00000 n -0000036935 00000 n -0000038199 00000 n -0000038445 00000 n -0000039336 00000 n -0000039578 00000 n -0000040515 00000 n -0000041028 00000 n -0000041905 00000 n -0000042838 00000 n -0000043938 00000 n -0000044167 00000 n -0000044407 00000 n -0000044947 00000 n -0000045487 00000 n -0000045773 00000 n -0000046101 00000 n -0000046160 00000 n -0000046308 00000 n -0000046346 00000 n -0000046390 00000 n -0000047531 00000 n -0000048653 00000 n -0000051325 00000 n -0000051693 00000 n -0000051737 00000 n -0000051874 00000 n -0000052548 00000 n -0000053018 00000 n -0000053441 00000 n -0000053592 00000 n -0000054311 00000 n -0000054973 00000 n -0000055472 00000 n -0000055941 00000 n -0000056436 00000 n -0000056861 00000 n -0000057305 00000 n -0000057646 00000 n -0000057944 00000 n -0000058468 00000 n -0000058875 00000 n -0000059373 00000 n -0000059761 00000 n -0000060252 00000 n -0000060590 00000 n -0000061013 00000 n -0000061617 00000 n -0000061875 00000 n -0000062207 00000 n -0000062251 00000 n -0000062346 00000 n -0000062719 00000 n -0000062967 00000 n -0000063660 00000 n -0000064102 00000 n -0000064684 00000 n -0000065221 00000 n -0000065463 00000 n -0000065592 00000 n -0000065877 00000 n -0000065921 00000 n -0000065977 00000 n -0000066423 00000 n -0000066459 00000 n -0000066835 00000 n -0000066879 00000 n -0000067028 00000 n -0000067263 00000 n -0000067824 00000 n -0000068042 00000 n -0000068250 00000 n -0000068401 00000 n -0000068841 00000 n -0000069186 00000 n -0000069778 00000 n -0000070240 00000 n -0000070773 00000 n -0000071087 00000 n -0000071287 00000 n -0000071706 00000 n -0000071896 00000 n -0000072207 00000 n -0000072624 00000 n -0000072947 00000 n -0000073411 00000 n -0000073610 00000 n -0000073855 00000 n -0000074143 00000 n -0000074187 00000 n -0000074414 00000 n -0000074461 00000 n -0000074790 00000 n -0000074834 00000 n -0000074934 00000 n -0000075471 00000 n -0000075624 00000 n -0000076142 00000 n -0000076606 00000 n -0000077070 00000 n -0000077356 00000 n -0000077863 00000 n -0000078151 00000 n -0000078335 00000 n -0000078639 00000 n -0000078783 00000 n -0000079144 00000 n -0000079188 00000 n -0000079377 00000 n -0000079640 00000 n -0000080073 00000 n -0000080225 00000 n -0000080766 00000 n -0000081056 00000 n -0000081697 00000 n -0000082114 00000 n -0000082536 00000 n -0000082938 00000 n -0000083368 00000 n -0000083772 00000 n -0000084092 00000 n -0000084626 00000 n -0000084965 00000 n -0000085242 00000 n -0000085666 00000 n -0000085914 00000 n -0000086292 00000 n -0000086621 00000 n -0000087033 00000 n -0000087462 00000 n -0000087881 00000 n -0000088180 00000 n -0000088577 00000 n -0000088855 00000 n -0000089183 00000 n -0000089574 00000 n -0000090089 00000 n -0000090563 00000 n -0000090902 00000 n -0000091251 00000 n -0000091593 00000 n -0000091793 00000 n -0000092006 00000 n -0000092444 00000 n -0000092792 00000 n -0000092836 00000 n -0000092943 00000 n -0000093455 00000 n -0000093607 00000 n -0000093805 00000 n -0000094228 00000 n -0000094671 00000 n -0000095092 00000 n -0000095366 00000 n -0000095826 00000 n -0000096306 00000 n -0000096769 00000 n -0000096910 00000 n -0000097250 00000 n -0000097294 00000 n -0000097421 00000 n -0000097683 00000 n -0000097835 00000 n -0000098322 00000 n -0000098819 00000 n -0000099210 00000 n -0000099631 00000 n -0000100104 00000 n -0000100642 00000 n -0000101146 00000 n -0000101499 00000 n -0000101995 00000 n -0000102347 00000 n -0000102931 00000 n -0000103380 00000 n -0000103801 00000 n -0000104319 00000 n -0000104695 00000 n -0000105071 00000 n -0000105421 00000 n -0000105896 00000 n -0000106281 00000 n -0000106731 00000 n -0000107004 00000 n -0000107302 00000 n -0000107346 00000 n -0000107410 00000 n -0000108029 00000 n -0000108560 00000 n -0000108613 00000 n -0000109417 00000 n -0000109461 00000 n -0000110157 00000 n -0000110761 00000 n -0000111247 00000 n -0000111920 00000 n -0000112470 00000 n -0000113187 00000 n -0000113769 00000 n -0000114360 00000 n -0000115099 00000 n -0000115530 00000 n -0000115969 00000 n -0000116522 00000 n -0000117099 00000 n -0000117621 00000 n -0000118244 00000 n -0000118882 00000 n -0000119312 00000 n -0000119928 00000 n -0000120580 00000 n -0000121229 00000 n -0000121806 00000 n -0000122410 00000 n -0000122834 00000 n -0000123468 00000 n -0000124073 00000 n -0000124774 00000 n -0000125403 00000 n -0000125797 00000 n -0000126228 00000 n -0000126867 00000 n -0000127338 00000 n -0000127779 00000 n -0000128326 00000 n -0000128848 00000 n -0000129503 00000 n -0000130120 00000 n -0000130546 00000 n -0000131174 00000 n -0000131699 00000 n -0000132268 00000 n -0000132751 00000 n -0000133326 00000 n -0000133867 00000 n -0000134510 00000 n -0000135126 00000 n -0000135680 00000 n -0000136218 00000 n -0000136721 00000 n -0000137166 00000 n -0000137856 00000 n -0000138331 00000 n -0000138896 00000 n -0000139427 00000 n -0000139909 00000 n -0000140448 00000 n -0000141027 00000 n -0000141578 00000 n -0000142118 00000 n -0000142826 00000 n -0000143424 00000 n -0000144028 00000 n -0000144623 00000 n -0000145167 00000 n -0000145578 00000 n -0000146036 00000 n -0000146568 00000 n -0000147018 00000 n -0000147526 00000 n -0000148040 00000 n -0000148445 00000 n -0000148959 00000 n -0000149534 00000 n -0000150134 00000 n -0000150697 00000 n -0000151279 00000 n -0000151933 00000 n -0000152554 00000 n -0000153029 00000 n -0000153490 00000 n -0000153987 00000 n -0000154479 00000 n -0000154998 00000 n -0000155658 00000 n -0000156437 00000 n -0000156995 00000 n -0000157467 00000 n -0000158196 00000 n -0000158837 00000 n -0000159294 00000 n -0000159910 00000 n -0000160597 00000 n -0000161204 00000 n -0000161701 00000 n -0000162229 00000 n -0000162759 00000 n -0000163349 00000 n -0000163918 00000 n -0000164674 00000 n -0000165195 00000 n -0000165752 00000 n -0000166359 00000 n -0000166778 00000 n -0000167310 00000 n -0000167911 00000 n -0000168508 00000 n -0000168944 00000 n -0000170556 00000 n -0000170886 00000 n -0000170930 00000 n -0000171054 00000 n -0000171331 00000 n -0000171482 00000 n -0000171855 00000 n -0000172177 00000 n -0000172601 00000 n -0000172887 00000 n -0000173291 00000 n -0000173490 00000 n -0000173707 00000 n -0000174099 00000 n -0000174490 00000 n -0000174669 00000 n -0000174983 00000 n -0000175027 00000 n -0000175104 00000 n -0000175597 00000 n -0000176141 00000 n -0000176617 00000 n -0000177024 00000 n -0000177108 00000 n -0000177407 00000 n -0000177451 00000 n -0000177515 00000 n -0000178111 00000 n -0000178387 00000 n -0000178440 00000 n -0000179584 00000 n -0000179628 00000 n -0000180769 00000 n -0000181368 00000 n -0000181919 00000 n -0000182445 00000 n -0000183162 00000 n -0000183738 00000 n -0000184355 00000 n -0000185088 00000 n -0000185686 00000 n -0000186284 00000 n -0000186708 00000 n -0000187263 00000 n -0000187791 00000 n -0000188449 00000 n -0000189026 00000 n -0000189444 00000 n -0000190157 00000 n -0000190676 00000 n -0000191272 00000 n -0000191911 00000 n -0000192544 00000 n -0000193072 00000 n -0000193504 00000 n -0000194114 00000 n -0000194706 00000 n -0000195327 00000 n -0000195976 00000 n -0000196613 00000 n -0000197275 00000 n -0000197855 00000 n -0000198230 00000 n -0000198834 00000 n -0000199478 00000 n -0000199873 00000 n -0000200681 00000 n -0000201155 00000 n -0000201765 00000 n -0000202326 00000 n -0000202942 00000 n -0000203457 00000 n -0000204031 00000 n -0000204716 00000 n -0000205384 00000 n -0000206031 00000 n -0000206645 00000 n -0000207049 00000 n -0000207466 00000 n -0000208116 00000 n -0000208589 00000 n -0000209015 00000 n -0000209501 00000 n -0000209929 00000 n -0000210375 00000 n -0000211044 00000 n -0000211711 00000 n -0000212165 00000 n -0000212717 00000 n -0000213309 00000 n -0000213791 00000 n -0000214410 00000 n -0000215090 00000 n -0000215613 00000 n -0000216311 00000 n -0000216973 00000 n -0000217381 00000 n -0000218009 00000 n -0000218633 00000 n -0000219146 00000 n -0000219571 00000 n -0000220178 00000 n -0000220754 00000 n -0000221312 00000 n -0000221801 00000 n -0000222376 00000 n -0000222921 00000 n -0000223449 00000 n -0000224024 00000 n -0000224611 00000 n -0000225088 00000 n -0000225769 00000 n -0000226314 00000 n -0000227090 00000 n -0000227587 00000 n -0000228108 00000 n -0000228619 00000 n -0000229058 00000 n -0000229650 00000 n -0000230335 00000 n -0000230792 00000 n -0000231349 00000 n -0000231890 00000 n -0000232403 00000 n -0000233013 00000 n -0000233542 00000 n -0000234263 00000 n -0000234845 00000 n -0000235394 00000 n -0000235997 00000 n -0000236532 00000 n -0000237005 00000 n -0000237715 00000 n -0000238390 00000 n -0000239020 00000 n -0000239612 00000 n -0000240197 00000 n -0000240782 00000 n -0000241414 00000 n -0000242014 00000 n -0000242529 00000 n -0000242931 00000 n -0000243547 00000 n -0000244069 00000 n -0000244589 00000 n -0000245028 00000 n -0000245536 00000 n -0000246064 00000 n -0000246453 00000 n -0000246960 00000 n -0000247660 00000 n -0000248238 00000 n -0000248821 00000 n -0000249445 00000 n -0000250010 00000 n -0000250561 00000 n -0000251059 00000 n -0000251664 00000 n -0000252279 00000 n -0000252990 00000 n -0000253463 00000 n -0000253938 00000 n -0000254601 00000 n -0000255048 00000 n -0000255654 00000 n -0000256404 00000 n -0000257076 00000 n -0000257622 00000 n -0000258103 00000 n -0000258590 00000 n -0000259333 00000 n -0000259865 00000 n -0000260378 00000 n -0000260925 00000 n -0000261428 00000 n -0000262081 00000 n -0000262835 00000 n -0000263624 00000 n -0000264189 00000 n -0000264642 00000 n -0000265066 00000 n -0000265801 00000 n -0000266368 00000 n -0000266851 00000 n -0000267319 00000 n -0000267956 00000 n -0000268585 00000 n -0000269117 00000 n -0000269671 00000 n -0000270123 00000 n -0000270859 00000 n -0000271499 00000 n -0000272127 00000 n -0000272624 00000 n -0000273138 00000 n -0000273672 00000 n -0000274031 00000 n -0000274618 00000 n -0000275189 00000 n -0000275773 00000 n -0000276479 00000 n -0000277004 00000 n -0000277568 00000 n -0000278157 00000 n -0000278568 00000 n -0000279108 00000 n -0000279711 00000 n -0000280312 00000 n -0000280729 00000 n -0000281362 00000 n -0000284067 00000 n -0000284453 00000 n -0000284497 00000 n -0000284732 00000 n -0000284972 00000 n -0000285411 00000 n -0000285918 00000 n -0000286209 00000 n -0000286351 00000 n -0000286865 00000 n -0000287273 00000 n -0000287682 00000 n -0000288079 00000 n -0000288504 00000 n -0000288906 00000 n -0000289209 00000 n -0000289715 00000 n -0000290050 00000 n -0000290315 00000 n -0000290550 00000 n -0000290941 00000 n -0000291261 00000 n -0000291725 00000 n -0000292131 00000 n -0000292552 00000 n -0000292839 00000 n -0000293236 00000 n -0000293508 00000 n -0000293815 00000 n -0000294205 00000 n -0000294626 00000 n -0000295110 00000 n -0000295557 00000 n -0000295752 00000 n -0000296006 00000 n -0000296423 00000 n -0000296811 00000 n -0000297198 00000 n -0000297475 00000 n -0000297950 00000 n -0000298192 00000 n -0000298628 00000 n -0000299141 00000 n -0000299655 00000 n -0000299941 00000 n -0000299985 00000 n -0000300324 00000 n -0000300360 00000 n -0000300779 00000 n -0000300823 00000 n -0000301021 00000 n -0000301499 00000 n -0000302041 00000 n -0000302580 00000 n -0000303002 00000 n -0000303474 00000 n -0000303968 00000 n -0000304430 00000 n -0000304722 00000 n -0000305091 00000 n -0000305681 00000 n -0000306082 00000 n -0000306375 00000 n -0000306792 00000 n -0000307229 00000 n -0000307819 00000 n -0000308132 00000 n -0000308440 00000 n -0000309022 00000 n -0000309514 00000 n -0000310015 00000 n -0000310506 00000 n -0000310942 00000 n -0000311447 00000 n -0000311896 00000 n -0000312281 00000 n -0000312646 00000 n -0000312690 00000 n -0000312863 00000 n -0000313079 00000 n -0000313231 00000 n -0000313426 00000 n -0000313815 00000 n -0000314171 00000 n -0000314528 00000 n -0000314900 00000 n -0000315315 00000 n -0000315595 00000 n -0000316314 00000 n -0000316512 00000 n -0000316757 00000 n -0000317115 00000 n -0000317306 00000 n -0000317626 00000 n -0000317898 00000 n -0000318250 00000 n -0000318608 00000 n -0000318854 00000 n -0000319237 00000 n -0000319525 00000 n -0000319719 00000 n -0000319893 00000 n -0000320224 00000 n -0000320551 00000 n -0000320885 00000 n -0000321181 00000 n -0000321225 00000 n -0000321303 00000 n -0000321450 00000 n -0000321662 00000 n -0000321724 00000 n -0000322045 00000 n -0000322089 00000 n -0000322960 00000 n -0000323843 00000 n -0000324341 00000 n -0000324821 00000 n -0000325629 00000 n -0000325732 00000 n -0000326056 00000 n -0000326100 00000 n -0000326202 00000 n -0000326691 00000 n -0000326838 00000 n -0000327157 00000 n -0000327625 00000 n -0000328057 00000 n -0000328489 00000 n -0000328947 00000 n -0000329067 00000 n -0000329352 00000 n -0000329396 00000 n -0000329628 00000 n -0000329675 00000 n -0000329989 00000 n -0000330033 00000 n -0000330113 00000 n -0000330611 00000 n -0000331182 00000 n -0000331418 00000 n -0000331996 00000 n -0000332083 00000 n -0000332426 00000 n -0000332470 00000 n -0000332992 00000 n -0000333346 00000 n -0000333861 00000 n -0000334350 00000 n -0000334864 00000 n -0000335343 00000 n -0000335583 00000 n -0000336104 00000 n -0000336537 00000 n -0000336704 00000 n -0000337030 00000 n -0000337074 00000 n -0000337166 00000 n -0000337383 00000 n -0000338148 00000 n -0000338923 00000 n -0000339370 00000 n -0000339797 00000 n -0000340526 00000 n -0000340643 00000 n -0000340986 00000 n -0000341030 00000 n -0000341174 00000 n -0000341593 00000 n -0000341739 00000 n -0000342131 00000 n -0000342505 00000 n -0000342939 00000 n -0000343159 00000 n -0000343561 00000 n -0000344042 00000 n -0000344352 00000 n -0000344748 00000 n -0000345193 00000 n -0000345683 00000 n -0000345879 00000 n -0000346249 00000 n -0000346704 00000 n -0000347118 00000 n -0000347588 00000 n -0000348000 00000 n -0000348448 00000 n -0000348893 00000 n -0000349429 00000 n -0000349739 00000 n -0000350151 00000 n -0000350529 00000 n -0000351051 00000 n -0000351549 00000 n -0000351996 00000 n -0000352459 00000 n -0000352894 00000 n -0000353151 00000 n -0000353296 00000 n -0000353457 00000 n -0000353616 00000 n -0000353779 00000 n -0000353942 00000 n -0000354103 00000 n -0000354265 00000 n -0000354423 00000 n -0000354579 00000 n -0000354739 00000 n -0000354900 00000 n -0000355060 00000 n -0000355219 00000 n -0000355376 00000 n -0000355538 00000 n -0000355701 00000 n -0000355952 00000 n -0000356237 00000 n -0000356281 00000 n -0000356432 00000 n -0000015516 00000 n -trailer -<<0A23106984CC4744A5D6734CD9BDC42E>]>> -startxref -0 -%%EOF - -5999 0 obj <>stream -xڬV}TWL!!> -D b )-l`Q|Vihik.SZgMA)ѶKBjSZ]g9޽{yo`% 4D!D\le:HxN>`:]k}t-ĵ]t0-{>^k. -ACyXnyÃ?gp#0Q^W|Ɨ7.O: { H*m#azg+[VRRyFigxWw?p[?:R{Q+L${PK_9 -T!u1yL8L/uEY`ZҿmxZ[;seQv'_=s>rhsW6~yɨNgi7/em:?zꝌo |?NT}75Tt-5uN~[;+p7/Jqr`2_m8|-1tڵgo %]}-sGܥN } oy'j~,m?d[uV6~ZPVG9{EY6K(C]$2?RU*UQ}mo/***"7!~ſ -+zV` o{zِi"Ce"x=Wqx3T҇kpŁ(!s6kwluGxGQkނ+:np )IpƳ)J-ȳr:HPQPۻjec./꧕r䔶ep2Z{5Ou.;WG'@@"tU -ރ;5TH81͆ ]"E:SwmÙzY0]760r:f;SU*K{`\@oiD؎gXT)iBˑL&d Ågx /걆fi|Cه햍d39P -(&<-9AN!)[|[h+iLWRF0Bu{uZ.ۂ\faxA Ff> -&T1H7۵jTCI3/8HY5܄+ӛccEP"u8@2#rvQ(٬(\wCFbs>Ò g;DQdmYYEQzc,6˭#&3lHq! dD\/nR' -hWN++h̰5سI& VZ ]ͩ^"|F1K6ld*е䓎\ַ36mGpA/hʗT:t ?S}ӕXkEŎXEP0ဲ")\zR' -+c)Wc(Xm3|L>Ǜ6lo`<;6,hYT{Vy 8t&PSf5䨘 LXj,\Ɖ$ GS~[ Q\# t ")>S"WQ(hr>C2"UvS,Y@@m&dJ,KVl%*d|~8is zB~ cQ5Nظh]$E3z8Rx)B9<'x:``yY>%Jw ,oҙs -ɀ#ٻb^@C/m\zmdՍnQ8Z= px)gh8l_K#X6gL`Hq.ȾHs*kBbdQ8*]93&P%E -g Qij'9 6K]oVsmٴ倍$`ϧ֙H9.t.Aȣj&`j)FӽŒW1;%yD`a*P#+$ #F/s< X> endobj 5241 0 obj <> endobj 5242 0 obj <>/Font<>/ProcSet[/PDF/Text]/ExtGState<>>> endobj 5243 0 obj <> endobj 5244 0 obj <> endobj 5245 0 obj <> endobj 5246 0 obj <> endobj 5247 0 obj <> endobj 5248 0 obj <> endobj 5249 0 obj <> endobj 5250 0 obj <> endobj 5251 0 obj <> endobj 5252 0 obj <> endobj 5253 0 obj <> endobj 5254 0 obj [/ICCBased 5296 0 R] endobj 5255 0 obj <>stream -Hd Tǿd2%MZgE" -H몬q d$ Dy# ),B4VWku[jĉg;a{sgfϽ|VA#"ãw*e* bQkޤcWw__z-յ -恟26] >Q` Hi@ @@68 -A9I0]@bH!eF+Rh%,MPg1:R$ӒbI4)rQa:Tb2U%EHdJJ,pu2)R2R*$b]-Vj$rRBH LJ.QdiE2F#"eED!IZdjW*Rr&+ڕt5)fUjTȅ3]-+R+gV+L-i[}sŝ+1CVNp2Ug6CݣHSUJ5cD3pwU4"Ig\ @P - @ 1Q VlݰiV?"c=L EbLdNutZGlWn#*BbcHUȪUܢ`bd2±nwu5-o̬ٿG1jmډukK]sgCx5dt}*uT>GN`Ϣpܑ>SNSЍn "N6G~iw8<2G⩣ñ"7#IQ`/}^V;7_Jf%cQ9A>OC/ &0:0A7SO/>뜇'4] ݿ>Bۭ51w)?C;Q Wm||kDPpY*C8a.dte\'CωI^Ձ?`%숧:PZ^KN S}}; Mf/k`9ߜ镣H{\惽KNg&0֊)PO}G$Ayɦ"DCN1r+L)G& -=p5AПƔdGщݱa 7JIO ᬉ?:.Qm[ )'Q{7.7fA,oI;sLZ+<%$U|~!7Ւٲ3-#^lnf͇[a^e -<>1VN)'^\Ϙ21.03Sdl|U 1 jAl-{M?wnT[u -s7J"SO.&+ -7\ ݩ0^݌/ϡ!mJ>//c6t5kT`9HK-PwI&>t ZƗ:.acg>⇻-6 ՒDВ Pb E!#3djG [rxC8ڃInwjMS]]g/VzѲrN'eZ r.uEO RTmΪ*k5%=33ƫnS묡+% ŸQuV 𖾄)<WWNN_h]|8Y:cAP@WbMrBy}s]JC_o1-9+ZeXy`nohWM1BRhu]F+AHJ 依ڎ3ߎJHLB$XJ&{>PoQ1(Jak29eaW3|}Cx@I$nCv'+1m; y݌jzcs3F) - BƂX$Ifk0Sۥ(u}[EO`P_k HNz3?5C )p?(̊3t#fa1JϾ9S2k" XHky1)s?_R/:l4Z{[Z'ga;&\o͹%rywQZ$S7 -{e  hbX*B ESG}^hEq 4bWLSncėjc EP) gmpCꝚ6k;Ϥ'nHj:z&.1M[rpiq䰚B.$;G(vfGKnS!s@`<.r*y5%cZ~0tAJ:&Wg߹ѧ6p_ke;>y ߍ`k*Q6. &B#&Oa%ѽ]?0| ~J~=SxZy-Dy)=`|(L*JqSrc8k3H6a9l!7!^S˺Ԩr-:i$ 9#KF^m#ٿ@W7?|=lJW`e뺜u ȄHXW\:T YwK4D"Qۍ\M):@|/rsqxTZS/o}=CȲqWvq`7pK9 F01E&okyy2oF$Ӫ&}n/;0AVKikVUVB)0afy|oJr^@a[ ܭM-> dQ" GDZBT*w`(.~rR)6Gf SX(UBmk)eqAD66@!PIu=S٬e`dsMܒACU u )_Jҋ|Ds -⤮dBy& )W40tdV8d-)[ho!<6Qߴ CA%CN.8 s4X\3k@x^"TUSS^iÁ'S_8V/Λc)K Obл 5obÜbgRb3;1m/i$(0$̀Y0B0oIHXVZ w^RsK=Q :c,OI'\L'L^}McӜW.v\DObf, -x]ܴtHFб!`Mϔ򍄑gd"v1 l'S|jw\Kԫ>o k PGQ}0Ȓ!SZY}NmI4r^5/pr{ =F#z[}-Z)! -}=OA9h~*^BL\S}vi#Xzmy3Ns[J eQ.# 0QQ'EΙwnT/6(wv*QQ́\[ETyUߩȊ}hN>ӇYr_5ҨvWr=btE|S p!F#}e=~D*)0ÿ*^nPbhvMr[ -k]FJ qL`ZX]sQ}ԦcNvR* GEPG 呐ח6[39CN2Y -ACK[t'eނ=}#㚤KҘq[`[ªU8k"Gֈhr3:,!)B3ƭz] uf1NhA BkGyJ]ZRzy - J*H)|ǢFeXܹ]ȶ,9_9߯^G)ՋLǙp@"ۏ%AYۼK>F` &ͫps @WΠ[iyr^|G]S+8uKsԠtWWw@5ټrV% Fu5kXe_홦s4PXMR`h0o9^jsХwi_tx^ #ց.S1P(>\|oʄRCQUO ]3}`%xLRǀU-aQKז$Az+ X'A* oY'$e]?M]* 4b ʛd]W]Y}GtYiIy\qdeIOE{IA.ME-wt{k3k9n9h86Hbs1 ~3&n٪g177LQ_.ވ$X5'2WaD L 1N#DJTt<7(%<(&s2<@ݰ7Oq -( `O=h9jRI4-:{אgD=G?3l=wnxFz:NOL LxJ}q5j*Ε`8 SQĊ84QR+jC3獁y]wΚQJmafJ,!5p3 @d ' EAH#hE~l[{`!-| H\Hgu(s+XM<^,K凔L3T sC߈q[$WCV%EjK3WC6{_Z_f^cUoS.9PU`5E8FzElךMUqz gQ:0[ -_=p۵2^(kRBc#z.Ӗh`SK投U?-#MI @(@qP;<,k6&6B>! -fzIEH0X5}(>"kMXhkq:lI aD)bɦ s+~i^'B[SCe YLU_"+閔 4"+ e&>VA 3-ݧU5w;#aX(Dz|H\ :#zOnGw;rp{]~GCYrJĥEb -/knl8݉D` -7ynW - -endstream endobj 5256 0 obj <> endobj 5257 0 obj <>stream -HTAo0 -S=BڰU7 eyT=>18?U d7;չ8)Cʎ=񽿴qfUUQ+^̣:?Ngft[OwqӪkek|o/NXO{/N?mٺη٩JZU64%>tJuTN މ&p!5)d-{bMҧ;/ =- I=xf1E>{O:7O;|ad;w!iax#ÜP?=P5z5! > endobj 5259 0 obj <> endobj 5260 0 obj <> endobj 5261 0 obj <> endobj 5262 0 obj <> endobj 5263 0 obj <> endobj 5264 0 obj <> endobj 5265 0 obj <> endobj 5266 0 obj <> endobj 5267 0 obj <> endobj 5268 0 obj <> endobj 5269 0 obj <>stream -HVn7ͽZ"ˣ&--`}pבy%~{rծC l.9<<3sfߝ -n;y1< "brpőx$+2<dM&l턓<{w%΂ g\j-thO,hU2"5.SrS_n]6":LX=:fϙ ];Wi]fPA?V -q"[9[go8Dta8g): DhG\LrRƽRd^& &Kd \LQ4⼏`eIh abP8b -<&~ HB^NEc5ǬCkGEc *\?:3}Uc3NyhrVpJ K^X^6+TVVHe8ƒ$bF5ci)ZM+L:D_@WDKՌAj.]hTȸLAjR̠јƶvF7F9MIC C>AVJAP7t`!1QtɌtJq:ZrzA5,&Q"d6(QTQ_iݾO Fy0񷁊%E߸_,{܉P k2~̳{r<喉BVsKv4]nh5ժl9iU@+E_+\f5]o[ f"n?Jfh-WQdfL$nm.H|4wbι)=HIJ1O -Y2Je{L{&n4FT _Ȟ3taEbc<#Tp%*rwwZQz"-}:TNjax@0 ?C׋g4Oӣe$ibf|8m>yph735:.Wg(x"2nACqVOnw]55ӺU1)j+ж -#01D:U&aTދWFtChR0<'Tkpv`K 4}˶b(J*nMްõy>~vz&I޳=Z 8Knh]$*F:v4}Zϼ@I]$ -jUSǹ}4Tw0.^&S_.t-HXLᤸ7 7^Ej!` 0dMdS.46(9ę5J7ۺɶ[!!J& Lr^Qw(f/ ٷüt>x/%7N9#Lc9^ʝ`Em1xP  Hu[B|$Nc1;$b$o+>#`!e '3uwMR>Vey j "':2u2xToK* -endstream endobj 5270 0 obj <> endobj 5271 0 obj <> endobj 5272 0 obj <> endobj 5273 0 obj <> endobj 5274 0 obj <> endobj 5275 0 obj <>stream -HVۖ4ۤ,JQ)ET'x$k!Ld -P~g~Iȱ=t-EY:g}ɯɏ#θC2ٌtYe+g,Y@&rCdd'C$uđ̧5L`.'Xye u8#߸ r%5K \g$CN=A1@½S A!w#wg!ApTK勤|={9I/-񣣏' +j+ Qm]8/ ˯%_EڵKm o֜of~8 -14@?<^s"+[rVA? Y -endstream endobj 5276 0 obj <> endobj 5277 0 obj <>stream -HWkOAE DcfKwy,VTl?~;kΙ3{:lBQ±{kWo&CL`&5Mrsjb7v :zC桇QR :u/ɰH!28, QG'ݣǾu{Յ[,+ɢiD!ôBEB@LHhzD -S07Jem{gVPr+a%f> endobj 5279 0 obj <> endobj 5280 0 obj <> endobj 5281 0 obj <>stream -HWn14AҖ@KqJەXX P@ZĚO=#I')HEf$_{NЋ;] -DݓRa(t$̈4>5H׌* D6ˎz -BrsN*9/O@0)VJi0Jy?7NWh^Zi~㘽hi@:DDZF1`. O tynA -"$#gzTԃbz|*H-i #nPsu-2|>l JBq5N[](\ !DzΒ~lL&0V :Z.y8c0p`nL"OW=n ?R=Y'sVWBjZC_E* -2 A黰+:(t8"KI6@ORܫ~Q Ϭ-cHU3Q|>stream -HV[OANLx) $IwWA[D"` K/{붻奻mgΙ|R ­ -^xYY )g!<IՊWy(3;c Q8 ID~an:׷a ^`RyDl2n Hn q \ (%-8S@VLr #vJIdh,Q|A@ Zie 䔩t58E>stream -HW[5F*U[ T!xANQ%^޼@aV)N](;3'l^s9wAhD cҠ Hnd C=AQTG-FY7`@0T -`nnk"7ǯEĈɵIbyj7[85ۄ}u2ۈ +F(e*L,pb5c4K7lZm8r7ril!H,*Rx9P<]JH1z PVYאIL?ELrĄ44+wYxe28(¢:\B4y~&n4bVq)r`"1aOh4l1bufȳ$`D\-3m- h&z+DI6UA5cߴߟɫɮ%;\(n.,|^f(o3 Mַ/?]EM6Qm" ϡ$]4T7>~ٓ\4J0xw7uu#2ޜc{Ml'nMCm+Uf5_຀U,~̎g;#VKB3ؤS_" -9X"ntH[˕Yҵ+ARR.DuBxW_s\fL>ACרRcP?v'*0M׷^yMoOڝPDt"} -YK/Q@n#ɠ%ۥ l_ gSQУnT"Q> endobj 5285 0 obj <> endobj 5286 0 obj <> endobj 5287 0 obj <> endobj 5288 0 obj <>stream -HTP1n0 - :HqNd$D;jJ 2<ܑrZr䙽0B2N~fp*ĵ+ٌ:Ln"-Zȯ4"/Toj ѐts8"EP4`rQe Uw8m5 u{BƵ77⾩T*"hfc̜̕,O >dO9dh - -endstream endobj 5289 0 obj <> endobj 5290 0 obj <> endobj 5291 0 obj <>stream -H2233s8bUDPc&r9yr+r{gPRTՓ+ ɒ -endstream endobj 5292 0 obj <> endobj 5293 0 obj <> endobj 5294 0 obj <>stream -HWIoE"!Y΅\U#OdP$9a#44ArFp@\XBX¾T4]=3ns{[}춋&\+>= -14xShΦyQ1$KاDaF"^oqbwӹĈ5Z^ךHT_((%Lx뛽u&cۼKsN^lUNė:joq5z:!ŻW纡h˽S}&4j \Ǜxi.8Qp04~R0N>)̛ -a'i񃰒0=ct Ee8kӠ'@J`;Fε5F$[`Y:PM1oRWE%F3$j'r+J0C,^ bNeh_ o <ޞY}neҒ{-ҫr %i -xލCt.˼#9[Z;aesqkK$~6*̉ʶ(A7[b\ZCQz_8I_e'KFuW_vſ@Iz w|E6)X/gcxB̎JQ -r\L cE'Dg{i%JR[QaƇ錔 \ -Sm斋sXdN93T4R5%8u9?_L- 8~q TP2}4jΡM5k$y|o/H\hv :ˤ%okܑWInfLϿ\r` 8SCj4u^'VfE.h?DhHoWjdwtIg,}k~Mݮ.`]y[L=]::i*R‘y`8Q 3*6X8쯿t!bx= (=Χ,.:RDgH>Z֛Y_ ٹ թ4L~z+|U_p -endstream endobj 5295 0 obj <>stream -HVnEupB`LBdGHzѕz? EY,F"Ja2fl+c &cVUw_c^uWSsV!R¨ը?hWɭ{}C;=N6QG}$FI1ḉ9+]k&mL`R 3L$QŠ}fR)͹[44%jXer(H#9JQ>:8C)(k)7bz)VE"fSr'?Gtb~9_E*LU1xg~,wHI2w pxk3^Mris1GQ,s_ink5kw_`"ע~=~rRw֪ -HmVwy8XəEȥ3;鐓*d Z$ 1 8y殬VYBG8ͬ*o*Ոd_i9Xwh6^?$`fg:B5AY)/0(tf69q -Njn3[dhi*ugϼTAeaK7s c$?`c 4FIP ^&7h7|\#t0nO$N&>tk5\AmhA]3sq)/O /4 W$(ynn3Xn2} ⫋s-b^<7GD]h Wrhs G0pttw+$Ln~0U$4[&p2w\zwz/* -endstream endobj 5296 0 obj <>stream -HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  - 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 -V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= -x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- -ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 -N')].uJr - wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 -n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! -zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km - -endstream endobj 5297 0 obj <>stream -HT=o0 -:8ȵRBU*\0(pvB@8k; l\mq7o8 Ѭ.fjH*n.ˊSm_&w-Wߡ@Iٜ - %t Y=dC>ݭt%c8"f& 4h -H)bbf)ܻ^Z%*v. -AEaFUlՊn?_:8yO3 &3-^͎Gğ`t - -endstream endobj 5298 0 obj <> endobj 5299 0 obj <> endobj 5300 0 obj <>stream -HO0$p?dV$OM^޿.|{{;!aP 9-5u?ލ(" G(ه l -#Gt"xI{KD D9QH '2SـH՗`p Da$D#/`c `#1YHj*ЏȆ#X}PяHla#@=GQ_aVN'0u1Ph'r`b*F[H -D ڕ5z̿%-vwu4@/1Hq)3pR[)2H 0Nf ج hفh'05䱃d:S [5.5Sb#rt5=@|!L؁o!5ώ951Lo[E-q4Ubt\]o.66 fKԣy 0u^ -endstream endobj 5301 0 obj <>stream -H=N0`W*y mS`9#x`$SAE[^~~,,VzCbB4 Hj% ǃ3z1 @?q 88(!/CP}v*BnDu`Ѐ>stream -H=N0T H^zvvT@"L@07GQr;Ry,=n!;jV?؍vPF1d%dXCJHuY:eN%QhG~e}*H8r47O(@vwflnN:󎦖u7]26$h7)\kv3} '.=`fr -endstream endobj 5303 0 obj <>stream -H223 3134)\\P1X.2LrW0N W4Ӆ?'W @˛ -endstream endobj 5304 0 obj <>stream -H;0i0FG.ےW%`q I :#T!p^26eXi?~{Wf{(7v>U}oуTN[%ssTU0ltrv{~wy~/-A&n0;&/tA`bIX7vZZ 1Y ]@(NaO[+ Qmԣh#u;q0U^9'1<]FoP!5ip4)wo_CJM ꇘoT?mAXd)V,ڏT!6# ɋкI3p: \$pwC9}#pvz(~< 쳰 -/9P8(^|L+$ oji: eSLj*9 ݖ̟)4S`s݉XMO;p }l*Am^lr/BK -pqu6XL^\,<>RQB/d;TB$B5"B[4DQ]DIQ"-e`RaRjM8}IQ+w * 5ZjC !1d?̴ l凳" -0:{9 -endstream endobj 5305 0 obj <>stream -HΔ0K8# rS`'@=z_G#ȁPL1.vۮ/R]z.=o*ʺKF~zqvu&[VME>Cl<={Y6t[~㳼1 !nt^(Ua+Wċ$2PѾ0PWHA,EK7{1//h#㚉gkV8G  &) =$@`i<pr! f^rA{a5EWPŭ -+@P k'(D}ey_נ0r?Mng -#W0<ˡBHNsimJ߸!Jvpke`cV4iW\P(@=@2w."ECP,-1b!C 0y؊):3nSxpÃ<0ʭר _i=~ЧP'ͻ9;U0㔆u OV5u{KҎs=!_{[ -endstream endobj 5306 0 obj <>stream -H;N0ЉRDrn_|*)@ (9BJQ@E iOlH6*-ERTlÖ?썉"-joԜFob/l+;6,{nyW,;_ex|a5;6A{m ܠQ6NL!ҖOAq*Yv)$ei5$ w-e0FBIC%c{_L;+V=p=\xX.'89Gސ8<>>yRwOպ g5Z173rGǫyG:$d;c]Tr.B*s*$:D0ReOT/O:wүIh'CY)4N݁SL訐aK>stream -HMN0eQɛ!kH$@,@nn#xE㙱 *UH$7oҾ6jV\۬u5P5|f-xi7e[; <mQk.EV}<uXL41[DV=ֈG&F:FHĢIڢqhpEAlB#C5C-buZ2:_Ɨ'xO^bQ8_17&pu%0rloD5{WK=[!>f*D/ 1|` 4$I%d^S!0>"Rq2.* ;bN眛:)~s9=_ )$ -endstream endobj 5308 0 obj <>stream -HN@ K8̅G`^@vv==уFᛐ@vl3ɺ& -CR"ELuIM4V|wx*5yeV('t y ;b- lNe&ݥ|~z3jO[B5=Z;4@ny~=GUIqGO Bl}D, !{Oi\z3*Dlͥnϕ -e cnmK`ܱN:}*ܻ݉ ? -endstream endobj 5309 0 obj <>stream -HMN0t3G^@ "5h߃BK 5-ku]7*UA"z;q'4ovy.d_VT%>}P^mp:%ꬻ$O/;Ā^$G(2r h 6mB>HNs!KB* ͘d+pc-9ܝ<9،Z6kcd+"4b~3xZq - =6ți=ON-7<;9>`.:r䌼Cf֓^o{M -endstream endobj 5310 0 obj <>stream -HֱN0^.#C LSLtr5QG&&ǣ(<cBm{_3:溴?ZM>!IB/qC3QЍ%aXO D8xqaVo n]zM#]4aB4`"i:eF%{y~}UJu+5nmX#Qb֘Qb6bG%1z6=f{TvteMU{ua@ݬ{S45eX5oF3 Do޾n3@`rň$I-d+,Fwg|N.c(T3B"Hj _8ml4JΆ""=/5`3 -endstream endobj 5311 0 obj <>stream -H1N0Џ#_vb%*ȀF|(Nڦ@%&"?ٱ-[עTUHQː6bͬ ?gƩT{2kRbq{A -^ߟD3`?d?b:}=.4,;l¥-2Mr ;]}:Kl&.pMl&16ő]Yf%Or99˴+ๆK;`t~* -endstream endobj 5312 0 obj <>stream -H=n0p#JoxP` -#h%G`̀b^A8AlgOe8V6F4M-~$ǒ -Vr-aj̍NN@DRҬ*?[f}}4rl98IbS\yyJܹc<+5%k:kB1|C:؁)f>ڹ >>3XUp; -endstream endobj 5313 0 obj <>stream -HJ0ǧ졐Ky؃'@= -* -(}=P',<4;d&3)dZ&!Dl]ʣ|UZ"d+Kœ(GV㺜]5qRFE" &K\Tե|y~ՙ8JC|@@!;@͸ - i(C#F_Ca5JbeLª WvO6\iCb -=++wiG W12'6j8`wL,0ϖS}G޼cH^fZ3FL3m| %Kk:L,BGg!8zHz!}`gb]`*aqøg< c}aZ<8LgoMt'@S[I;/pVml,)bV 畸 " -endstream endobj 5314 0 obj <>stream -H=N@߆d\@`veIVM0FkW(cpJ\ +wy,J6lDG:ʋ:ozPYOJٻԕ.sܫmkmZ$+o/OQqyTy&>stream -HܔN0u%j\)R H09}<!c'"uđjw+\e*xe&/8!r.s&ބJ%|+UV.R9|zZ$2KKܚi+DRɏQ_q1Nb#Ph "ɵ5^ȅU`.ja','.1hp]Ƒh`Z:0m?lIPVKeL!P䑭#m>stream -H1N@L 2,Xmj"V@--4Zbx9 2oܘ;Ix ^G*͕J.4E"u,)d\W2+@}ӫ^ވM) kD"ޜD婼{呰־z݄(gmO ZB!j&쀅XCkkΉ Y e,âIBU; zV6xN6H!T;~QQܸZ9a+ۢz>.U;e/_R?ݶZ;`i1MNo$Kq.{L -endstream endobj 5317 0 obj <>stream -Ht=N0&G/@gH H@%h9!Q̌MXj?of<ϓITѨR*' VޔME Ku[&>E˫]1ʪ' ^^E"J2SHO2i(~E'1]Ș:D=a_" `d،h E`bAG، @8Z8 k"Ⱦ# 3[l`rAo| s0yO:qg-d%U/ap4GR^`"LjLԌ#1E -cT\1#J!RMԨv0\EЗPcї0l!0[Â[WYFs<\l;tkk~xnG  Mvn(Hs vCY -0L\ -endstream endobj 5318 0 obj <>stream -H=N0A+MGX_'^'Q\EZ@"T()@Po)]D1HBti?.>stream -H;N0` ;) -Hd@#$h$dc͘!K@Pv?<2K(Ȋ,~Vp*w oP%x揘ӾeKzMTB 0 -endstream endobj 5320 0 obj <>stream -HO0$ v$OM}w'HzYWDuw_5FR2緋c!}Tm4RRfZքP/%uRHhHNjUɽe!_h*"uvUSae^k ^HR 9%Kq* +wϺ+'% =P0+d^)[jʽ (ЪLL L0$.%Lа(e>L@%S_B"HRQPmLB!ah)])bʁLT4m3AaCzPvխ+Sr]c ȦDb]! A> endobj 5322 0 obj <>stream -HTP=o 7q,C?Ԥ ]#Ruݽ{Qѝ:kw?3i -ኃJF-Q:;O4~ -ƞGo^7v+Ź(s*^{#ͺ?:2l4N*MQ5R^}KOn+12._DŽwiZ W~NxQFY#Qp==徉ZKUnr_Hw_ - -endstream endobj 5323 0 obj <> endobj 5324 0 obj <> endobj 5325 0 obj <>stream -H1N0`W"GȻ@I2S`БT愉kqz1D~u# ѢCY,?qa'G p?H0Xg9\~ -endstream endobj 5326 0 obj <>stream -Hl1 -@DGR ͚ DB:ͣ)-PόNNhl8et$b=qRuҒrjH-9MIkRveOvND"rE-O0;ЗzrgP|Y>o!* 7-&7uhaiC/ Fc -endstream endobj 5327 0 obj <>stream -H0@d#$/--H H􀴜x#G|$8]9WgNa;\ |~χ]?e7k67/u|;|#;b l 4cg̭$^8VX 8[ԜmA ,}PPkY)¢\q4[aJiVR%UB_[.t4d7APe E7ٗ߈= oW? Y9S R"MON3Fh%k+ GfYz\ P(5 QDrJNJ(Jg# ߼LX"9ڧ6HӺ". @u*t"M^tD -fR'9)lp#aC${4&DtOMxnc*"zMME1sTV(.{ c>wx9 -endstream endobj 5328 0 obj <>stream -HMN0` n8#dfE2j" ]yuBk8G,YL@HtLfїLh>^)"Q]l:dv7o)N^WHH撄1 W0/$&( -?&wywdP` ΅~n4dyŴ -1zEZQ`T*yLiZM9AS - -Pǔ>,.e[Goy8 OJLdg^JL.U"W8*/xnXՉBhY&=M @KBT(B{֊v4,lA# =?dWүi%KΉMFȗ -^' -endstream endobj 5329 0 obj <>stream -H=n0`p v&i CdZx4Gਁ GI4 P m=I|Ov˶Î]Ӊҟtcnp>?͑nvkx~?S[TvBϢԽ:q2&%"OGIQWtT$ "& @ "F$UD5"h'ϤuDM&5ONz~}cMT$,C ˛:2TxA\GiYPx4P@q!L~D*i,x$1GH"=\*4RϦzy kzn;KZVr&[t`ԉ%"*b5C.Rcsy jFJm7"d5B%'5"0w1MGq1栁XgSʽR)7G@"2~PFaE2(HŐH@ ,HlH؁ |\BJˑӿ > -endstream endobj 5330 0 obj <>stream -HN0$ ̍d;LM`'&Doƣ{@~?:D|X_-Yd֧=Dn+ɱ'ME5]\(b{}Oz^izN^ 铲`3[Lvm8m6L}qڈӂ1V[fv!+deKz; Va~*&tkfK:Vò}N@k`o$J~TTy%&= R -5;u)cfzX-q=梄 ka`eFM -8cSbq W+[ b1'e{b(ɖ*|Ԅ3c5:B̊sE0Si>6 D2fhh#m0|d2h:͟F/RzCmw1 -endstream endobj 5331 0 obj <>stream -H1 -1Јo$UV@--]LQ4# b!C*gH0M*g!j~YMB%O= ! -LjZA#OsaU %̎xʿLTT~/9s5  -endstream endobj 5332 0 obj <> endobj 5333 0 obj <>stream -HTP0 A7-kUKD9%Չt_+$?=[CK6<-ͬ8XcuXzTdwplwPB^p -ZV !2[$4`rQ2uUȊ(?@2gOŽY}6"lRc^ sdn _O'TW3h - -endstream endobj 5334 0 obj <> endobj 5335 0 obj <> endobj 5336 0 obj <>stream -HN0*\N0<&4EO.YF0qCύm7ZC[LL^9B9{q - >"̭Yv^e4NН?r~W~:]&[>X -endstream endobj 5337 0 obj <> endobj 5338 0 obj <>stream -HTn0 Ew}Hlǀi }I;eAv}IMѕxA&3$oa4 8`Nxd9.zH|N3{׍Jp!5X ;$pt34`S/퀐Dߟxz<hqк3tU7?SY*SgڠjҢzoQxG\`o!ނe+*r-\7- ֿy$ιXn%W,+R$XL# D-s˭9/v;׏Ɵ`T - -endstream endobj 5339 0 obj <> endobj 5340 0 obj <> endobj 5341 0 obj <>stream -H; -@E#)||2o~B* PK Ek,Ew`d4b==Jz&gF9Q:f,YH[p-aW -EILx_p*Q3$ZQs<H,%ݠѓkvڼɓa3jOm[i%<3 -endstream endobj 5342 0 obj <>stream -H=n0(pa6-"%UL(!'U8 -G@Kg6[D[l~f<0BFҭHDi%KqUHډ]%H#/),,I%&YvQR -㋂vox$d;ʌkp/>?^z1oc,00emZEl@#ʠ"(5$c5I#k)?&!UAڃJMIz;b:lפ2&ڨ 5DiVD_RMFCX…B"O)MEX4fq3=Nht~Eg>}ޥKފXӳСSz&`scMBv}Kjr4hvl-pfUUB3R@EC&M/(jHf׳~{WJ -endstream endobj 5343 0 obj <>stream -H;0 Jl>/@$2 G D'6O~ 6v2 4zFNZD.%#DJZw^q~9_#Q?lHo ɣ571~۰*_D -endstream endobj 5344 0 obj <>stream -H10 NI;!ȀF"8?X'o3 'O(1" >e% ~HgU\Gwgjٿctb,pG -%nܯHʎe{ijjji¦Ab -endstream endobj 5345 0 obj <>stream -H223740Q0@=3#C8bUB]$ɥ`ȥ"ABIQi* .WO@.q -endstream endobj 5346 0 obj <>stream -HtMJ0 - k5 -BЕP. r!.Ķ1)D|[&OnJQIY5m1MWuRu5ӇU:%Q~8EJ>>stream -H;N0`G)2TH()@PgG(Xalmt;'3cMUV\z79_WZ&u6_4SY\-se>}ZGo(q(gʶ7)땺[h#) -#@{݃O>hM>x7v?C};{:14l,f/jnvq NGv;t -0*m -endstream endobj 5348 0 obj <>stream -H=0Ǎ(p|&+%`rpO㱇$Yr}+E%rfƺBev 격ڡj;YFvUob*ݖ(F݉~'1 _|5\'~ƒ?XQfn7Ogd>9uEYސg_'Z 2],h-'-2踀NR[v3 aOyp5F^ؤa37'c |N*RX1:#KHׅFםl֛SXߍ@ȊJn8քg7yz@y~ggwm5pЭr -e]a_J8e-nܕ7:)5?yrvMs Zx[H ?QJw#W`/o -endstream endobj 5349 0 obj <>stream -HĒ1N0Eg"!Y$٤ZiHPqAt\+G\b<;DB:?TUQ*Y7\eYRWhA). %FG^XUr 6TMH-xfy#{ĺKf] ;Ka26pq xBJCZHQ[;AŚ(&H9 Hh\zٌδ_[3r*t8Kj5h?ftPOOMHɖ$["68LolJmS :ۏ [i7Qg-iȐ\s5u $z6 ]ut![蹋 !~ $?fiDɄَ0`Bidti~6$ ,BIL8\Į:v>f -endstream endobj 5350 0 obj <>stream -H;n@`!`ŧHUq. FRG#,YZczDITe #߲,&1yYRuPQ.*qRyaS2NJ J̺*dw.AQ/|Vw{fRm>4Ifxv!8A[;!5LR!LQ42%> Gc-h{NLX35\GoK1YB L0 V>stream -HαN0 `G*y &Mp02`бCuN98!fI{[WZrlg:o؆645խmVuey8y9_}@:-O'\XS㰼& b,z}y{ĥN, K@ fř`RQ L&A1-(^u!m\S-qPr8R/+`NGC.s -endstream endobj 5352 0 obj <>stream -Ȟ0 Dz_c۬f@F|?$b+wӝ}όmL$Q}'+dzҹ5-g)RtŔ0jBkRyA`kQK2D<4 -endstream endobj 5353 0 obj <>stream -H;N0/PKP_.kT@"L=02`Np"p]8_U[cM"wtYQ.|q]YY:[U-,=y~Votzsofxc[`>?^u{!_pat@Ɯ1|JC*3 0(à 2 0(à 28C84d!pgAHqN$ B2 g5C] ,./Fs=V~uzh-~7Yvl:CmoFg1)Ogh+Tt9q -p28k` -endstream endobj 5354 0 obj <>stream -HLKAAd[ jo>$f!aXZo&PTb18FZx%(jеjٿKk'jY.w.B"Ճ?> g -endstream endobj 5355 0 obj <>stream -HϱN0:Db'L -Hd@ `NG蘡w)$v`}ctMRgM.){ϝ\{mp?st'>>stream -H̓R0\q3ixBT̜:#3Zh(< M\ - -?~diVZJ]CM&uQE\=&2mꪱh&]eM^屓IUTev|VFfݽx|ݍ\IH?yh0;Q䋨RD &i'L3n48 ne fMr I"ZvD= 7i }Lm@H'tcA[h?_q9ĺ -mo[1[fbGKa7Ʈc';ٱbv6;`)=KUOm') -endstream endobj 5357 0 obj <>stream -H=N1 4 kHlJ -mRneqR$zd+_6O%8EO'jf -gg]LO_1BtO줸)V73VKX5l󏿃/gA++.)BOPu?L`d-X pf :'&l -l N!ǪBkKۂ^'eGnd٩ -endstream endobj 5358 0 obj <>stream -Hl=n0p#H^8@#R*SvЪh#026~_I*,)\cɓ:ό'i]{礩夔,^VVx>饣WVx|{b?߿$rHO#if4XI~fHIwZzҀ”Hl -AۼaHHS y/3d[`٫W-Li\,EŲOmOmӢj;J UIhk&HH@X#a +Қv+WF t#! w !&$(^ xቱ9!0@yNP[EVUk[]$-9'?B -endstream endobj 5359 0 obj <>stream -H1 -B1D9^ '_SZy7bx3cj) &LWBHCM1!VnBe7+=- ]<#Hq +5 -endstream endobj 5360 0 obj <> endobj 5361 0 obj <>stream -HTP1n0 - :Nd1<4]<4 ⴻ,Ѯhy#)n> endobj 5363 0 obj <>stream -H22ճ446W0P0ӳ4320V532105T03651 -YX*rr[T5("L s<Áq{IgC.}O_T.O??0z b`GCq2b.WO@.v0_ -endstream endobj 5364 0 obj <> endobj 5365 0 obj <>stream -HTPr -ɤ%<&@1c>ˤĮЊޚ/jl^!q2 -Qaҭf逓xގ =_T>>߼FoD -7huqd"ݫxOWPܿ^4N*N*:k<,3ap]&L&c -On+HjlP3Ppfr*.Pmޓd13oKw^a? R - -endstream endobj 5366 0 obj <> endobj 5367 0 obj <> endobj 5368 0 obj <>stream -H|=N0qG"y 6PL -Ht@# ؚ(>BFQ?=ۉ_i TզRUW7rwinvjts}`EI[&oWC)2 yMO++#Ŋyɑ?[ Q0D"ɰfl/kLȹtaE缸. ~oV7 Z@ ϺoзvnŒ]@s}`MVu3vͥl ch33vdb=FlN: ]Ҩº>stream -H22׳00U0@=S3S8bUXU"`\.'O.pC.} <}JJS<]szrr -endstream endobj 5370 0 obj <>stream -HԱ0AHnH\K9]jx4G@Kfԅ;ڪ1w.6'cdl^_qE ^su2]OuQo9ubV~4:\Imx̐l|1G$+ r'O ~߼ -5# )F?2T6y9ޯ̔f!Mo[O ;GWvnk'B S&| [X|9FB9GJn<*&yeu" gAN8q4.uw1AofXwy5rPwI|B੹,h~JD_C"-.{*O,g[[Z!}Դ%{6YR2IslH'zT# -endstream endobj 5371 0 obj <>stream -H씱N0PK!~ҺdT@"L<02`N-GȘ!J9Q+P|qudbcF&&NQNwI&2ՉT&fIWQJdcQbN3޸43Er~i,7RqJ[,+0(FX9lhbm1d,#,Za-! k"\!ˁ2l.U˴BshT`0PBjĽy]i=xdgU0 IN)ɾ@˱Y3g'dlf6d;۴;\a<J >.u^\^| -0m -endstream endobj 5372 0 obj <>stream -Hܓ;N0Ja}~8J H@j Y"9{<HHWx:K, -2EW.,FT]Mۊ<ɺ9UtYޔ҆dֹw%Ɨ(du<=]x{}7lg0Ɛأ-p\ϡGhb'lM@ɅM9.XGH 5|! dqMi[m!@8C#`3'đ~YT*-\̇YќHq2_ ua ?d11^#_aL6ʭɣ]$j16dɮ] e6c?UsHq0mw#u[%0 -endstream endobj 5373 0 obj <>stream -H˱jA1[n^=O+(xE V>LhvdCE!e -`lАf.:][/\FrxtFtB eZc9Bf>XZoṈ T`v \7@͓Q̱&ᚊ -&ZM;Bg%N,;f -endstream endobj 5374 0 obj <>stream -Hd=n@Q ml$R\DJ I"QRG([R (F237-u(LnJOޚgUּl}K+)ۘ}<9X}0ߏOfGOwzQ3]6R*B=3J=LH1QCŽipmcF88 -^xlu OPہ -["x ;zB Z".%B3ɡH(lֳ8c<&I[)e!D=mVF|V2 i:!'4y@#L؞ |EW s'R$?RK@x7F~$юvBI>.*ɗ8` iJBQ,i -[ w}O %L -endstream endobj 5375 0 obj <>stream -H;n1`HxׯY% e $Rq$%p\)GPnZc*Q@b$Kֵ -$ 35ɺvҨ -:ূ h'!R)QN#㵥}J}a@BKP2+*(_ x~"ӱgv^o|IbĊ)y1_ٖüw0b -endstream endobj 5376 0 obj <>stream -H22׳00U0P37164Wе343R03062ʥrrAā* P΃3)KrW04N -\ -%E\.\ ".WO@.Jd -endstream endobj 5377 0 obj <>stream -H?N02TzKw;L -Hd@j'@;2`n3T>D;-m\,b/6oZncpUS֭]Y y򼭼mk ❷W=gR6=J(g?{>qH<͜pS"H( -ٽ Np4H" " Dz2E{KpFh{3R?%?o -endstream endobj 5378 0 obj <> endobj 5379 0 obj <>stream -HTQn -;NB.4.P|IaX1vqR;0yj̸sjq~42oN!t8 v5I uYqjL?SrY.iuhbO"oЬ@UƞY9!#/Ws5.V*t "K*'cyTt$! 2` -zů6 cګrCD)Dtؼx$R0'ߝ8пƛ|sFmΑi'5ޔl~oG - -endstream endobj 5380 0 obj <> endobj 5381 0 obj <> endobj 5382 0 obj <>stream -H<=@%$S ._ j"V@--4ZG(0$|{*2D}@ѧQǚBfY˼n #Zmq;B uچkM!pm& 2DpkT -endstream endobj 5383 0 obj <>stream -H1N0E'"=$"-  *[RN.SX1;'+рc3Z)\_!$kޙ-nCΜ^\5ycǖx+V[~Y\ gYy!k18& XkV垞;,Lq=›k`|呰&k(xpZpFpm L}&O/# -_ A E,SއX9 . QH]>FAEg a YD IP'+-9#a/zcMkTz9܀?. aHZľ` -endstream endobj 5384 0 obj <>stream -H223434T0@=3SC38bUU"`\.'O.pC.} <}JJS<]szrr -endstream endobj 5385 0 obj <>stream -H|=n0Gp ^ $nVq )dkqEJW -:J'3$Gvq $yy2)ymRS%EU.EaERu,,7zQ,rΨ&%" +5iZMoMeye25]ݙOju4?v,@k=Lv%m?^%eZяd/Un NA!}vp +ܦ4"`ywm'e#ʰ!lˡ: 9o6^E$־NԽw,>zXʾSޱ?fN&h@qdI+V>$n6,=@N 9 #m\D]!f뒡?uЭuPtS 4;?dL|49w^^ڽp\ňfԇ` -endstream endobj 5386 0 obj <>stream -H1 -0-=Iv<::(:ެG;5V(?}irJV8ԚI%@XDVfix'ݩTGfY9 -C-'( x:wOG跤^ږcEرPc-->stream -HM0p,/<*qp&a dSi+M:9@e5,@~؍IS#vN~8V宬6]}_=6;:T]kS7zN?}TS9_3߾UۧeŷO察部.R\$΅Rj2'CX(a31S%IpJc#&4'(Sf]9#*q,UMl]!$SǦZ!3E"w+mQRiId&I(5Эd0 HҜ"b5iHqv`a#"֛fbJH4"# M`bMÐdoOP9mCos'ŤɶDcH#"ZDDb# z"HYL=k=c-k=nKj3O#;,$р^3w^1Ocr%e'"XHS -OSZ_hv:t*1)e;5"¦$aDD5Y@fp n g0C4 -endstream endobj 5388 0 obj <>stream -HұN0+"yHMB`+ͧ?'x`4RrYbUREUϕ]W|u#7x]JWݭ|xށt9:jPF4[ cx?h9P#CGD4;2II' A <  64,:dQ%ԁ.z>R&=ceiVͬz;̀)ǼIӸ/0pG ڰ1I`1o'a -endstream endobj 5389 0 obj <>stream -HӱN0Ы:DO$NڄNA$2 щ S 3T1w>ZEB"Y/ّ.,)|q*3OeRV\ydKTI̊\>(",y>ObՈVVHRHW7Ro֚o/=A&Ha AV6h3*^=L: -Z 0/8}'G3&EwRNS*JD 8фe^Ֆ>CҰvTTdPXo5W,o8S5wzm]!Z!7WT}굎:Ic,Ե5DNL;P(R{xh5fe#W$Q -endstream endobj 5390 0 obj <>stream -HN@P\ {pT$&Rhu'se0F#͗0;1j+]\(;-L FBI;W`HSwj:ȫwb߉ ȮdmD9Zdݵ|y~}݅@D! @6L rka#E68CBJ$i@={" (_i{Q;9'hoᏄr >'=pi?Oz˷ƱwJ//zhLР9lH[F? -endstream endobj 5391 0 obj <>stream -HӽN0>stream -HN0 }P)K!yڦJHt@@0oF%бCUc$vΥk[LbUKlZ0G[mWHS&8+%A^KsfSٕ8o΍UYsm^_TsGg=@3}C"5bq`o)$&K#+#Iys>:0ڃF-M"y|VB}"|T{&DRhh$0ȳk\ ! W#GO2xs)>stream -H;n@AH xTHN,RRvQ8%fG"]CiFHhu)T)=$iPt -cM'H{Z*nJ(G -/X5;Kl -`-HpFq$7 6f_Xly٪Nl_Lr-o_WR];h- iI/ LGd% -endstream endobj 5394 0 obj <>stream -H=0`! ^`eQ+ILZ -\ZMx*MlRFOʦLS˶5lkj[֧ͣOJڬ'T?֞^T51YᅳC:X?2/ϯFDxmE@E 2 -!GI2Y*r`dp,B/@98!g (nV㓀RkdШ`!3==yDx|&feAŚp~LΖɼVq Lr dָ+\A䋼t@y+. M@J>''f{Qxf\qT[pY,Re'Ȱ#BZ_ -̳>oQ8=[xE}j}N։?{^ G8HǀTp_o ^t -endstream endobj 5395 0 obj <>stream -HJ@)9G辀I6Mz -VsUh= -* -M|>stream -Hѻ 0P# -k[S!H%j dFHIb"Ppœ|W<UDM"#(QLYԆH&7 -PBu!y.]AXa`@PDx8m!=kCQ+}-ׯhs[,Q3r游 [67G>d~}~]164%< -endstream endobj 5397 0 obj <>stream -HMN0 # gɨ,Lt5P.4Yo#dA@:M,'-۟oҲ(6;d{UbOu]rs|ŋڡUZJ*YZeٍNdKoO eX=44z>? u?4nt& X aa9#h97tCh12 ;Sۓ_d2,A=@=i?B@H8D! 'Y'r!'|wE C|N!N77,%b^.ՐUO&i Nl&mBWZ14%[q/L5 -endstream endobj 5398 0 obj <>stream -H=AAN -s쾭$-$TRAqnTXBpIx ,)*x )N˅5-LPh]|u%)rY(gd-iU&}(㘶S:'w9ej撩39kϯQt+@88Żn -endstream endobj 5399 0 obj <>stream -HMJ@K&GH_dZ3qQ0 AWnԥEعWQr,RVUz./~re\â̊-qˬXpgN1[Ȱ5Zq'7b^_ϑh4D4F$Uh?TY#@J,f K,YT=@$YT*HEC*^|g Cno)Wc"gT\5)O(n%x7[9E\uo1 ^7>xߨb0+vO;YNY%xQ ~ 0g -endstream endobj 5400 0 obj <>stream -HKJ@Ȣ69B&阇Yʭ.b&^%G#d9 IYՑ/ !WNU~QT6gӲraZ7MYG}N4-;.mPvfűuub=u {(& hE#E=-@E>H`&yT<%kܷ]i:^h+U¼ժp񯿡bUߌX(D:IHdaES ̖ -endstream endobj 5401 0 obj <>stream -H=N@d\@`t+U)LjiQ8”}YI_TYRyu3]$F_uRV6ڃI}V٨7UJ0+&t4 ~zUNnTzR+mT]+' 0L 1GC (INېbTQ! -izfjD`rh!Xxs Cl G90ZbpCpZo=l8C)yGkpvؑǘ}[ l_+d7mFZvp -YQ#nn:w -endstream endobj 5402 0 obj <>stream -H=N0e%G/@LZȀD'Jf(9BQ~F"K >stream -H;N0RXr#/niHPqDFb9B3vcf%*G2s$WʨT3Zfr[XJNDukr2EE̮b|MOͽ2V&DS}^d}*q-S #T0bFC^H"e#4 Nal=v3x?'Oh9c0rsGC5cA4CPz>lp|5G.pyp Rf,\ʄNغ@8m?p.-uB ;o}έʿsg dv -endstream endobj 5404 0 obj <>stream -HJ@? %}h{ - - kzBofe!Gq)H|wa̰L%E>ff:/5O҆.!LXzEM)n(}0}ORZ?vF܍DOt`ܠv;N@䀣|3`X3sq>6J4 ?QM?k/BL k~ -endstream endobj 5405 0 obj <>stream -HTMN@ G 0tD&҅F׏qBe -de[V۬n&;WfU_ʽ"Ut)Po/ٻOsL`Gj\i|~ -4";78Nk:. L+9DNG pvL!VFLpTM\-V0 -@,@lt9&PBBǬ\uܦܕQN?ˉɕqL˜&۹Ic>du;*H|y̋` -endstream endobj 5406 0 obj <>stream -H10xz)-(a"AM`PGp4#024( <.Ȣ,yBc̗1ÂG"An0lSzB)i|ܯ(&wq^@2vvB5J>F;Eڠ'NNPH3۶#r&6$ -1[ Kkװpꫥ -endstream endobj 5407 0 obj <>stream -HJ0)a_@y&m  =zԣEo>>JǂaǙKg2)k*eƺ@4iۚqY|k^lR]n;,n X\P^Ţ秗{xX񏢊UU;ՠUoT_A#lWk4KcK&-x>stream -HtMN0tA ހ^@dD&҅Fw&ċq kKK̒>stream -HtҽN02DG_mNk%`dTyD<}:,nt4IgI&'rdәtLK9w/U.'|RUUof )ʕݬE\A*ۉݥDodzX_ u5u iA产GZCܴ5=U:_VmbV{}[5JXT9PdK#,Ȫs_˔ ++%=ơr OsjYڅ$H ^d( -fNukK&["fgQE!q0}Fؠ#QHF1n9ji!E´`>E8a>PdN{ -+vT`;PIӂe7pՎY Q6O)WOXħLP+ЗN?<-z-ůk\ -endstream endobj 5410 0 obj <>stream -H|j0_ ⎻B< `qVP7#x f%Z4X&bi%&˃ -1M EiBcEl+Ed8??ĸod_ns>8JI1 'A{j>stream -HѱN0: G_:!*ȀS@07Itss\BLx$s3Ykmuju -tUd}|H5iÎrO0|ɵ>lo/tIu__D g*/o&~, oPlN,KYβe) ArSc6H6_Kl+Vd%{F9{i?ewVNFpBlz -endstream endobj 5412 0 obj <>stream -Hl1N0EgE$79B|8vbg*)@I:A\,+L3N"E -XM&T8?5֨Y*c|4EdҹurJ8u﵃|xZ$ҕ"Vx{!H$K|Neyd% &|m-%|2+̞cĞKӜݢap" QY02 ʞ9q7ϖ#%|-Z wO -endstream endobj 5413 0 obj <>stream -HtN0=e@(-mY8DO>z3Mxnr 3 &::4tbZʣ:1_⃅gp6s`]{)|Oo PAv@9G Ys/o\Q*^4:UӤ6tQEU߰*ΰo#19;vb{vf3>'Dr?k.խH4#hOUrtpdi2ǧ@ 5ySp-| 0E -endstream endobj 5414 0 obj <>stream -H BAC!unR $A,4'ꍑ.V8Y9iVZfV,W^wX~VS2k}/VDo;!k*Bx]7X,,l;x 0r -endstream endobj 5415 0 obj <>stream -H223434T0P03437WеR&F -zf&@B!W!%Du\~Sr#\.'O.pCs.}0`ȥPRTGADՓ+ 02 -endstream endobj 5416 0 obj <> endobj 5417 0 obj <>stream -HTQMo +ئfbnC?R{G-IE_@M0oy3zlZ=w7= (|qQ(-[N n]4F#%2, pp'\>\ Wu CCB"UBY,VlTS8m\U86G3lxȏ-8 - -endstream endobj 5418 0 obj <> endobj 5419 0 obj <> endobj 5420 0 obj <>stream -Hl=n@AH `0TH"RRIG.] ;ݙg)a~UV١pܕ&2cVVU>ut{yڷOs[=fh"7;; V7A}EpK3@D'uOԨ9^jWs|tWŻx-Nj]{xo!{xo?Mjwx>;_(oK~`^ g9zVq(Fi}{KMsX&p^+87ym`͠ްXFcc_`Ai }ށ{ 8+p  Ο\jg gκ~F77y1 nC; -endstream endobj 5421 0 obj <>stream -H23401P0@=3ScSK8bU嘂U"`\.'O.pC.} <}JJS<]szrr+ -endstream endobj 5422 0 obj <>stream -HN9@ v^k{c& T<()@~HHt)ƂUz*.9SC4xo‹!5-}6P',32o+҉ԑv4&RuE`^X~m} -endstream endobj 5423 0 obj <>stream -HҽN0K#Ւhb; -Hd@FQ3D wp۹4HqeJ8:7AQI"LGM|I^2 =VOr[Qe o1 WȰS/F@0,\":9%!a D ;~aG:1V"Z D 2,a7Gi'?`_m{Κ9qnMsĞ< 0n|P6VJ,\hֻ .|8sx5yS%S7 -endstream endobj 5424 0 obj <>stream -H=N0G/7H H@%h9!t)V13"J/x0]lֵ.Mյ5MY:):d嫬vlbNѕqu6 -TAKm妿oϲU($@ - PJtD#"H8A h&JcHE"f (4`H'CHJR -(# ^E["Tm -t1'MH|R|"%|s41B($WD>stream -H̓1N0E'JM_'NW+-  * GQ)0㱳z揿=?J _+SKj2\I51\G^Xu[KTX -SIաAٶg= +yo/voaRF4"%K/p Q'ʂ d9ӡ~pܸ%8g44!MtЁZd#Ê YKOhp~@ kEr&B@Jъ1~7/unqZO0N@VH3Y}@bUv0K$1>@G[XJ{̆ MòݱOEas -endstream endobj 5426 0 obj <>stream -HͽA ,'AG0/Ό[ ~m!U>ZZqֻ&">`W!?sIy2jS78ry 0I&Ϭ4=\W%M]fKO'R&d1.&Z`1BfJA %5@TuN&CllŲ6{QA9` ?7qx`9 -endstream endobj 5427 0 obj <>stream -Hd;n0gE# /&B)r$eDI )cϰ]KO<*KTQe&URԺRIՔZ˲Nʬ*鼑oJrJۜFIM]pr N/E HO2i(~Ew'1&vf<" BcvX-cVh1=N!@hdz(` 0'} ^Fq2y<chdήx[s&>[ -,! d fRg48\9&;5?w4Дк^FgjOc?'w?z6!?VLb=@.c8 q߉g'A -endstream endobj 5428 0 obj <>stream -H1n0G!RJeN=@۱Cv(#٦@$^ vGɑ]ݦ`4ɩ"W9'Gdzc - =iQ6~c>7%A`P[k U9P -t-Yݦ:_KzFj`R jFJQc*>QR -%\(}j}kj/09WzPxQo5`F*JIyQJūT[:uW&Fǖݨ.]\(\Q Xu981p`["@ad=F$ݜLj%UVI9j˳V94^ U `OIGfyg. -##y! H -endstream endobj 5429 0 obj <>stream -Hlҽn0pG H^-~0T )mfN}cVZx4Gµ,%wN!O8qlyVT?Qx(ao1iFYQ]HF; UǸCc8x_D;AtSY+PoH2 %ނ5TxSLoL^C+ -D^9JSpİ&:0Gb0{sɩFע73gtO Ow]F4AWz_ -endstream endobj 5430 0 obj <> endobj 5431 0 obj <>stream -HTQN ԸޤºG=i%P;@p0 3{"{0j$€6Gw9 z;3.P׌W&:m&bʏO"oxȠi@x,싘x"1Ώҋ -N .Sh;V&0/X2:˄[ALFG6YUNIU3 q$%LjTD̋<م^s48xhY|]l0,#5w - -endstream endobj 5432 0 obj <> endobj 5433 0 obj <> endobj 5434 0 obj <>stream -HL= -@`2&;I -jihmcxxB|n,D`x#NH GFKXоHF<6ExchOj6х;jW,۔j1f?s>N['ЖhB 5ZB¯םѡr.@+DW4iI)\ -endstream endobj 5435 0 obj <>stream -H223434T0@=3SC38bUU"`\.'O.pC.} <}JJS<]szrr -endstream endobj 5436 0 obj <>stream -HT@j60df6 -;AOգEaB/Oȱ!eU;3^+vvonfWPӖOxf۲}uͱXsHŋr_m⛴_m>mu}6w#yA`iС $fݓ - OF4Z+U_ޘm4hdKY'q(N 5Ш8l(KSI]!IR5_)hg(ZZ#_L]WuY,:v-.W]$"XTD UV=j['M/WW~!*03։oH@0>G[#ƒ5gR$l"9_zѨ?f%!i{{v? zޠ -endstream endobj 5437 0 obj <>stream -H|ӽN0pG"y #"~ΗN -Hd@FQ3D1g%Җ. swqFHśRnKRXe\yD$qY*4.6RbUxkgtڌ˕x_R|(DN$|]=^s6707z6C\`O.&D g[Q/НY0[_qLJSv_-sͲP3Favn&pud45$d!p[xpGdo`" b3`7=Oh&4 .c[/v%`PF;'@ 팀%`MH e ccv8 A(Χ[mSvh]fG5xX^v;>ρtt `dl -endstream endobj 5438 0 obj <>stream -H=N0 ]uGxK L  & kU"X2T5v>XXpU"ǎuTmSlիOVU^Z=TYRMΛMLE Sm-+uRbsz}y{D6xLx0@݃+px{N .Ĝѥ??D)֦>G"eR4. ̧0GJ18vhŷ] Gtb-29])_b[/V3#{gD9f眺@;ӒZRA[sgdra\+GKYJ;Ie/o| -endstream endobj 5439 0 obj <>stream -H=N@/ %ـ<}V/'D+RX%a̕_+F24nh.O4jKb.!L4¬&4`-q&e.AF}  0YC -endstream endobj 5440 0 obj <>stream -H1N0`W"y 44U"ȀF17Q|,WҋY_VܷoX6+GMחq[VfOҮ-wu|a_Mܣ ;B#>0^Ut{xpwmeot.\*kGVqmЄLDbREٵ&йJ!brPߋiǂ.;11JHSC\[fB<$k@1a @z)wwt`Ateh@SS\8zzsA0)T_<' [#L0 :vC&'.@TgL$4z={%/#X% -endstream endobj 5441 0 obj <>stream -H;n0i0FG /ZI+Jc"@\v6zy4EGPB y?XF3p2uuh[Z`^ﺮ2ͱ9hVuۦjS#_W!*M$.79M/Gn3 -gd r"3WSx 8 2$"TD' PPp L3³f`,?@Ep+ŇxˈQŔpW_ն$5ȂZwf- -Hxq~-"L8 m"]6Za6teFhE yϰCČTDZY7ą=u?Ҷ.B3g p"qcFh{h?&Lm@w8tgD,(<*,Hq,ėiow'y/ 0g?I -endstream endobj 5442 0 obj <>stream -HtMN@]̆#0TV$~$va+.]ht ^ Exqǂt7A+l cyh#$*fHE/Y">stream -H=N@1Lv.0UHa@--4ZZc5H>stream -H|J@c.nf^ p^q5E[A.Z2]z-7huFɋݫ3ϙ$O4Y"mO,X]j=D$/q*l~._-?}I;%$bQ`s"dAF8@_G59kj&KՓ6KCK$o᠊"P*JlGEJ2 }x%u[oS1c4gj)x0x18F:a)00 G9UcU; 7sPG&2pb`1 gZb8b085aX*ZXGDug}Y} #D5itg4Z->stream -H=n0qGR#] SPzcV 7k^$#$P*2rT@wV7&ƺJmw(JV, V=7V_nďHPJ(ǏnuG;FybƝ fWE0#}oݙ:2<cq`3;_;pȜ<_FD֬zOV99>Mh73|%,r-~[%Mp~su -endstream endobj 5446 0 obj <>stream -H\10` ,DvI4m:`# CKG|b`l<޳G*gwV^uT;/u]jͲ\oMl;˶|߈b0[aJRܖ>˝X-OK|^_| vF#3p_<egK c@!b rdz$!B<a=eڪ!IVXe a4*B9W1*ýpQ zv!VG 7r@ې|'q&ܻ* égg`(=܏;t - xxYB /P$<܆e'`s}! o 4+7O%bQK%͚ۦ'{ˏ6AK"M6ëX\\~>B==0# +q)M S!6rF>/;F`D -endstream endobj 5447 0 obj <>stream -HTMN0"&2Ϫɨ,Ltԥ &#x&$^w]﵅IZ>ھ{2,+*Qib.".ebtSVIL=MeNE! ||8SOb[ţZbqk[u>?_Du-Gcp0 #Ct,-ai!{л}AC+F1taG #AwzN8ڬX{IYO0lyj%BE0EйN8ذp…th1s9C7#61 ρU]3h `MEW8D{͠z6=Uڱ %Hq`z]&pYTA 0}â -endstream endobj 5448 0 obj <>stream -HlKN0KX4#L/`Wf5ɨ,Lt5P.4q% Bh_צs[TieMYUYʔH:31-rU> -}RJu_չQjЇLOQ}~NyX8yZtNtYw ɤƈ??mec Fq(>stream -HMN@,Hf 2hjR5 ]yuB;SHƋp,HǙ7:Dc Um^'|$a*2:50F? MeI*4!|a[>mĖ7\D1[.`-xf+&d2%VHf[P\x?K9Apte -Frܸ߃/mlrT͖{|z#Kήݸlmbo~l_;9.Z&vqc{VcTd3wbosWiZṇ.]+qT/E0شc)nM)mg4ou,ou-JߨGG)%5w#lh8d7Bkw9gbln;4Ĺ͌W##C>*ؽ:̮ -0j -endstream endobj 5450 0 obj <>stream -Hl=N0T%G/$,D*  & :TJ%\FH vՒI|dQf4,i K³ f9O DY>r:HFɢ$=M$iNe$,o)/ "wA -8Vb El5RBLp-E)h댲R6^YiPS -?hFh=uǍr%wY)[hP7~6Ώ˸1~֮=tBόѵ߷zAGm͠c[۱g #UIȯm -endstream endobj 5451 0 obj <>stream -Hl;N0Y&G/@ޏ*)%Db&>BQa,=IuR2:+TQY~H L3&}Z\=c'jܩ&Zݽx|ݍ@)Џ43@MKq p LbҌKW"pWjl4g:\{jim>stream -H?N@gdsa3PHaPK ֐xd@IAv| ZK *%U*LUiuey^T:R½x&W.G4|\ҹ|xF2"e%LDȷ'\ -q0q:@>p bdhˏh]`L{J?simheڽw{OsϚezTq |GhEBXaQ=jĝ`nV@# -endstream endobj 5453 0 obj <>stream -H\MJ0.Y sBeT AW@]Pt'+g\҃ ]4@?h{/ɣ0MHCD,,eb8LQ=,aRaI/a-.D>M7bŅXl:p.=Θֲӯ H8VNGP:4 d}U9!EY|8t()wԘL{+Y?VJgM:uY℻,nFUc!qR/+7Xp` =ρmWۖT{2mub7b7# A+*H= /NC(4@Q>AiFlwƇKAbG_ -endstream endobj 5454 0 obj <>stream -H\ѽN0pG,y#/@kT@"L}`dX&yuzپvsri:i䅩g&*i*|5IT}( 3<`uIi ->o:/j۪NZ?*Uӭƥ}_oS0G8$a a?v 4Mb8bgcXtaa'l8n1u#x E2a@ aM xx=#dH7#\cp Fϐbň`СcGbD cEݷY 0< -endstream endobj 5455 0 obj <>stream -HAN0`&ؤ0dV$&0qV@] 7!dAm7j]<>& I2Uns)V 0FX6sB ?8`Ɂ#rnpU8#{dl$@¾j\/\fe$!B*xX" yɑ`v -3$&1 k[;0].xZP`4AD$CH1W NFJ_  -endstream endobj 5456 0 obj <> endobj 5457 0 obj <>stream -HT=o w~V iU)RB Ps΁Cj9dȿ/pU~p5 %̈ &-62gL:͢# p5w{"9?g; .,NW+'ּ9VkIBH@oX{%ΓhbN=IV^uJHL(ܘ(W;ňxb[`kb - -endstream endobj 5458 0 obj <> endobj 5459 0 obj <> endobj 5460 0 obj <>stream -H;0pG)Fr# J&Ii)% R$2El`Cl묶کCVO]vxK dʰ'\voh{1Ǫ tጫou8K%38$k,ni\Q.Gl#oG/G~m-ڀh=FBRnq 5!EkH64;h9-D<,؅o43Uk#g՛(W a"X201sjHSo[4ꀦ/h_Cix=iYB -endstream endobj 5461 0 obj <>stream -H=n0lj2 yB @RJeN=@۱CvqڀgRu(o2agh@w~QFi"b?$fIkH>H,(Ap6tʐ;dL]H6TDY@>HqKze͢[gб!k;1g`sԣpa!p5۪$R:1oFrӉTdWΰ~,3D\u7UO\<{uLyƢKUŬaBNJPPX‚+Gh[3I(CEhH -k=Eq(v4Jw4RBs0pFEkQ]-Iݾ?GҬ/+T VkH -D~P& -endstream endobj 5462 0 obj <> endobj 5463 0 obj <>stream -HTKo0<|lrMڻlѩF6d_Fpx,Ws8T.i_̮<G/,Ïokyy'swX\7/Y~29~Rƫf2C9,kֿG\系> reb]ԬL[8kO n}, a9vVp%98Q8'wx=uwn$(n''~ wn `;7u?(n 'h/p_+'=EScnע{'W>yv/s;K_gf -:g&$w`N-+[8 kK\oɏF;;ksY)FAgAwA17 cS ?̿oAo9^uZ֠A@,9g> endobj 5465 0 obj <> endobj 5466 0 obj <>stream -HMn0`fɛ!@3I3IU@U,: WQ X"O4yPUu$~㺪XźmڡeW}SVUpS6}5l(j_PqnzJ] Ϯ5__==޲XW^Nط?kji.LI4 D$!c,3IiBr:CRQH4)"T]2(?P47HϤDxOl 1X$dA_tD-ޟHd*OtA|nuJɡߥHEmcdttFL&l0H7$)$hs>stream -HԿN@k#p/PZDR5D'@4:sN +n 'wpgw(SzhIF9NH~J%7:ZarXVXGz<ÛWv]1n. nrNnqr|.Ь(m# yӎ)Q ErJJV. #{Kɚ[h$k~ءլb$HiR9 J)4,!wJaOxD@yU)RjbHS62y]QFR@K(&+eziPp'‡|p'OȒg%kv{tRطg.0:%1I':AF&0g+ -endstream endobj 5468 0 obj <>stream -HM0`W r&4Ӯ" 8du##Šh F|;=ao'v}#;4G6mt`ov܏̍N8!ua>һ3ݽeKq@wW95Ml0R"v%,ydVa`~ 0ȿI@B`°Fha%@ ܗMLs웇K~Dh/ap !%4|B+ 1  iVH`p8!v@@+6P^`r;D[XH+yrU&OԻX|l і`ɹCٺC -=貤$qú rKK!W3ai\7N:ڤ"Ҧ"&P:se sKtwq7G& FDPPM^|.MnP/ %Q'"! AI!0i`Yo/XLJw6t/hKs*!@aǐrvLzr#Z!s 9 0( -m }qoO? -endstream endobj 5469 0 obj <>stream -Hսn0A+ aZ)HIIL(WGQx_Ǵ?xզ2.v <3eK.D!o]QX,Ds(k~(վٱ)zLoZt}[{ݙmlaۻ|l8SlFڵ2AdaY$7Ho/uzb+X8n9ovAh .qsaqCpg|s0dz4%Z{oEg2kYls%wp_pX/1WOV^XO9bd_ 6Mmi\W7Wכ~li i|e|Cُo*!Y'kP $ '#G/m3==[x^Ca\Ϳ9 -V[` d -endstream endobj 5470 0 obj <>stream -H=0`G)" H.d3IH HLJ - \%W\01yvbg)q13^ISe]5?VmӞrhnonw HS5Mݗ&f8νݏ}Ûi[zxz{l{zyFC )pALQB%Lcc @juSpfs7v3Qv(+ |+C jO"`5sA:Vv+5H\T,%]Af<ǘEfVGO" 3;z^(J{X_FWaXl ӲIA!\Fa5#I6q'[ }:Ի@\ 2'J-x>stream -HMN0YL G+Qga+.]ht 7*#ELFb䓶> -O0c.8.ey|dloMa>]':L[va{]2tyx:\z8DkRz=xB; {n= p [}a -щ{X;L[u#Xzʳـ`^(zq7Cc5 -endstream endobj 5472 0 obj <>stream -HA0PYDq&*] ̰duXp-sp/jίʨ)ޤ/-m,yEYv᷻hRT|Wڊ QM - Vnl=Ewu- h{~}U[.ꆮ޿₮?kjbY *cpg-8&#$ÃGriN(1怩+DEھׄZ9z&Len Ts.>Ǚ=Dh;tLb*43ӶM+dVu! -JυYE* ->AqC3u℩1QNt;)qȍ?)͑c5zH2 ՟96أH~zmyCpC8ɟP 3w.!y`2V}͡f2Oo!0M@ f :OyT]9)(PS_FO !N7`/ -endstream endobj 5473 0 obj <>stream -Hֿ0_!?B6iHHt@V$`dT#^,G!J$Ի,4j/}yTܝ.mYGuSV}]+򍜛mTu -h<\ٽUj{xryM'ߘ e &6&ptI\X$S%Z4 &HP*~O(HI=@x4b^N1E(jI׭]c.nGE0=5KE$@\^t. ?lGsv+:1jnew/TPۉ I:f^ZL|_L> a>zȮFInlu+QOJ,ЕYi^l%_7MY_wE -\ys󙤿ZQ!b,\ -buMe7 -D?"h"}@D\O!='i Hӛmt%2k(ҬL;Ӗ :@藺Gleb듴Ƿ >7ϠtcDn"ۅA$zGD`~QV}ˇx8>+" Y04$w1U"o.' -endstream endobj 5474 0 obj <>stream -Hձn0Ћ2 y'"TJh;vhi|J#KmoPUK|wp(Ig:/4INEHSʢll}䙈8*8K~EĊTd$gK9PdsMyf{APחGR^%!TTH$˹HF!QX$G$;7JRUR҂.[e`dxخ'$;ѬJym"a`4 g +u`~T/AD6,Z_Pw~yKjc{ȑp.G$H_G3R1ͦ 3 -pzLK1G q!!W%# ZE -endstream endobj 5475 0 obj <>stream -H=n0`#H^8@0EJ[ کh;vh b#0zbYH'!Oy66fJ cI%AQL,qDcdAQW?p,IlA$^ug’pX>7\b)#Ul?*DS>ńZPZ#+٩H#z4gG(f}^'qY$%M&Y:Nk:z q/Џp)Săŗm!;>stream -H1n0/h0>xȤi CvڌڢIG rG O~& :E9~2'%)|_6B+~ԥjxJ!5UYF/}g;UVʚܩJ($B_m>q)+yeݰ\=;ac.iȢC" }"$"cMTE!U.Y!$4a*:e\XDȰpmЀ)4AZX=LG>CtF>:ooAMMp[頡@xBgI>=#BMN.}^3$R-'S&-uhuӆ-r4p^WA/aVe0sNvsf8ǭ@rtTڙ'8E*VKZ'*b|a`V -endstream endobj 5477 0 obj <>stream -HM0uɛ\M}I6Ti.FX:G7!G;KX1qďW<$eQe^uɯ}^U]ț}{l1?T?3ۼNW(j?~͙xd7ZyO:_}dW)HaK@ĚP b)NIB t#Mx}, S+l`Y,,} -NmOj/vO=:dh5-S=[IzVf@ϡDOesg5`<"#.GvY<^_\4>j,IU! ϣv=rv36 wX#d-<ϰ>zEBfbծC'zF5;O=?=8h=.]/yGO&俉=8/{GGF2U8xY -o^ޱ? 9L -endstream endobj 5478 0 obj <>stream -H=N02D#h4i~` WQzld= /Iswi̗I~<8X{ĩG4p(6 o-čuC$lc{E[]s?%h`)&}p' 8C"IME)D*"HDHRݎ$jt{o vY')d?B[)2L{K HrLQ!m~2jHň*3JK,Je!ce bt1.HAD` H*X 949eJѐ" C.B~.&vGFFY*ܮLs>2#XZCHj,!?ühD]j|XS0&;T8D>c_  -endstream endobj 5479 0 obj <>stream -HMn0Gg GRJEv$Yv]EX~4UZg٬,iIkCM/XuhÊFy Ǻ8mST}vnE|Kz\FT+2?}߾~'wDq;C\YL(h=@c؈_\fȳhΘgLaC9r3 w1bG89dj9{h0IqqgS.8ǙT{:0TYnUe:QQ#YAO3lbEKD%,a f{ׅ,6ۉ+U2N7e[&}?zzN#5m5ŇLdx:bj'9*Θ(q['}_ ![ -endstream endobj 5480 0 obj <>stream -H1 PF.,pszƮF$RT{$eDIs4\Gt11?8b&.֞guUweTBVcU ޝJ6GOev=Tӯm˃h]l㩅4 >\עxMw5FR WHH{_a>~=hX{R/-L/%3V`N !F ,m!@YE {`l -lOEѾ4U`p)p&=n^.,ܨ@6Xw+5{d=RErW?@@,2tX !Bey^JWxmeԎ[_L (i,K`fJCTolmO1P){~s,^HI' I*?cFB> GBt)7n ׀ 3NUuVj:]$ @CZa_Am/k -T -!L\[K} -endstream endobj 5481 0 obj <>stream -HMN0YL Ghɨ,Ltԥ q;Iko,[}2J %v,$wÐ24b g GK fmЍ-@ЧqL>-a[K‚{3"]cq$,9 pֆK0FDPp4W)VJ_A+mF{^.'.-3 r.,`h=_5ZsZZ=+5|ztQ3+*S^ҧ^o>`}?7nTt{q[7M T |!: -endstream endobj 5482 0 obj <>stream -HM0eaɛ!*] K X'GQ "%q&=;ͳLd+t7S)juQ\˩j1_x]<=4O1g~w --Cwo_};߾~/k(HHHC4u &aD0Z"z> vH`$E2r vYn=U&}DD/R)}0K[XB"3ZEGͶoI P|XKu]J䆽tmJ` md0wP 1]ٰ4 MS);֎S؉gk'It -CN2yxx2Tr+7r3]V3+K2*ͅYbfž$O c%KTRcy*3xo|]`H)sx$O$i\ՙCj -endstream endobj 5483 0 obj <>stream -H=0`M_`3q2T * k(9KQދh%(p~gxY5EU{vמ]ڢ/Ux]ta9F/Ut_33⍏д؟O_Õ?2~-MI_1Nw_5]h"a#i'-]'6O d~a 0Bܳx2Fz"A$@,yϾłٿL}*:XXF;' (#U -e1,LdeJ"Qy*&D?WY$&}$xPDzZt| y 2 gW"v @F\":WC=YoN*tVF32 I 毭sDGv5 FH|7J'W#9bvՉ-L(I.iXA[wDv|D'ٰ׏(3`(xסH< g{ (%6-Jԥ2>stream -HM0੺䍏\MdU] X:WQ|"r86_=c;OTvե on4Wp벯ڡS&pKw}S}}jh{\yOoPmM/Ǚ_=-ew?hHe\қ&'$2 <p2gd"QsBa^ eղ3F4Lx&"4l -@"*c,*IW+_,8U3)2o.˄{عޅc,q.BY蚈,lw$ xPEafAH5h BnB:NP@̎LԌ{LDꙡ)m&&+\]Hti=cPVU )8H.xVOHeʲa2dՔ*ʳ)UT S^š -º/jhmCKߗE( 2kYC 5Oca ( -`R<.S*mM^!u},L}d@ُcquZfE<*'r·D&!%fd{?^Q8 -7|` -endstream endobj 5485 0 obj <>stream -HԽ0`# -Knx²OtIl))rU)J< -=xەBŒ=cLe추7UErdU2n\޺$-S^AǏu.?~wgqoN8ܽ%3q8O0/_c{`{O {4$oM<W>stream -HԽ0`G),#/pfU$@()@P'WZ~.?d;Q@."}3oUǢoh۲jxw,ں<׼jT}fsqjc{W7ve]6 l׻? ;5n;v{Wpyÿ~]^29HI9AV {/$`$Yy^.ۀ3>stream -HձN@5& pT$&RhFkH1>O cq&7cvYعA -~t/EH%~"/>Kx,TO1"O/ x2KB@q =c&ZƼyа\S͘g8I7&TVGa丝<`VpߣRߢ9[b4gE.!{'2ԓ"ףJ1 uVZN 5ZJV2w>stream -H10`W"y i4)Ht@?02`7CSN#l?uU?voʮCٞOwe۴C?3eS7QCUCW h{~?ѧ =>4k^7==>}kz_|H//Y6 )R{1MK1ha`b @_L9}\S 34a)¾#i~b8?^}g ϘAv!ʗݶ[Ymf-Ѫ2-L@f~ L$ TlN-r>' -fTl6>W؀a&n1>8e̍#(7۩{=E:=4-ύcU@+$" -#-+~ ~7fG-?NP.X1!^NXGP⭱{P܁G s[M +v 0H *zA }u/ѐ" -endstream endobj 5489 0 obj <>stream -HMn0p#Hp|0xfRgQ]m]J bI},,\_sĤTb2 7(HAX^ޖ"7!/dXq_SB9|>۪!drӶ+g錸C2-òyP#y|YBAP2m2<# -HAe e[g+bSek17 oVgMEȳ([錈Bx :ZL/jIv-Y"(&JL@%]).bxq% GIEPxk6(I!'PJo;5jcDk+xHMIΫ/*oڸF~77;q Q{F !ե?+ηK$4WSPp^ h뙰B jB1 c- ra'`B -endstream endobj 5490 0 obj <>stream -HO0Ƨ"7>B}mHUHt+,Y`Šk(^za%OҎgJ),4VVvwة}SvmUMUaSunUpVMQ?ǓXSf#VVVaN2kzĿ@ge=|`m tlRi$9$ފ_ PaM -endstream endobj 5491 0 obj <>stream -H=0pGG/d2i)@I:YQl8(9B Y1y/ X? -14fMl;cZ `ݓ'$,Il.AyVK7&36#}OsE9 -3N;O`yM`+xtYF[XdsǴcw~TqSݣtۃsrc"r;A#eX28'f"qSswX"p{gHfD S{vS!g -XKzmsrhhzguַKfV\ihhBCo:ٔ7۔ZGY7ȷ YqXa fih N.VwRϏ5)g0 -endstream endobj 5492 0 obj <>stream -H=n0/b#=!TJ;jgr4ƀBlcǨV.6'4 IHVA<%!_qU'^NXH?ka>z8<>M[Bc_H`.;U(*XdFL,WJ3ON3'nIԪEeVuZ%;/Ū/E&$4Sk몏_=U?j$+uP/UaR%<%Y4pEfI.N?K%R >FӠ=O - -endstream endobj 5493 0 obj <>stream -HN@=G`_e)'LG5z߬޼ȁ"t)I-iZ=عegAxx΢$nzB$t#{ D,`O,]1 0]zŁjH~p6/ίNy=.=Θ5*5ƲȈ* wSDJ"D4z=KՆS+: -겍*z"U;>YKӖI;QMB FHh-~e|0~Q쟤yw4XI',k*)9}[Q'cI%KA;"( oTLHİyƮٗP - -endstream endobj 5494 0 obj <>stream -HV;n06:xkժZIl |ev-#d!8F)$ΌTWH[M}uߗ*'2ʮN9p[Pm֟X6}w[8H ߒ#o>#5_>K/ üwyUv)ACgRHzT֣%2]B;W2م](gB}(# lⷡkrc0'yMfqPmeC۔\bKJ)YLU3Ey/{wЯ,R C{aIXE$a'PXL59Qo܏p <0ԧQŦ7H&>stream -H1N0PW*y 4k8S`9=Z1b` [$n_ hҋmPByHfQD'IsF0Ә%{0?|sҎ%9KuB;ppE٪8X\<><+u('Hr2yN'4@%ܓ #ҟ۽ѲDQTfNr=IR_* w2ڙ*pQåö-MES+Q?$u]Ԯ/JjĞ_=cW)x}b%;2 dwz۴ _YKL1H Uitη#J\Q9Ⱦۙ$ .W f 0h-ׂ -endstream endobj 5496 0 obj <>stream -HMJ07tQȦ LkUBЕP. ӣ՛Yv1$v>:Mq#l -xE4ߢS]$oR5r+vͼN0E>&S;EKPdRiR7:cb+DjOn;Ⱦc;H\"7CaU -endstream endobj 5497 0 obj <>stream -HU;n04J#/§B$RT9@2E^Q|Jb2ocN8hQ&ぺ(j@ bXT C1ՍY1& >C}EfZteDxb`46zzmK W0F= È<ySz\dR6Y=RUmwո2+upƈL:;3v,u@k,RNX ..JC5V\”5X\Lz-s)js)SO៺բȡE0.RAؽ,]&΍p bηF̃}Asg_nh -endstream endobj 5498 0 obj <>stream -H=N0Wu_4*F$@07GQ &0K lڮ*OoE%)XJ#Ȟ=PoIu?, EbٔΆlmB滿t[*G)%] -SX/ਓD")xB@Q0cRe; x> *`CBC]%!HAMa iI+-Nмjf~U?OOf2ؓ[đRٰSc3v>h -endstream endobj 5499 0 obj <>stream -HM0`MP_`t:*] ,gurp#xE?b'T*H,>;/s>stream -H;0`RG/3fIi) kQPrK.]D1mPH~eXІwݡezյөa-شまb [ -fןΝo|H_ RvSo? /軷_cl/-rsD2 ɬSO Euf\k9V{\(۞yͭ -O34X!Nb!'0D?|#RpD4^A0x'1lq\Ő#?k^1qHXCJ$鮶CKFX?X4kX[(X[ FGUE (7wz9kj=Emgbk_GaU"|Y>1:( /G `s?V$ױ?Akmw5_0_ 4@ ~ 0ۃ{ -endstream endobj 5501 0 obj <>stream -H씽N01TG_iOH$2 FɣQ3D1vcljH ./w3FE·9E̊TTd1ˋ4?_8~"6+{g ?X̌pw%)O9Sh{ZX)E@:֡ Z-ɀ[}$PH$ =?$$C$ \F; r$ <<+z9tՒpezzƲ; -]@HHNA6h4Zق3Ntא"ͮu+ɩHm05#kb418fs[")֧YFJ6O -endstream endobj 5502 0 obj <>stream -H=0";6/&~RYZ@Hlu,Qpp2HQ=c; - -@ |c{<ѪԷʛK[NjePu{NR8kujۮMw{qȗoU^HTc#wI%gXqe *Ns 3cygFK\ciJ'8:b8NkʏUfJA -8Z -*<},+[#i5rO48D1+,P$MɈg*q]OI_60ơ`4%#H'q -M$0N$'q ->І 8@2q(Ot }oJ^1o?&ڰ#fMˁ眮 u u5j>stream -H얽N01TG_I\|L -Hd@FɣQ,=D q> qK}_y O4c4 2'Azf{0ηqԲM 2y<{9{x;^3o/ay^ߟhyIQSah8CuxIсZXBHP\1#κ˻¦A| 5 Yѡo1BF1Pc DF sޏw -M%@VzAs_#eضpGz=.W2Fp9s,^ 6k蹓5*d^UU ART#|D}B@? -=~:3BVVt U5@co0$1ئK"Qgo* -T.xRd>o^~ -0b> -endstream endobj 5504 0 obj <>stream -H=07LaP#XX52&)IL -KƎ'6 K@"~{XQЂ6y]]Mo2o=e}*yڒ>0\eM'n&/.mU箤한>PrzCYu!ww-Gr}E߶c&(`Ogl GsL9c[Q.v6pN# -=c ?d(\J eF!PhY1OGQ3<:^YHܿ#ꙥST =HD9xpk'|f ÊED.u6ώQ -:ۮ/~S}6u78 -C@[`R*tԧғ8N9>stream -H얽N0]uŏ@*Ȁ k,GȘ!Jp|TbsvD|ͣPYeB"E2cRY} EiO-sF,$t[=k.dJW g {}%&棯gt0R#M]YHO~Q̐lwړ~4Y؀ZR#ryX5󀮣 Sn1M,*ki}@ iV?m0u?&oڨXz~:@NA3fw5,  ؏Ddw" - *Dp]! jTS-<0gEz€?I 7ĶI1EGn -^~ -0F $ -endstream endobj 5506 0 obj <>stream -HԽ0u-FrK?bw"0565l y -endstream endobj 5507 0 obj <>stream -H=0`# -$7_`;I}jeg2+͝𘚾 -8HqLX{g>{THf\'Doƒ# -͸2ڌ8S-ppQ -S8.2 O".l5Н9U pH}  ^"!a>stream -H=0`G)H6/~Im"- -$8PR~8G*>J&Egb"DH)',yɫ=įnթMQVEUM7m]qn* oFn}_gzxE]3.n_<\"(B$&IA Q,2>!1>2t$XML`Xd$}$C_*tJ!NٜF*cNq$ J -2X@&5,2.̔.*&!mCt'XT$2aAЖ."UۡEqwNyAYQ$\e$V2 LCY ;&r1(7,dHCTLtЊ#@Q,} 518cn<6e͖í(/L<*≮܂`L =3cwQ2|=0sSX6+# 9$] ^f7ǯV||t@Ԃs$_"Y$-c [._~3}I 0< n -endstream endobj 5509 0 obj <>stream -Hܕ1n0idEG/Y2e˚ -P)H;fh"q+h|H:QAP|Ҕ'MQj'<g&)*Hmٗ~%OML*d| O9{x{gYJr~|JijU$I}y4I}t'kBF@H&50j":jpr W&@I,FRXjabbр`a$'1rt,X?b"O:^XS;DFN0i[jLchAv?;5wewT:Eyԃ~:@TVhvf+* u@ZDpdQNh=̤+uDYִAMP,"2 58!/_%, ˙LLlj$c+X k]5·9~IOIaj :7Ȁ؝asM݌Ӟ Ez -endstream endobj 5510 0 obj <>stream -HܖN@ /Pw'MRRbT BCr2Y1B. 9U=ac$`0[;3Y-{0$4 H߲rF cLeIJVW[cY0í%j"" .&$YQ!%F[IOMAߍ; X0cf&%IZXwXC -IM=SSKu: -+W뽀!#[m>͖!xC Ĥ$!DRMHͰV ugd%Y~ZTIRb֘UB7dClz^) -endstream endobj 5511 0 obj <>stream -HԖ?N0]u%G/4IS`9fb\%2fbNm@Ub0H@V~f,dq1I(ʳ܆#?L<ɸj HU`z{)q$2\aT^;\bιǥ$UP둖|IR+2Y᰽+/"A؃T|@ʶCEP!]s1<-2|84|'LW3P0bkCrʄ4iI'bu՘gu(rHCNm17tO@L}P7޿5⯅ֺfs dYD'+I@@UդV9н)"կflrKNC϶j~zg4Yu(̿NWQI23DGA^Wu1l - -0 -endstream endobj 5512 0 obj <>stream -HAn0`#Hp|`4I"R,hac*zn^qgx`G(R+O 8Wr+%n2&YRRJӓTI<ݬV<]7%*M׳|+}=_RnI׻wBQ|{nƑEHˌs֌3j - -"e!@fR3lYf9 fdDmȱx'!jfl/.ցU"B HZK9%RC'sv ȡFK:j.q,jX@!}`XG2q%vm,m,)S66.U 'v>p_)zc!-M </o w\o +>T -endstream endobj 5513 0 obj <>stream -HԱN0PW@M8i)R H00GfPipPAP7Dy((%D<#,8)'f38a[']ʸmn+`7m^qO؞XA7XAW߻i?[ +v-^gv[[x* +ZV=rǎkz7Dqo}{~Vy]*/꫁J|_qJ -endstream endobj 5514 0 obj <>stream -Hܖ͎0PɗҖ][s6uՈ/weuK0"vD%&tLƄ(DDukD3FO'=kdHȘ[κ q\QyL #Cn& kLr$ҎDf  ]"X;Kd1%؆!s J=DC?QV2 1W̥K 0#R89kV콍4'"3j kO5峭;rD2"CҀ{"y#bnm@ -&Ќ"8R`ԎȌeԆtk˴'"8;ä7-kJ:Z |98Ů.@'| 3ӟU=I2dh%HsmD'!z?oEoP‘i[j!!*et՘NtߡHetfWD #D/2㯮%uI -endstream endobj 5515 0 obj <>stream -H1N0PW H^zM8iR H0q`d FTdwRTRc?esc'apg7tRqR qT.K;1'͂5!mBx6Ua֘ 8@+ KrVݑ؄ |P$S $ՂYLn+L +bX`VlF;UU=eǫ6eSfk3Q=rJՑ=xx]=ff\&Q|v4^>}5KMr͂RJCfnhHin/VJS!=ߖ{q{)Qx th7 z+-{;DP -endstream endobj 5516 0 obj <>stream -H=n0'x0:x֟#O;$hROr 80b% -}>|1l|9?+x&/qqYY&쎕i>6*K ]3O|Iv67јmW;jGֳ+:%y$YO-캔}}+wz{l|@+4{}<[Gk -endstream endobj 5517 0 obj <>stream -H=n0`G H^8@ R*SvЪ|ȀL ~"UҲ/z~6=`JYu'URRf 6',Kv6#/x%)M l]IQ%s R^f'xsMؖ[sC^ߟps.Q [Ph@ B'IM-P G4I/!*gƾxt:nDJJR*6NjPBNVmeM#_Q --C#ƈFWFN`z<^'gZ]QlŠ\RTA߾ْ:;_4]/kuP~9UY?5kL;P]Wy#vtnz=*I͟p#' DV9tH8L2rހboՙ"_*NU -endstream endobj 5518 0 obj <>stream -HMN0NfA^`2oE2j" ]yuBvkkp,-?mbo}%(vFIQ ń QCtL|' wHH{y -A>-ć%> afWf琏ttiAIiH=KӅ3"m!yPMn8fHB))E֥2Z$]9bR.836~Cs]l1 )t$}Y*iK'cDh#ISnLl[n Tz>] !!?&\faibˤ֕@Ɔ=d'~&!!: `'g -endstream endobj 5519 0 obj <>stream -HN0ᰤ^d}* Ɖ?;P4zq5 8r ԲR(ͥ1arIȏX:h~^Eȅ0f~pMnE% o% ',\ŹxDdq|yBYl.ٜ` q*̠ #*0U07ZST:&ݖTo? Hh:ug? -:U$Ժ$5#ginCA5:4 J %qZLT j 5ZL#׆Wɳz)ugjJ -?AUJ@S)*wW4t|C@9yO#1Jp iۛR'$_Ӌ Tn.ĨZLEƕ(Ƶ瞧QBo${:C7=D]>%NQ94b@o $(Պ0B6|0`fЄ -endstream endobj 5520 0 obj <>stream -H=N0`A^rIdďD$802`N5r;?BbS5vggPcQ.04+ -S\>ºHu -YGU -+@./+tOpy .ONQKrgσpo<Į0E-bP@@H 懶=iELm35B?\T|A@mOpWH=jf/i~I]`/YBm!"4tJJ/B$@>҅¿W%r# Z&r"}U)xձO"[:)%8e=!9tEdEb逓l'1S\U2],$F,5ҪeSU7`r3FtW0a~e~!s+3CLkB~ 2l|?jFG:|:A)V)5XϷ~0{v -endstream endobj 5521 0 obj <>stream -HO0LHCY<5q&.4ą*ހeJ)CU!N'~?ZDIUYR#M">E~2ɏiQuw*XYA^-O>HȧÛ\5#=z52JR##Lބ;y slM>zK7!nb( -9 *X(9dnHe.S L-{S ư}z;k54#N(M]?ȼП[ywmѼpۜߤ -h(s@X FQvɎ!F[F_2C;O[6y\hNW_FVw.kp2F2`WhSqPe `p ˉĀdOfSߐߑ c0B˶X_~o噾C -endstream endobj 5522 0 obj <>stream -H10`# -$ef*M"eHI$)S*Ar%M q116Gh)JO0<^`uΏÞ˼(*=ӓ*xx׷}UĦnNU^EY~=gл3}`t޽doׇoUk$»$ )H' (  I1V]j)q4F-NT@3mdcM'h'9} 4cA:ې7#``ElKh?hVhBJo (S/H2d3r@N GR}sʎsW&k!]GplB}XίQ.蒚钚']tYX^pT!/P!)Pd@j }58>ةc`d2`F1^R̀=y5# o`53}O 0#8M -endstream endobj 5523 0 obj <>stream -H=0MM_`3f>H HLJ -ԓoB`ƅ`??;~^f:Q_~tm+ZoN~/Nc/N}s8]/Mo^|7~<6GwpaO@Xx ~]w۷|N k JaEqb욠atJngV#EIqN>3R\BnFԸ`*K. 1Ò:ʐ9X;jd8JNf|m yEYF4_+ݮ^^! UT18md,8cұZ%] -Ex5:VXh@wcUDgwp*e#X#544͡g!^q8t~0, ->Eu-pv¸C:&@"B「Ey.̰^n|X7U}}.2ѻ0VVt 0 p~uDwH=U:/Ȉ`jV:b&Jǒ -endstream endobj 5524 0 obj <>stream -HԖn0` |Ȓe mz(Ny6cl>E[WgPG:kRwDnNCujcGcuX-Ҡh˫zn [M#;=i ڽ'Mۡ7D^?oZ\Zpg9Qo%1^ KDHܶ!TXer KAՐ9ޒ 6,)6& -OLdKf +bGh@ܜ 'o=&7GXH!:}ְR^0XVGBY>+-/|id93"ߍsHa}YC^?{^> h%bvNq<>&s'*M)hQPn'14"LGd#Xүd/uAV 9ԙ1KjJdtQa)Y)^Jšb]#Q]RuHc_|C%~b -endstream endobj 5525 0 obj <>stream -H10PR-]|L iHPq܂!%M,qSX?l($oN=?P~#nӁl#o -!JCQ||F0@-{?3}eu7}tw~~W_iŰ@$RP\4 ,`H 꿃GǰLCnō:^Cf_.GZA!P " `L+`[3/egm $Cv`ɋqB bS"W @]Hb\ =  ` G6t ,`$ySSS]@o(@b9`c`bpY!RfUlI>stream -H=0(pjM"eHIdSHԐJ -҅3oY)VQ(F1`,e)oák򦫋㱬jTZ>ThmXmԝ_j?T)odմbV~8v;LnCCsvRHb*$ƙ#ĩ9ͿD 9RxspJAņs^NDXq匡/G,  gb.4p9qR mL' -)@1b ѷJ -* -9w6u=Gx1N~!Qr͛(("Q[] **yԓYg=>2Ot@Nwׅu=T9'; -Drql4kR3$9}aRCN҄\9_%pJ)g +e|ܙ=秙 k/>yg^`pF -endstream endobj 5527 0 obj <>stream -H씽N0Ǎ:DG_h6*Ȁ G2zIξ` K|w%qbWIlaZ,+Vaγ>stream -H=j@'4ǨI 6Lr1,-13Adâ:N.mFIڔ%Ih^&!iԵFW'30 rq!xY=7ݢNo'\3@_Kk( 3 -,//2S+3 KS9,]4kůtE -U$"J~%Av(MJl55bnZ:R$\#2Bb 3-MB!Z0zi"FRRJTصȧZAwzB D5 -endstream endobj 5529 0 obj <>stream -H=n0pG"yv焔R*SvЪ77P\ !~Vj/{V8cѥ-ynF͂u)6?hasa`{!زGz'Jع|bgu{A䆾=a>Yg>si2ەb q+Mw2FP큖/|))2#9ACKXx, VC!TPf!M)cq69Duu @JSzgB^ xQ ﷳj Ԣ3S2IESy/t_QIzA-:?&Y^Qb'/ |1! -endstream endobj 5530 0 obj <>stream -H=n01 yJp!5CvڎZ3ܠW(JVMH $b˖@GU聕fXhfW&_=%,T Xgi29P_Pr Yɑ0s,b2vZ_ϙ!\`iJW^4e-nh]vK}*mrtT\6cV8Xgto/];z7H?e72xǤݷr%C -endstream endobj 5531 0 obj <>stream -HձN `.שɩLtPG꣰vC -&%.R8SB08"ǫ|EҌ"Ia xMrZ -aJXUsRl) -&;LY -kL)ST7VP?8^RJ u{ҝDC~I}K=RE1QhZ@d?C2S~>am`"(@"W)F ƟG#sD& mZoIe qW";ZoЄY̸9.X{Ww>DS_ȱ+cǓ_~SIͽFM% -{Zuتk94`hŷ -endstream endobj 5532 0 obj <>stream -HԽR@e(G^ BD13Zji5x+/21ƉZ |a6"x~9ġE h." {b/fRĒ&˹/g2aC͏K~65b6BFl>M7WmΙ"4Ft130s: ʒRRfsH031S2/wJJ{|oٲH;ji*SQ:TPw0[&Ta6EQyofcVbaflW2i/T_3 ^^Ք:k˭7pKN>WnCќhR#zzi'W#wUYتK -}جw(sXtA1N>~Z?G۳ hcop{o`v> -endstream endobj 5533 0 obj <>stream -Hսn0C!i`BJ[ کvЪ ͹QD g\t0?X_96 |"X2. #$`|C6~A_6"N.YPfd'o$"UAI`" #’RR!)HjK)EM -𢈵ON&RNMd""Ѥi0U.uZcQt@ Gx$G:I&#O֥v;.T3M5բdWcuxk-LMNr>(@rK"f@2,mH1s駟ܐR2 6B$ | -^ڡ4H|>tĐ% #gQӏK\o(dbR$jjDtX?$>I0 !|kܓoň -endstream endobj 5534 0 obj <>stream -HAJ0 . -MijV]҅^)G34d28 DC5OhHʜ%EzʄD9dNSQB{w ݆Lpnۻ\*qvI+p># gyy~}96;Ң FJ( ^ǧM~Kb T: =;P4i_~ߓzp՛V7?M'㙩'(K'oik'c&w (B+JPc8@c!NP.D_f~xc[Ef5|_T 0v\J -endstream endobj 5535 0 obj <>stream -H씱n0/ @$)m2Tj>@۱CvR_G#02 \+٢ 34kˢ0˄4 740lCB?YJ_y'i1[G UT iy3z,ڐ՝~)#~~|솈_ ]rqsՀc: -a1_c9""&Uq٨:*V,Z"˾`,` Ӡ4Jn\ UBUbpRFr:i US 9ʃ) A5g>W7wdDpG;x6:R -vVx_ |wMj@%Ma}L;X薓n {)Yٓ1Ĩ2:oy9\1rJ7 B."<'#DT-0!Tyی<Ȍ -endstream endobj 5536 0 obj <>stream -H;0Pa ^`eGe`q rM (:K=Naʟ8!uUeг/a:6-Vӟtjʱe~˦=4{ANt]CY;V;=&sO89q!B -0r -煉FV-݃]ȫ&c<$w;ƒyuDyuw|D<'*q|H6l~#f\3y ;A,%[  TLelIvm9c2 17yÜ: I|&lή lk=fzQRp1W@c6 `HK0%ͥlHI'r|lvQ !\欠x5[r1+W()Xe4Cjn70w>,%ѳ}/П*d 溑D?WW -endstream endobj 5537 0 obj <>stream -H=0`@r# ,S!&RT9[$Jj r%\Rxq;j&Quc3~EӜچtU4yQѮNM[(*P௸=emY7&ۮ:SES%>|E;Z##>" @{!xBx&//JtNI3ZJGrBIlhGGt&Ƴ"eVt^5WW[z|0RĢ)Jψ9I -Ck=sdlP;Tmps -xK9?{^m_@Q-Q1.*,'yJpy7<0.Gz =6Wfqm:G_^dWlq1Tȸ.ojLT_U;_1^򥼢 -C/,~g ;&WZ}O#Ћ~v/:?o8/53 0 -endstream endobj 5538 0 obj <>stream -H앱n0/ A|bbd iz(NY4 E;GQ߀#A.eRyg$FLIxdUJܕ*RTeFdyW)يg~ۺ1T]nTmnQȍdBJ{~b0`G'P b X &ɺ$8BJXB*J1K6C/)₲.؜\' QIF@u9ut.6syN4pR|%E)|D?@<9"v79s9tL:k\څd6d&<:Yi<Һğ1)?|C_֤('S@ ɍkBzB &!$"/M1H= -0#d -endstream endobj 5539 0 obj <>stream -H=0`G." lf䧊Hlu2Dڃn2;~mP`,EO{(aY*n$Lci"J,> ->bXVYӓ0n,4s>#[1;ѩ`7E.L-:T D&9]]+dY"wMW9pXcYWKwսе4_cma| -wvvKmC\CSW1ku?õ"loyyV|0_I_7kԻ.[RIO 9Vؐ6{8l -,6 -,nf8M,ǽ l -ɝyk-4! -btog_N^:~5; gOlN?f]"v7{_{yaw}``l -endstream endobj 5540 0 obj <>stream -H=0`G)" l&d'*S A`K - ZG 7t)q7,K.&/ӳlE -VCslU[UY5;yQ{Vys-{z)uT!/Z69kVt|iV-==ҵra0nk$[⸧ |1Jc&L!] |hbGz9O#N9 䅗@G| -M-$hQQ`ҽ;L#DcJ  -$nK3&=[ɇ# NާطDj2͚Ib&d:f1cTD!RKV6dž"H#C /=7 <P' ɑ[B$)P善LѸYU%NCKzTbvReaB=/kv,, !"#D_բ7w~N /{~MfppwMfLn$Y$8a`$R(~l⯺J݋cy{QH ?1g'`F& -endstream endobj 5541 0 obj <>stream -H=0`RDr3G/dIH HLJ$@! ڋuD_ifg?"9MזMݜޗm5m?S_V]{f쫦mgW5yߞWwIvxw : ?}yOmlRxEfɄSbx1fS,0ay!4zSa-E`0 =8ߋzIW)U\㙷s@Vx3y4| u:t_o.̠\AUaxR ّ1M2VS,s^T9YݵeR;ӓy!@_ 7T'OX.zC~,ٯ3OmkZ>t{o-K_OIN* *m1s&b`%!^;#gh՜!O&.tPHM]#xufqHX\?^v\ ?;WS>= -endstream endobj 5542 0 obj <>stream -HN0 8LM`'@=zhm<#p@@`tk_Ͳ|[VB,,R2<=Jp1$ C1W[y @wl2 `e>y~}`9k*Ŕ$N0kD2 PMrJr/eSLCDmѦܠѭᎺIh,FC]_,1F߷s)5(%86mREqFv_ßrbQNGjJP -xL$)nƮM:6[͜!PIjtE@ZT#rl*u-պ}Lۉ^𾕜vP&ya)a &u -endstream endobj 5543 0 obj <>stream -H=n DZ2Dbvl>stream -H=N0`W"y 4MuT@"L@0'GQrebر) 﫟s=ץ.9~cc'# h~GOx;^%QBMs\7 -wɴ4=#>dx{KK1heWg1Y|Т5A|XdXs*!bES)2,'lZH'3D֣t^ -"XLDD*.A+ RSDr]AcOјb-KkK 5ܤ6[DRQ ) ` -[vEމRG[* - By371N0~q@iO&+i o]ZC))|Gj{4.㯊gՊ{UMBb -QLU!L6! T. -endstream endobj 5545 0 obj <>stream -H=N0`i~H$2 #fX3D]KST'_c׎ԧk7,$qYf'iЍn0^ӱ*ene0@z҇g), wCYaoswI[+9|P3[!.-$kKZKxm-eHRZb%'cJy'$1Aɗ)ZZa @u[V-Yg/D35RrHu'y{}d$r i?ɸ8LM`'áŔ %%rro榨^t<n )%1:ݪ a -/%+ S!/VfRlO]>\oo -endstream endobj 5546 0 obj <>stream -H=N0`G*y 4_tT@L@073D1yvc-̀R˫>stream -Hֽ0` H^.n[*SءUf~= hcEʐ/lꪢmPkʶ]myjN~7>;*z諲۪kL7͏Wx>-߿5_~xu $!>stream -HԖ;@Jaɍ[:{׉itHPAA,-RFb.dy]ZHs&Pcwé՗9tnͩ?}[|*.ǦV]NO(t|{},mݞbR~Z|>/ 'SDɓgHCX$B"$D E .Y,>,\O6EƀЄ Ye)m!sC~O"DGd[#Y,)jT;bʑlGNHYv 2r@7ȂwYU ˊpQ z1{dauZ"zT8X}vX, 2;:Hd䋲ꈌ\D%>QL Psil.Ԥ+GLd0Fz Y D qp5w$a!̒:6.̸Kcz[ͫ)&cj1!p|`(dl{!Ȝ%!*3r6D59 #$V amB1y{%O#iD$-]Kn:ܪC! x; !tgoB:2bfzBz9+/FbcZ1*{Ry#l&6p+d\]x~{V^2.p24dMG1Z -endstream endobj 5549 0 obj <>stream -H=n0p -p D%V&ICvڌJ,zmYt@ GOH'p yQ|]T;k(65kx׼wNv53W󺬛-/J^q5.\2NW[D%2.$oA=!!%!mhqT#^{ k|-`93K>9I]xic}t">.LɂYS=sR.Cb`(D%7cBƙ:ĄB9B - m RKt).Х@'o[aqq=ڐ-̜qϒ?/jƦ>ƦQOH*CWIL= 엞<ǩ_MS{ C>K?s՛Q"Ԃ&>-P:!NAGѯr -endstream endobj 5550 0 obj <>stream -H=n0`G"y8`Hi+R;mo{rzFmԾO0 Cҕf<t)8iDpN4'F>_0;k)U*IjGl3IpIY`{}F +@sD])‘; {YiмO :HGcN6vƙN̵$ܬ@ - %wBN|^D+>stream -HM0`GM\ISt] K !װ,Rd_)U`齤_xlzoڮ:uֻ۪-{u;v;=`{po޲ۼn6Ovw`珪mNϪ?aL]mKLfa Bhz }!h\ hOdaÒ= -CrAI  I:Liz Mb݊f"HRm3aAdqL_ls&RqA$<1YIi$,Hxw6!K`@Y\s(&hS̺+S~Ó<@ddS+ ᢭8159)$U;)\WQ%%u{ e b}_tG y7?1J k@!q[ĐN.4 -&i5]= z̉Vbtz1L ?W T\ABIqCtFKĠ3`IJt^t fɰ&Tv?I!O`2u>/Uxgka6Ӎ1PƞQÀ`c7=9V6 -endstream endobj 5552 0 obj <>stream -HԱ0PG)Vr.MtH\%: e*~ThRX6xx"n99xn8z܋.E+njQETGy[GᾺ:~\5صm[>+QUG}.]˷/oO/Sno]jC(قk)FvkƺԌؒrԔ._!DrB*ʁR#1]P#qpirBX$UF()rE pR !aI)sBT$#Ĥf¯ןұT) et-g l>stream -HMj0'\`Ԍ3i E]mk=GY7u! a -}#/D<[?IіB F$ű*ald`lN#~q!R\qY9kQg VZX`h3Щ>96" -pM,ebIYQS|`eJfճ.S- Դ[CIrFM(?,o[[9%eI S3&8x" KLs%Nu -BHkvŵl#'t醮-anY@w"zeюځ,%kG8rq:+#X{cuNhЛf6ݲ)O -endstream endobj 5554 0 obj <>stream -HA0`GYD&G/0i܄I4DHֱĂkq#d`~LAPh5?ueYR4E[W7m]Tj*>*n 䡨e[7MS4us-PVNPkR݉_ )|LȺsçc6QcE{kƺs- GʙP gBz^/nK236(Jy vfϴ:Rаrs޲9g^g|,BBqKBB@H)VFΌyd$TY':.PC8,\7e)DgIVj>stream -H=0`G)"L>stream -H=0`G9l&HKXJ -V\H#! -է<ٹloMWYw*nkVe{ۜzPOmFc7}[]s|S~GzwwzzxͪswUpyþ|^^RtٰR -B4!lF &9.cA3ẗ́@*ܧڐ Oi(7&K״"C(A iUqBT0C;Da8@r j"CV戦)5%Jh<+M g%qb9=Ceiش#s씴oɬd8ЎxkMoJT.r|`%@y])m>KXp, ZN;PL -Ȥg3i5:/-$2Gcȁ)G|_ Qn3Nv9,c:$qt D18w1?}uoohU -endstream endobj 5557 0 obj <>stream -H=N0We%G/4N&"ȀFr!l&6R@ Ҽ>W$ &$&rUɊ im*$T'T8IVX"6- y$\|0A -<>j;<~VFO"Uy⾨du]ѥâ{G Ѭf 6 -1j -z)I3 BT隄`!FoIzpP!$_hj:7Weō[U( )ߣd}v}Q_;NPU튖dnjLՄ0;fz"fFrhfpXXi]y;s$>NJmE?Ua.?Enл@p -endstream endobj 5558 0 obj <>stream -H=N0`W*y 4㐟)R H0q`dl(9B E5vjѪTxh/zs^Dd3' =H:=F4fWNL dDZN^,Oy| >vo_%n3ή0@ԔHy8-xVf:2:B d +h+a@R"[F\Z -"j3]R,`tieInRY*O=)3)XdE=Vт!jq9W˥AuxamޕEF-CjEɪZյjd)\x -IO|+Duiu"mms j}yU6d!kF1U)U.ri~j:˕؄jiļubm9Bg/gbw:U{ -k)|6(  _g0Ŭ^ -endstream endobj 5559 0 obj <>stream -H=0p# -$7>2x`*Djl"n RJM8 ˎ &Ύ" g`D2q5/Tٟj5v]jgj MknT@f^PilWd!84u -)W aB}{'opȀT!51eCS`[:8QG2A2`;X̱wcJ2~G3]b?i ֹpZgO;pٽWM%kX؛_}{/ɑ -endstream endobj 5560 0 obj <>stream -HN0]e%@Әq3U*  &@0'G xv\M,hlȒu!y,NȒFIhN(~yQʲuBTڒeQ&?T`>#Oxǫ;BHWsBjM^_٨ l6i\9B|!B֢|(&u !藊L<"pWOJ Q-@V %p K;ۓ(5N$q>n I秐2=᳤s"~@gd;Ow8'i!ٚ/9&!ԲNq -t]\\9ia -CoTp -Z5%IfeLW$Qd FPFIǣ mE!!2:O [˱0~+z8Ö;D =+"C:zc|++ i'i1Ͼ]mڝ C{|?y -endstream endobj 5561 0 obj <>stream -H10`\ a -i72EL(T{-(),l߮Rm7HAedVu{7eErYiWTyT%!cԩ4*S~.PT6,K]ܝAQ?]}Jϟ察ac`"ibbdZvt!itDz"_ޔ>rb;BOf|R\iT:I%3Fx LWW.sZ5Tܐ-6ۖoKmKoz@jWĀ D_ -GD(mPIPfѵ-KÃB'Ʊ \s}fejE\(sR"KxZ$ XRĦB &bSĦM9MP^\QťK'4P4G"zGus|eQQ͝_U{s[Y ?D,UmDSycgU`(* -endstream endobj 5562 0 obj <>stream -Hձ0PG[DJ? n6PE:@b $u"QPKO:qCy؜ʫ97eT-뺪*|c[wm]==U%ۿllYYg˫/k1ѥDIhR Eo!x]|s%p)cN1coB m4a]BRQ !1 >H|J-GA Ȼ(Ϙ27,z>S)%F -jpk%F {4X˘0"Y¸B*b#4Άr0 El79u  7!E! Si咘{) !OPS9!<:0unލH0BLC5>#ĕgDz ˷N$kmsK`Aҙ倔Z*wa )q:|QTg-V3M$w -36_|`2a|皩y.EXKY&5o< rdGۆ9/52.a_S9uf\{ N{+ySW(泯T1arz&5.(%.B1uًKv`[} -endstream endobj 5563 0 obj <>stream -HON0KfA G^`N?+QYK]OQ,I$T3&/ A_qLbmdy02Raƶ1#t&4gg\0I,'MyRY$|l» -Gw& GWGsBqT]חG\]`1G-!_$?@9LD c"g"sty&2]ybN1jtژkqm. -1o*+`>aբ 8αj{hN]Y| -r^.unnPյԩ7:\Oro4˦90<w'%)迫sjƈ]`N)>stream -H=0&G/xI2ՠ$@b+nI+\s.`EDߓEU7ۋ_uٶy/p\eՉu])7܎:eW_Wu&hv=۽㢹|x۝/_ PBAI^+sD G(&Ms,uB ǘr26s Ip0Rc0r)ѱ[&{^`C=OMZqH(IK-ŠP]xAW':NcHLQ&Yfc\#OIK6ty^hSVB#^v-hs4Pe@:΅i9j<1qK*@b"~wo緔{m8& -;j-GOn?9N1ф8b]S -w?ٿx<ze?| -endstream endobj 5565 0 obj <>stream -HԽ0`G)"#/px?[ Au%9b9QP6eco}S C @=IQ0!1!(!y̎d85ʥ0'Lbv"o1]g$hR 4ݛ G; Չc({ Eukg9x|Q% -e9Sq 6*8/6HוHёf2@1uGjloA%fSs3[GfLgs}TvYW56s4ܭ/P{xh0uffyuzW;KE͸6suO(Ν9O/SpTYγ%ífԆ<:2npwoH^#| -fq,) *A_[\Y* -endstream endobj 5566 0 obj <>stream -H=N0`G"y 4S`sV -=B QM~F*Đ7}vg1hC4  RCd cGPT6g!,tVXbt/ -* -=?f| ?6@Z2.: f-H+<8xC) .pf{@t"IF|@)T1F䪘Ēe#Dlt$9.*2 *!Ĭ&J94XY>stream -HֿN0K#8 -B.95D'@4:C+< 8~-&]}RJ[r<X_D!BD~$dDpC?Hx9}i q"k&~(NO~HcvH]JJHv32b/ϯ4;ϭ%+KX!CwO 5͖ -Uۂ KwvKcL5G3#)lieKH/N̡lP*%JIOi/*kਹQZ,mrb!O= ;$7z% -endstream endobj 5568 0 obj <>stream -H=0`HDr#ؙ%SHPqvK -ԱD,QPrs\bϼ畲-L>ű{~^JlfWU]lM]4Amf[:aimŻHoE^KQjyL+;z|N-,q$evtЄ $!ʬ3`}'($r ?ʬ]M>/>ʒ s$Xݡh}?D1+<}`&< -U5oɰ uKhΜ}:>|Zhɰ י 8OK{.gTi,Ώp Qr`=b5_X˔SM֕rb$kC /CJߡ5 't~?8brH[=orOڗYU<"YCݳ/ >Ywvblq ]=`ܝmyfl/sq?4a;#}C 0Q' -endstream endobj 5569 0 obj <>stream -H͎0 dzBb\ lgs@bO<pshM9PMvƎ+!՞e$ߎf>"A7yZTy[J˼lJ]UQӬpi[[le?gy2:+jy|FWy~+}FDLjv]A@^b=#d`12123FEm5Av/UkBB` b@rD%6[L֪wᤏP4ptAƙd;CueH? 1a0 ƻL LL@K1/IYRb@GLX8\p_vFd0{{Sy-tqG̨:xc !#6`&yNrA!Pv 1"HF+a <]<`c\LX!q`$^f1 ##q݈j%c#D6ל\Ka#_wg$0~ -endstream endobj 5570 0 obj <>stream -H=n01 yO6V*CvڎZs8G0 \$1~f|g QҐ}%qJW)!Ob͸)"D~’"g+{H}N_&'^ -A%Hg_eF(+ -N.k C R>THSC.B(-,-QR٢~)Q9R"dy\yZ^[Y ы \ AF1熤t! ?oqf{\j,,YR","([*$H)lCvL)MTe, q%5}"o2 -iܐN-vܓot -endstream endobj 5571 0 obj <> endobj 5572 0 obj <>stream -HTn w?ō:`I%ˋxHZiwgoTcM{l>$;k$m࠴-rwqlM?AUe쓒w+<]x?{w -67nG4rkPg9 {#uZpBf@x^CՀF+ %eӑvwM*D-)5&(eAy$[(NBOaTrqyF6lpV+WH - -endstream endobj 5573 0 obj <> endobj 5574 0 obj <> endobj 5575 0 obj <>stream -H<1n0 P p`^ Dɲ4 HS$Sd̐(>BFEX+H -8 -[I; 2$E1wxpJeylImMA} $;Zf~_?'p+*x(buHw(bUv[o5_#OǁSn-AbVe -endstream endobj 5576 0 obj <>stream -H22ѳ063S0@=3ScSK8bU嘂U"`\.'O.pC.} <}JJS<]szrr -endstream endobj 5577 0 obj <>stream -H=n0m4`_ 7,VHD -E$)S$Jjr\@I)X5LY&v۵9ˋ$VsS-66,>9+%,kp[c3^b0o#֗Hx+"cADAh ,EI% RysJ;}0,h;Q +WǽD:< g#[Qo -^7/'辡l?_:L@;{J&?~[:[Y/īS,tQ -endstream endobj 5578 0 obj <>stream -HMj00@m'v2)ԋB,Hh #dEdF)^ i7Z'(MOqY噜}tuZNGj?W4k(YتV)%g+[f7%5sb% 1#P8BQ" 'у-!|H M[:]>stream -Hҽn0C H^x'RJeN}cV G#0f@q,5ev'K5{#/6˦rui^>)*j+mMnTUW+AvEzQ<ȶō4ZKDg_ k|d> p'6R&zoi(ÀˈC81fH)!IgFL'b?D p6X0~A1_~b\W !ZpW.0q\RySB_@3>㢇@M usj<z6!S)n Ku/ŧ+q -endstream endobj 5580 0 obj <>stream -H= -0I\ښN@`֛_ڂ={K~ R"HFX+!뤻.t\ HӌF[4ZL J]esb\ٰ1W r /@Zj - HMl*% -TxTō>?ܙF: yyyR5\B6`W -endstream endobj 5581 0 obj <>stream -HL=N@ 4!sL~7$R J -(9”)4>stream -H;1 DSv~nHJEpRlA+3AHƮ+MZz5'uN"uTsLw͜<4UnPǿ=1w+#Ia>Hޯ}`gqW -endstream endobj 5583 0 obj <>stream -H22ѳ063S0P0345QеԳ004R036003U0憖 -)\\z `bBC[X*$r9yr+s{IgC.}O_T.O???B P @@\\\C2: -endstream endobj 5584 0 obj <>stream -HON0p&,L#L/0Oɨ,Ltԥ x1#dA@ A%оR>VzJ8J>stream -HN@PL@ɪ&Zji/ƣ3$ Z:ŗ{2 %Q4idB:aZH*bc,3'oDhfR#=bzHRvTB%镜3=RItK4$:_)/$&y? ov?Q؁Gi ii~- 5x JJ*K`f;Ds96&V~"_IwV ]ŝlnlqˍxݬݲlSfrD =Xm|m+m@`#t$@'7y ? k -endstream endobj 5586 0 obj <> endobj 5587 0 obj <>stream -HTPMo +|\ i6Mt띂"-9higlv[cAqg2lYrhp`i9;^fL}D8p -B7ń@<F[0#BS<FWy~ye-{mf ?> endobj 5589 0 obj <> endobj 5590 0 obj <>stream -H=N0pG"y)#[}MGBB*  &  '#GȘ!HIJ*/ϱ_s0ZH&?hЏǓ(Tuiu`WOk -c]Cuq-3?WA -OOD_١4Q i"ޒO;ewRnl䃝Z7\~Åu ֝wZ. -]a؉xhYG:Ư(05c( N0n3u Kq:vYct .@:M;R245/U. PCgsk$xܻ ]I}~%JSp)1~"gY COiok/fL 0S7 -endstream endobj 5591 0 obj <>stream -Hܖ;N@Jaiaql$Ne) $rAmG\0wg14~Y3CND&ryr.G,WyJ2)2^]%fo+4KN=rxTW7e=Ƒ!h`;P={uqE Py(΀7+F@* fB -djQ`à*f MbNabt77 Sd d MCГJ= .}j,sX38My!51N;BRkƙ!\ "Q=3dzPC~ATؤmѠ.O+OAJkxŽY1N,&`a@@ONVK!ͯ21#` -endstream endobj 5592 0 obj <>stream -H;n0p H^r|PrcVp 6E MLUY$(A$Β,lC$[ VzKwY^D,L$wS9k8G8?<@_09ʈ{oHOr] t Ajyd-91$!SkTyP4ډ-Îb#M:@Mfpc%j$$r@R`PH]}zN,'X\| TǿŞd|n6s||roʓ=X eȹaBU\/d|˜bTӌY/0l`fZenY鑺];V]&9Fx'-}l\ -endstream endobj 5593 0 obj <>stream -HKN0.HSdD&҅Fp9% ұ-'񕨙?/I rr xPD4M;J7rejӂ(ذ˗nȺ$ %%Px}u -R%> endobj 5595 0 obj <>stream -HTn wcOTu.kwR!C޾EWuû.`L0:o A<3ij43> endobj 5597 0 obj <> endobj 5598 0 obj <>stream -Hͮ0N8I7<} ?#e䪉0|uBkx&$;Y)n 1fG)ӟ(8,*byJ]^DDsI Yc̿py$='q'etNLҏEI~|# ??1?^ފo_W|aNᜂM&*6 ЧrKݍnר4 TISBPW*l45-5HvRytS:F/ -cEž+5] -J2A97tm;n10$3ED=RC̈l'W)Q_PmӢѐӦ!Zxy!ov:(˖Akjt(Ԯf f#y7E ϱ})j .Dݖ:|U{D?(u@ ~ASWph9 3ntJے -endstream endobj 5599 0 obj <>stream -Hҽ -0:  I&*APGEg_[PP4CH[p!EMEƩVĪHXf3EBSCR0Q'H;dJCB& f bv<ϢJyQYCӮi#nh&ٯ^{ꍃ5r;Gݿ0<YJ`m `P -endstream endobj 5600 0 obj <> endobj 5601 0 obj <>stream -HTnH E --g0 ,&ݳ/Kr@1g0F/0uH^R t6\_O_;,ϧsg]7_^ڥ۬>nm]v~t}q{:/y=_[/ru{xxlmeGۥM˵ޘZq֮wv\n^씱jbGĞ,qT*wMƿyqQwG'bn3O؋8K#lEl+o-l0[Gņ?ovVV{O.V[mÎUcr~O,D.Ysߐ3E|g ۑ v]9K/1p^c (væ@^J=!^C/ =Q x=>Ag/Ew0x?&|?H7Wο.%(YKPS48_**PΫ.)KMRH*#wgM^N FaIc2*;+2$6xyV:b9O4 Ҽb^3)zWE#3Þ?^3 4#?2Vbo<Иg \S.tPblS y9eҾSF>]LTCNWlթ;-qc8kR`g]dSu0"Z^YL!^UCcbuxQ vƋ3 OԏԳ~uYL-)q -s}6br,^}x1lY x},ϡNq3_wS|w޺^3F~J1p - -endstream endobj 5602 0 obj <> endobj 5603 0 obj <> endobj 5604 0 obj <>stream -H;N0(#$`wsZHA܀+Qpt)SX1~%πX VŏcglNv$7\&{Gu."omŮy2Wx%|PLm.Ǘ'Y.FNRijaLI˦RN[4P`ɢ`KN7(EIE]_bjD2 TBφ ET,jC? ɩ8_"6F~HM4F*%_"%&zTEڒ1t-um/!h@@}Eۉ L'i&i^e[\M8xS66d=GIt ?j=_Y`k\uL.8h|[-4bG(ʻ!1a 4&1Rݟ-|R06S=Ʃyv$t&X _n -:kZ+Ρ -g+O 0s -endstream endobj 5605 0 obj <>stream -HֽN0CMvďD$x`ddQ3X G#^:9mYe9ݟ˽h.W\Lh>sA{ba!7x#fײ_ٹ,bv|y"K1\ȧ;9}fOF9N`=HW6Ac 5fn[fC~9gR+fmBqh6FzgzwdKrOp·*6Y\秺ڑf޹9Y^gdō=/#:I0 qC{}}(?U!Tէ^_1̢Џ~tX@5;?&LNϟ|V߉Ԛ! usl㾩á&{]wbC -M75?dzA10qEqB%6J 0' -endstream endobj 5606 0 obj <>stream -H=N0We%@/дi R H0q`dH\,G2zbq^*UM%Wy}_/If|t,YwH3WIN Fgnfu71Bb@jбjvǀzW DbF ih.;WvXj ~ט,)Ќ}`ceun+0 洤VP S N4ʢWOvζNF G]}0A -endstream endobj 5607 0 obj <>stream -Hֿ0pG"y#$/pm]K"  {`Dsx1K l,lbC~N:vnWǛd<ߎ#>5==0^0a/6lEy%}+VQ4UY.BKFxc";$~f&0ɀp},*$Ol4jbS0 ʓRndGSvJ]]IKo0`ID eĄ&$/-$5ITZ"H(m>Lf$EZD,("Sp7;/J)v)rUmϮ1* 'LEr^%Wc-6Pu*J ڍ(!B|ꞅ04H⶟!\;b295O95/th}]@A{D#WX`)Z=yL%?D*rp6Q{UNKQ+jBoK_JQO5->0 -endstream endobj 5608 0 obj <>stream -HձN0?piwߠA8xyJM@L~: f񎆉g<Ϡ7R qڒAY, k5qvI`K(ѮpkKHh7ƃ+rd]5MmtdZGqk\z0:LclZ]ѱwc&OKA˧8-La_4'tJj-)XXׅTܩՇqx`}B!֚t TBL>mHɕ!U 0m\z4LK2AiݏpR"y|ŷmˊ{Ǥ -endstream endobj 5609 0 obj <>stream -H10Mɍ_`g"- 1T{r tH8Wq5r)6,-.&y3ciZժi{uv|ثZC#>u:v.oIUs~תy/T#v7/7RM2Uth.hcϙmONEN6u_Z e}{@y/y\68n҄g<ꂍ14=z`$HԹ%[]Y_YSxr)?M)zp#o,(^`u "zP_zzֳT}9C=N<[fG=tL-8٬#0 -({x7.hDWԆL/J M}cRZ?pm{WT ɑ3N^3y z =;lxu/1 -endstream endobj 5610 0 obj <>stream -H=07"M.\`o-i)%Z>%.҅`?/eH3o7?~waso^ӝr{ors8:?~ll}ug_ ^f V$tH3PGM#'ƇR8%IIQO!*JȈxQzF:rfdc9crĿqHi D*a,QWZ& SM⯈X@{g2𼮗Wu]q]L\M0$ב oH73Y{LƵb&L0A9$BZquT.g >eOkEf&2X$R~'aBeq^$ê.d)vDRMx&Ѧ5 -#oywH-!1_.${ߪL"4{ʴuCAmvBgi;7TFPnާ)^HEōHU4]UdXcF.ES\C "ZB2#I̓h?PP]j^0Š.؂ #_x)OwntZ~WBA? Fee몭H-S -}` 9 -endstream endobj 5611 0 obj <>stream -H=n0p -p Ķ:'ICvڌ bM5b%O2TeHIPٔrW^췫ӮֺV۫] ߟQAVޔ\חEZ<^DՔuD9o?&p2:N='@,j>ep%;0?Hp2@)R\S~Fx`-%:dƤsI!q F>sc8dFzRFMH#zp?#i(h m@Z7+3J 4<% I |RB)X|,B> -B, )B -B,$U0SK O&e3FjFd"zjSF&c FðbژQ;32Ÿf2]c/7k |*gd}1a0 xȼAeN? >9_4Cx-7OGM`V -endstream endobj 5612 0 obj <>stream -HԖn0)xE @lˉz4@<h<@۱Cd7##(%yRݡ@Izlܲvae{kw[?r 톡v;>#ktuǚ]]a ]o& hBHx81Hb )?OsϤ*3Qȼz|{tF0p.6YL`0~oq\wQ ^ Y!ԕsH-0r'>&e vVL - 2H!G(21 #QG=F[ DLn"#*}o,k."JArC"}3.0p9=*D -endstream endobj 5613 0 obj <>stream -H;N@!$p 5&&RhVK ְ( () -b忀Gx4ILaBMP,n7 y.B1t MbJpwI([+y -yRPmd1qy#Q\UTBbxp9*M*瑿,~ߣZQ1A'5EOP Y["VuF[Ǚv}d?ҺrC98<> -R Pp5]$٭5P4+&}TB7Wq<TamvapzנcM83|n -endstream endobj 5614 0 obj <>stream -H얱N0e䥏P@%4*Ȁ 7(y"9W_!`:˥rɗ||)lmϓc^ضW,dJ=%KyvVg뜥 oOd -[pPUltzNY H!4^5)qȑ0Ah%1,spVr.A8 NK#$Fh@_Ĥu0QEJP p{@PEb\vU vY{TՙguQ A  $o2i; tU`9!;WZ7V1"9ey5UIkG/ : -X啔ITij{ȋ|R5#B-@HBR8@?^4uW#1\BGjTktO ENBE1E_AMεи%DQB3IAc_}4Lδ@vU;) -endstream endobj 5615 0 obj <>stream -H=N01D T/дB)?`h 9HD E4C/_lϹ0;X|y,緂=>ݦlik `s.͎/O`~zcSl2!_S=j~`E)kv@I~1_)#n=SB*KaEۼZVd9$_a0V>2P;&b+E -;jX"+7&.&$H(T˽poOU3WZb!Y W4Xo2FVUjLt"k7K, P*iK -XBve[(Ž%تYVYNUYbh;%of,Y|B= Q֎7vfW] N[ -endstream endobj 5616 0 obj <>stream -H=0`HDr#$ؙɎ]H HLVA#o\bĞb Xısw]k_l~35XwMwyh?uqKcM>~7Gv{}v޼vr{|~N_˰*UH}"s:`"A d h R>xY7UqT 1j1l2^A<FQrd4k;^̦ACn>z`O]+퉧&w3+r:GU3c _=T>\pn1v9f12*ρ!]Ŝ#sPےsDC8NѪ{ -4)xfb;m@@VC0/#,f{X›`zSM -gPVZᅰԶJmhaαFr>y,_Pmr𴜦p\Ǝt@k:G fK 8< vƑ-zd8+b&=cM=5?pCM1tޜ5t |:`ۇ-Q? o01 -endstream endobj 5617 0 obj <>stream -H=n0'h EG.ʏL͔$3$h׊7z&_ݴ񆯾)_]~j;}m?OM3[l, -P[6P\ 2`[AGXn+bKEiF2K[o`v`'jBKw0Xzo5SU3Vc8%£hj Xc_9ޢS19`_fs"G!;dzu72~U62[Ȣ -AB?1 Xp4ܘLe)+tf#Sjuˤ=5D/c:(韛</ïu㛖,s6V.NĚ؊q -endstream endobj 5618 0 obj <>stream -HN@!HՓh)z"DO>z3ģ`|9F?c[Lt>vgvȆ(pv2rbP<\N!E=~(neE"a<L"HH~E#d3P -5K`x ! 9!uwi^yi/YcmٱscUsRÕ MލJZ|qw׃Ϡf~NᯝҸqae[nm'vK -|}~7nwVo̸^Aj{:V'q&Wܑ3ߙ2NOxSc}C\F| -0hS -endstream endobj 5619 0 obj <>stream -HM0`."y#$PFHHt+,Y uM ecgv‹;/{Nwaz{~p: N|~=k8oNoƯ_}⧗ܝ]ēZ/U#IƆRT3eh4ⲰQv!P p!PЅ@A:'L - 6t+?,oU_e+ ȓ'R;uۀҩs[]UH$cR Vj:Q_;[t?./ -endstream endobj 5620 0 obj <>stream -HMN@Gh2h *]5DW@] o2G`9&q޼ (tΒ'>ObG:['ue{G[QlS5k U"0*|y~}П,|J9x$&KF89QTVRFUvRoI"r(3RHA@@>C- TuC(dW -Ť%J ӌ -endstream endobj 5621 0 obj <>stream -Hֽ0Se%мMJ`shM3X18*X R'u}Oߝ?޾>3;A[</l .l=c7._}dLKN]3\Dp!E AYh*3qG;>a;" Y-Bڡ2HZ0Ub2T Ղ,VNBf33tpУ &Zɢ\Yרdr$}VνV YBfx!*Y\JE_v1k">{1!TŤ^Z$L)FJ+LaxU #SQλOE՗/C -Cs: -8 Yʭm!&3h4ʰUl&kQqx?wF!$`DL "e,Yx+"ĄT5OR~O(EY("nWrW|jhҚZ -endstream endobj 5622 0 obj <>stream -HԽ0`G"y#$/pӆKh` G󣘉Ո%Cs||sRxhvݮ5o}]>뛏"5{u->|G}>훦˦񕴩Ebrj!r΂^t&(gTB /iBz_!EZ{B0%'"r-g1T08@#)"s4wđ->1 kCfAr`jӞ.;(Ya2X6?Sh\HAvԉgŨӜ`QD\B$vJ!AѤ@b)/7%A9_ӾMN%Z~Hݭt\%:S)uX3=t3'QN.3M]7n6Uҩd?|tLQ )UӍ\GLaަE85/Aq>>se߄h5vBx*^8i { Njd?8*QEV0 -endstream endobj 5623 0 obj <>stream -Hֱn034O~ ĉ5H[ -S>혡E;&?i#y"5-Z(?']ol]y榹ssWϵ*jvi(bP7[y77bsuY}ۏ/FL!X%xTҩ"ɰHBA.j/Z -dr3t^9(29_aNfTFr. -$SQCĦ2\z˸Ud-{bf"D058')SY ѳ(h#y[kK^,B4EOzQ/#) )Ɉw $o(=d(HWQ:FzUHzQmŮaL :ȋuHDb8!y뗻I-hS6 pQ^ܨ˚dLD~A$և:* )Ѳ.y$APc߰.ӎUGR -3j:yqd'sx2D;LD'E%28 u2o# -endstream endobj 5624 0 obj <>stream -H=N0`W"y 4iچS`aZHш(Ʃ4/Cԉ74//Kd0M"݄kݝDQ>bLw0aݴEw?}N;o4bjC9i_חG_Pُxze$33VpuMqa{4\Qg0Q71 a`j'Rv* LL$1B3DQ=NtݜȮ>yjI0)!WY5K-*p,56(wU d#FH+;BT5ȗ-8)t{hJu JL&(jT!*eODMPf `R'C$0d?I? -/7T}V2 G!XL#-p_2W{, -endstream endobj 5625 0 obj <>stream -HտN0Hܝɩ &:}%| RGlhoگ & I^4_dGtE)/WHˆ4ZZ\\us M8^_s+Xȝh<`O3 -BӠO\Ky|˱+JN6VcY+0UAp we) -@sa/QXnv肽նKYZ\>#QZL͡+`wY"F׳%jo e -endstream endobj 5626 0 obj <>stream -HԽ0`G)qݫ" T<PRx ^ňpG2Ed_fn.vNflvmM<k]Ӷ뽿zwrw qgWk![y}M{M]GM+7g͇c)gQ0+DI#'X $j6j6HjQp4-.Tڈ'X;#o|.uyU̾žg ̎װxiV_ݣzx@OOPjXOO gMra]ኒ'@-[ʞjUscĄ&D# `Z64'N ؔ (8ߖg#az9GՁ!0 `*jhl64kO;WG7 : uV`N7_ݸm -0R]lZN N.Ol|85y\my?A? -endstream endobj 5627 0 obj <>stream -HAn0PGYy TJ.ЙI!aC+Pdu,`(>YEdcO0vQ(yIٴvݫ˾uQŪot>篷]|W{mb}mۉؿm -k(+ҙ̘,3Ft^QtcHB]S2BkGNvcǵgbLƒ:szE'hStd*~4!93 K^Khi }jh(M^yٙ,؛raQe6QB[y=i-_J⎁!5D7Rbꨪt5˴UNHRiDT0DDC C:"ao^#J0/ )HE1g|ND@̀4P~S̼Iv Ž]'KNf{5|p* E.J Qe; -endstream endobj 5628 0 obj <>stream -Hձ0Љ&n8VV *>࠼ f$p²؞$'C"&/;n}sqx~yb}ӆ+O½v<'bi~i'qz%of4XJNtigFN'pghxIo&# goLH!mHEBvd|G? Y،9II,+)R,bp$9Px%_v̥4HBQU|C'gQu4021݄x38ܜ%MB8}BGxS8 >E!̈́iߥ#7@ތ>?$SҘ)Cx1S(\~jN.0 S%"6yTdt#5BaTB~5/mZ\gY-%Պ #BfLh{H0o˷>oQFOSR}ONx/~ 0" -endstream endobj 5629 0 obj <>stream -H=0`G)" dw;SHPquXQPr! +&y~N^@@f&_4]y[/7m}o鶹p?o7mm[}hߜu\yݴW|{IEçw?^q)#c+TQʗ}S0L0`Ď!ATi -HR @&Thz B"dLȦ椠,HzDRS$X$Ɗ)^$6\"xI$8 At(g]HNt5='^$Y$VqD$U$AW(iBv>>}4l[@i $ -d,%e.aI=/p44QuBv=UM)(tURy 3ͶS I &M; g=ߑYJUx")\G73:PGDw* -X8B d@VTá $))$Y4!I#,|dB5Z9:>So/__ A. -endstream endobj 5630 0 obj <>stream -H;n0 ] 6=HZ -Sf śG8r Ȉ/)P75~x=\]v<|r&ݓ3+dwfyx|yG$]&}I65*]AP/k+jHm Has%Zꝓ}PHP3ek5: 4=钴wIT # iS -\)8G:y(M= bjfm42'֨y)ձ5T̯&'Zi$2ZJ  IG;3)4CJvqJ$/2مb9n]BaL^$1'Dj掄& i%VF5 /V#h?B)erdޏ'{Z[:!7Qt! g0Մw2Pt&+y`,y% -endstream endobj 5631 0 obj <>stream -H=0!L;HPq-6X(>!&,3 mE(/lwn6fs}wOzg}*{fȶoz϶/wu=_u?}yώϙ9s Jih P2I 0Q/rF^U v&|5t>$1QtcCiTE2LN/:[USp{hxVI`og|7.<_!tK( s>#ց(a@s -TS\p>Nɡ-< 9H E΁}]&a#)@>= VkjM&ġ׳563 {g0+;/"ՠ@5hԁ -%E eh ->۠19Gtq !GKߏ)!fGq黄>Q:? GkG2o2_C<pgA1cj>7o롋n][F\n$BJ:WOvH% ƭ2őaa? -endstream endobj 5632 0 obj <>stream -HԱ0ЉRDrOnX$@ ]P?ş҅ec{l'6'$$\$zwOnmbj] z}z͓m8C^]6㏧;vxWa$)X - @]Ԫ)6tSY[jU29hDThSs!dT8mx絼tM=[(#bu8 tOӕFNW,]̇~RKW>l^_h ףI$"&3ܧ.gWi5[IV!ٲD״~m`+p+vD_jM8~sk,kG<4Dc*KͲ%5fvV2^EHY`qGOXy>stream -HJ0YPK?mwKª`a=~OChD:E v.kvqb [d0MXL?B~;[LW[ͮ7)0a1`|b:e=8Cma$=~h6]S -JI(WS3{C7R~uWKd`ZXKK7.ʎr]GƲ6l)Հ5K1}Ǚ;_$l^WGsYލQP#z?O2': k|`7 -endstream endobj 5634 0 obj <>stream -HO0 -#_`{ݥk=VAГWA=zPzkE"#L&vR\ ?n̤ө>/ÝPաq/Zv0T_ܽ>tn9Ӻaݴ<>stream -Hս0Y&ncZtH\EWR{^ňqGk&㿙$Lfƻvl}sqحz/W2Juq]_=7$6ovWM{/7o7؜^7_>iq~?l "h.6PAs9 0T ,,fK`0P<!X(@HqWI!g,; ,Z@@9 K`yc;"^ໍM00026"!N⇻u)˙&ЫrvdYkS, >83 "0>~5]eb&5;Ac:v\ҩU@Ӽ:Ɣ?N/Db1ct)eG`0E]'"@N^j24b'z%&\>8B11^Pq(gGH[~JS~pP-O*O}W 0Tá`9 ԏAϣ/OV` -endstream endobj 5636 0 obj <>stream -HԽN0s@G/pr{q"j"N>A3M|-G@|(9 !AÙiIӿcsɷ.ql֙um'g'qCڄ\fN&_YB,-v;)| -endstream endobj 5637 0 obj <>stream -H͎0ܚضRؕ"- Jp#\~4#^ď`K${>gvbߞq6m7W7}s{7v4n[o67w^5qo>W㕖@^rFqH\sI94 /tk %t!u TEMz †=U.D16 ="H`Ne` .|l{ ,̠v@#{q#\TfπL1)!| ٞaO6{R1Y21dGC8<

<`9\|=K+MX -Jh &n+o58POY {Z<#qzL8q!ɬ(.B7-UP=9A [є+F&oQe_p9QrcC Hc^X:CePI/NH:#N>xJ1O5ʦ4M2hLf4`q`&~OzF{_Bf -endstream endobj 5638 0 obj <>stream -H=N0e%G/$ҟ)R H0q`d9W5r;v -*!oh/ʢFEt81]1e劑[2WTt9 elC\ސUJ ،'1 Wg0=w$="h>stream -H=n0pp Ė -'iCdz4d$Q|\?R"(x?l?VjTߔ秒>f]*7t-͑nxYtex'^+=~Z `B!)IB:BXBid*H3 d'W,"m LQ0'QS˦MEjRRK&G. -Q >E]1٪2))Lz#  2uGRw |wđ oo,G,>stream -H얽N@Bq6<qU$&^aZZh7G [Ӱ۝̆t7qg*.KNXRvirj'ޭַqzذ,Np=98@5@a/YӨqQ)1$0hB;0ZyOe2!nk -XrZ&T.L졆"0|g) !2ct֪;"RĢTLK̽>+:Ư"gTCM!E^-I(k&V㝌oiKky -:@ 2tFeBB9Wvʧʹɲ #ZXQi[|"EU. -QOq&:_3P~hVM9v*JjW[W}KP=âڣהZs1!am+b4Ӣ= >ŁݰO[kt -endstream endobj 5641 0 obj <>stream -H107JM\`3 T *RR:YQp,,qpґ$߳fZmL_d'Sv.+'evȟa㷢B|̦g]wQlf^l_fEy!We_en>B6Lj e76|%S˩8 i׆R-3rrRH~xB ARfi9 +hdt -51Pa%)N]sKVhè5 # RR8XDۜM#H\!zBr[8 gouDIODI=QRDzISQ҄ ~wJ |jA2@&rMpՌK {DMGHR3 , mciÌ>.Q;>ft8o!|<=@z'ጧQHsryd +'wOO鷾/#R( -endstream endobj 5642 0 obj <>stream -H=N0`G"y iJ6S` ͜+D9|:~%ljzZTku\w%~+ӥMLaQUq;\|\q:K\.秗{;µw7z`AsfhP h`"3cuQodZ8pfRrTfMt\.t4Ne^ir)9m!j -no`FL![ ZOE6Ԙh:`Nhgyo]jSW/jQAykԹ;<@5Mnx6KA'$:;aLBt70Ly`YRֵ>A4Cg/0u[z3H27tlm6MSO_o -endstream endobj 5643 0 obj <>stream -HսN0*yʀTmS()?`sxGJpžHT|Os\Ljb,~7?6Jl)ߍwG(~z o@֮kZ _W?5Lp .ۏ2sy :'[?Ժ5c`k\ee3@ָ1uH_ - 5an[24gKq%>%Q -endstream endobj 5644 0 obj <>stream -HK0@ՋHf ^E@HK XG e,ە\h E>/vU~;NOS~˻g/vAW_$r@V][@aɃk@Pʐ Ii@&p3b>ryX4UCUK-E|SLoZKo:hy Chgi?z5R 5H"d1,hB5EQfsZ0%G,4R0HKYU lUjQS , ]$G@ {̅^ Q|OԜ@/dӥ%@  >Vc^HOba#C8sD ج+FYJ_ X!04Yl/Pl&o0E=go|j# E-ކ'"K)>N/4ՂWU+(P.Aa9=Z am 1vIMM2N:i&1srՊJ&[ -endstream endobj 5645 0 obj <>stream -Hĕ?0&G_`g&3[EZ@b -$8PR^p"󋴋$?}?{ٴ?fvs:gv?vf_&u9+Å>iwlzuD__ Uho1!-a!}DHHƈւRE[bs.>;6!XjCdC )xѐVrAA= /됂+. %.fqvQvgFvئ -RATrBJCH7Bdkȡixݸ;"dX8[L5zHl;ѪoXTڰX:߈Xd%):NF -^#4 6?Z0!$GUM[fu ~S6Ul ]xO$%E%(': ^ 3"ETZc- W$' -鈺CshBĴv/Q exhIB(AxBoLpđƧ3䒍 RβE}{_Se1 -endstream endobj 5646 0 obj <>stream -HAj0P/ #8ʐYUvEJȲGUtn5VoK_0 -lIsUoM}bw}g҇aS)Dg:s;;8 -~sr%-UΏK ΃pb,3L!z3f Lzy}y?sĔ;Xy0[Axkb@.vqAPAmťCe,ubn䒺o ] ւ M,>23mHl.Ot{%4?ͱ_;Kw y -endstream endobj 5647 0 obj <>stream -H=0E"!L١8KI:G"G\bb=;~;J0n/vv^oˋpo}'ۣ{j^Kl|"Nbbnb{eۉmۏJXLi2AY {6EXl_3u`}<7)! eY\'/թ\$؁GKhgM;AMt7W$O k8nOU"]-11"PK2`.*|l|3|>dw w:`R/3eS(|\>.F -%iߥn|wWC{^A=ڬşmىf'\3-ϻp1bHaU:2os]EH J]__~dM%Ż`>Ojgf-SemLplXCx}oǾ$ -endstream endobj 5648 0 obj <>stream -H앱n0b@G/IaBJ[ کv(x1́S)C:4_l>N|A:d(6Qs˜{[AUB-C>qmXG?}bՐ3͍B(,,8J&9QnU;A͂h$Oe+WwɯL Q*L$N7w$ׁݢ1rQC2PR(kCecy?wܩeÜM\v@*A z4'")O"Ԝ@\. |Hi$ -endstream endobj 5649 0 obj <>stream -H=N0pG"y 4KC)R H0q`dĖ܄D t`]?!_Ð,bف?П>#vIzp19zuK .Lipx4q$g&T^ J2DbL.Z8!Au1B9SIlD՞~TNp?)*hRRF$c!VI|PK~f]T`ZJPlw#@R$+JR{EP!Jm4~hӉbL`7E)@Y6Lo`OIlPi#@m̮3'  Go -endstream endobj 5650 0 obj <>stream -H̕n1ǍƏ~.wN - qT<PRAMkQf&_Rxfgny|kj^>} :_u~,zGَ_boˎ嗛;<,6"=˛f4+@x²UA*0 +r+S%a$̱'NI@lTPBP &N~{JtR{Rc$e$cBm*6ra0B E -l|WX< 9坂.&7ÌJ2]7:YP䵨AȣohRcBhV*v hμeF()c;K -JaCR ;\<PC"504]ClYEadYEe`7ޢC!ڢCXHڙ6AoacDi< -66u:AL Gd?!J= PsB""ԜOܯA~k6fRl6! a}`D J}|\O|MZf`Ms>_[ST2 -endstream endobj 5651 0 obj <>stream -HտR0_,<yBc&N}utWMdsG,M_Tm%' %шhMN:i@S/l1H̛j8Oϲjt@񗔝NIYq}F+rOs" R[fvR.qC_(arL8(qWSjJrªhH2:ؚrL&!N NT(rr~vN)4 -*gE|}HS&5_3ڎe[ -R5oQf*3&[ +7L{N*8ܝz3exC LR6\NZsJ:Hҗe6_4.!! do -endstream endobj 5652 0 obj <>stream -HձN0Ы:DOIܒL -Hd@FS 3D Ns\ Hry8HSmM.<>b}XxyL EQl+;,y"KYr˅رxK;k>stream -H얽N0]1DG_mRH.D*  &@0ǼY6^#1qR - Ur8"yY/IrǘMܑNc;^Q=3tq]\x. E~^ߟh~I+<$ԫbPKASyQe9)4jC|"5^)Fg4NƢ0ESh3NҌ:F{Py> NW܉5N+f|[Dv3ڭ-7Z~ uRPCd 5B~„ew5(d5o!? !O/*>stream -HMN07aA G^`: -3j" ]yuBkz47 q!Ikaq#ݐ~0!t틣DZ>hz=xS~*> w6e`ylX9禗^חG^07и`A 0HI:)/&)v%=B gAóэrJJI(fPNH$1g+j(jJݝ*JB3Q\ Qa*MjMf{MU]q,;MV2&'=AD {#߉ä DKTڕɢMH4!yNɄDD'L-`G -endstream endobj 5655 0 obj <>stream -HMN0t+QY.]htM'^XbB@_N4ąoK>Ga%^l|xm}Hhy1 --UL]cyw,\EԿbIH9kSv^_hvA.%@8#,FPbPc - /-쀨tp rжwܿ C ` pPs@> /5lZT0Hs߀$`abxAF!>5jCnB(FT"@Ґc? 2i8#C/uuvQfXgeFo9 -endstream endobj 5656 0 obj <>stream -H̕0 @!~s撛 \[ -=@۱CvޤG#x`m\Q(Y"H9Cu_7wt~7t>7|4=?IZ*.r\5rjӇU#Տ?9!녂km"Ȧ  QuRl- -f" DY iX2 kX 򀥆E{wP`ow_G3`ޝ`l*Z]YR')BKpP?`)W#.M89Y/2OHoBj9 `uIDs$.(:MѐXgd,H/mK3ȠHȡ!ѐng"-J[CgЪEzgx_{zG}`acpdG/hצI^ Q[d5t.\ X,T \2b[aOѳQQME,m!|}:‡VDz֒BH4BNt-ErHMǯ-VW0JhQFFiM4EV9i+Vȏ&8S -endstream endobj 5657 0 obj <>stream -H=n0`p *] q2H[ -Sf쐢ŢCţ威GGHS4@0,Hnm[_ugWޯw|>_+P^j2To|/|~]W]7w/57,*J@YVcl$ز6ke;QH&T\L$4 -RH&4c[\$1H'mI -g) L7@Sb*XcC1zOK&T4R"S]H9vc_6}H.}H -UI*'y$YMK֮DN~ Q.0]ZID:PLIaBOO8AuO.O4B'}!x '5ƪsY$úX vsIFO(pgϐJ(mV- eNX"cATv/k1 Z Y }Dۏ%HbiEKCQn$+$)M }u1HI=#J/`?_@WmUtiJi%Sĕ=ƽ[7* -endstream endobj 5658 0 obj <>stream -H=N0`W-iD$z`dH+(;D1q r(,hDWAL$XoclUL .#/8YmS[&L>Հgp,pxCKpҷ']aF^hSnLo8ӄҮ? DQVM}_! -qEFghI|A2+SM# iW4F JU$:P{8؎9׻o&8Tt\QQ>UTMp u,T[ӛ]C6AVCgҴus-CV ]IM TAql7U;]j"D'q/c4g'__g 0a -endstream endobj 5659 0 obj <>stream -HUN0C$/ hJTH0ȣ2fls>_}@Bb؟>g9r,UN,]izig1S/j{H7ߠojqdL-,[E7_*˭jLo@Lq0ah`}<#TzhP5Lհ2NۼR]|>uj>Ol`5"H6C"dPPh9FRC)5ЖbUS5U J8 - -B[V t8ૺq<ҩxyo<,"aŹ)>stream -HĖMN0ǍM\WJ|H'ޒ&\G4ev&7*u<3ckۦmqۜ,o'I[FmnnZ.R.~7\^5\4\F0#4F^ވgH:*@s4G#Ǒ()"_D="-*Z!FHDg>h1X+#TRWZc0rҙVqln܃E!TQsj&䫶MLm'a!9QF -<\7Sd )H%dp* ,FyysQ)O Z}dU(0D 4%X#2q0 -WœۑI|INY>QqBgAiߘRFJ`Q,B#QE6pd9r;n&2(la -Qd~**H *WF#ÑꜼؐ`]B$ GZ^7,p -endstream endobj 5661 0 obj <>stream -H1N0We%G/Ц)ҩR H0q`d\-G 3D1:XP Ivblg|'4/25L–m6I*:c=ON,V9׷#T*cwK -endstream endobj 5662 0 obj <>stream -H̕0'"t n’j$@*()@PǼYŏ`HMqۓV"3x~s{>o]= x5]7yUvya7K^-'v~lhN66Po3~Ϡm"+2}t$sb~9-o]6W"_+QW N Rخ`]PO8Ԉnҋud:w> HN_ HR Oȫ9Z'=݄E(R)Pb~p;fS@TBg? &[JTAJtjZADHDyG&- A%D澶wHF{纏2GBEp6war\eF/:DX[AAy -N31"?'R]y+׫$H~D_{IXxSccU.1! qHT$ Wb:o/V2Tixmcj\43{`@ -endstream endobj 5663 0 obj <>stream -Hֽn0`p#H/ۊc8-Pҩt̐G#h HxNI>N$NQݶvzwطzvoo6]b_oݾǁo>ӗ_#_Q5!J$LJJXSf*dlUGJ J -4ՌX]Yo"sn+l\" )iDidosA!X,Y -"{>$ђ j'srb!FD!&,bޞmXwwDHe#)B&Ҝ$+K;wExt0?Mg$ /Z l>#;3&?HITe5!y5%͗RK' El !#p!%Vau +G Ҩ#& -Z6`q. ګvH^Sad}A"L#AO $HqGsY {@LoG0:6& T4$ O* 򤝬#>?XD25f#=G|O򂖞r">R})5) ?WG: -endstream endobj 5664 0 obj <>stream -HAN@Я$LLz#\Dk銤j" ]yuB;#^xIK0IT75<)3=h:g'#/nٺsţp~~ pn=΅N<̯NmW8r/3Q,-1'09AY]`J彃 $92N9b5;}30DC28fMjC`ŰTbuEjzPTPW)ʝUQLo)}(ҖSj01uv[RNKD!#MeNNχ](m$A rFjy%XI"\]6 $+P¡sp$=Я(ZPֻ\th+4)6~T - Zrbu4VTT3u.hqnQ&ksC -%&}'/N,<Git -endstream endobj 5665 0 obj <>stream -H \X#/pltIl))%h () 7ۅ`apywwqw>_O׶=3㟿 ?q>-]>?/oz)VrSpnVLXXO) ˂U l<7n]3lSO VȪ`]:?Msz6MΨ͙el^dʇ7Y`QXŴfx@ ʻ>>stream -Hֱn `"d)mPmdk!Eغ2z@PlһJWu(>~ ܵM/7]WﶍWMWjWnZ6[/G>~{~ۛ]ۮꖮ?kj^*K"!؊mH{9oj/S2_n|g~`*6@E%:K  DKL)$t s4qU% -S /g?Qe"⎉D@:9@bIrIQM~B4:;>7dr -Hvw6;`t D2'fO?_ G~ -endstream endobj 5667 0 obj <>stream -H=N0pW Ho /D$zssG!q;THyCqk$` sO:]\6 )E@HTX8/5v}>2cAaghȏB@y du[d3MalpW@>stream -Hսn08ࢵF@PI-ڭh|m]9r wCP6>"w$=?nCuno]ov;Y^>V>ޝ??z-ֺ͟9kZPx1@SȱzAC433Ec~Ar6^rP4֩p徙zz4i->_@j'*s$N<0P̅1yu.L[+3nB% 51/Shm4r3>stream -Hֽ0eŏmBu)$: .a(X=)}J`a^|qs⿴]kCsw|yqhNE?쉝sw:nfǻÙm?6a϶o#>մl{~| ;f:I" BjR ʜ%Հ>9S4RҴ˹1BVLGE@HA 'p%֒x{G"I;TJQ º)F"9+T5W -&o}K¹|l%bFBOF]N#%L$gI!A Y%a7h N'486E**3*aOPE -#lN) ׼aLق1B'JHxB6):[HHdMyMԜL`F;@?eџ*r1P@8Li)=ƒDGh U3\__Or-Ut!􏅽9 -endstream endobj 5670 0 obj <>stream -H얽N0PK@?R) "Ȁ@@0׏G#d`8vLϾ'UI~;]-b!yiQr^Ƕ\^"v'^dϯE|s{!̥/o(PҰQ%q_ (I14VAԥ[gq\H(%Y`jokkx.|Q5s*dJAh.⑻Z}B!WP)RNZv" -~%D(:yCV}qiej >"D5B'ߠqykC+8ݷOC`й~L1kv/1jtN4 CfZ!I^z~V6ztJi=Awh)EMF{iZ$;Ư*~S^ -endstream endobj 5671 0 obj <>stream -H=N0u@#hBکR H0q`d2zl_HТ 9$s[uVG_f$}Ҫ icr5 =p~M4P7 חXn:&mql 6eHW&v :ɣB1bhu1!9D;Ӎ(ca5`c[FFtuCvI#O؃2A2^-\IAQO"(0_kg5ƣL.G]pm)l ` -endstream endobj 5672 0 obj <>stream -Hֱ0PG[DrOX&+U *>J -]}Fw)$gd"@BE6Lfd몒<94l#w(iWMptVU#o?3߿߿>jyB7=?>/~9z&(124,3c_ FRr:J=%1BߦmH<+ǭa"82Ҕl,VI%i_)#S%91)]] !B%!-SFjIT%gJg-!ӁzF2!igd 5H lٜT|<{`"k.f"RZExqA O+Zc)6:Xj>_WW̗m=ՙEЖ -endstream endobj 5673 0 obj <>stream -HKN0 ]D&GH.O -eC$>] K X7*9JeUc;'!!i^;$'ll8gb:e:>stream -H;N0QDr#>stream -H얱N0/%P@%*Ȁ Gk'c(>[|䓉8r4 ,'wx˩EѢB>u)/Dv!TdSO/<&6حhHd -SC`l292{Phk982PP R7XZ0ʵΉ0 G#jOߦMpxS >5P -ڣHs0eUU݁ 07FmRD:!#Eu;BW-!#EL~| %X[5m*BkZ>AGKR\t, 4ʏEGqd *.FZ8/ŵ`x -endstream endobj 5676 0 obj <>stream -HԽN0YR&o@nX~HHlpP^qN:J^)GIacgl V:]]TE(fUwcv*UYJۦ?ߠb~QV}1?:-+1__߉u(("Em2h7q@MOz˚&))egW9]qt6_mBc;֖[3ZoϺ6v i n- -lnup$t[nsX~J_RmCGkq-^z -endstream endobj 5677 0 obj <>stream -H1n0` -p Ķq'N CtҎs#A(熮 @XE=ROTZjqy).r)OY.ÿJ_oåzqc?nϗE_rMQxz?q4)13d 121>M#T}hsxayknL`E-KXuCܞ$V;&a3'<'n&YNǢQuS{=u-usd',Ќ:G̻b:jS1Mć,nrl&ˋx\^-۔8@-ЦbCOtgw):;A+bt !G7v`J2Ctm:+tS7ƺO]VZ -t]x~hΝ)JenkoWF?&{m6MbBV5 -endstream endobj 5678 0 obj <>stream -H=N0WuGH.PҟRH0q`d߄(92D Z??CQ=$9sq09I4`9 )>aOl9 [QN0;w=EA,lO3ςEmY3dF*DŽ% TJ2bYT=[#,U(c -EY*N>m+U=# )LnR1?ioE4wT$jI `=oDJ ?;|:Zx:ԗ:J̑&Uzx9IJF' pHM." *LMJTrIo${nQ͈ ۛ4`7$)UzU֨Jd7 hSjȖ#$9p'T;'Ԭ(} a 7 -endstream endobj 5679 0 obj <>stream -H=n0`#$/.RRJPzcV 7<"ٵ?OJKlrWyUlU]*ߕNʋT?>n7=׌<.rۼ*_%]ztH';Chr#FKl̑{7v"(h8#j]Jpc-l2ḠܓDگ%UVה) R& Һ1xf$^v%Ⱥ%ELPNezSz*8%$RGݡzf*-<ƭZ 1X]xyHY䬓Ysjo =լsb<;t?q׸wL_Yb9glqP֌ĪM=e?U xfa}ÄgAxj?>stream -H;N0&GH.Ɇ}VZ@"T()@PGQr)?xqV4pϏ=OV*^'6^7jD<풽wyrgrR >&pg4-ϏWZ~~t%BmH<C YDH萚W(rb_(dŬⱶe0"V@S-, JĒe&t0>JgIĵnA!@bdg~6䉈R!d#_ױQun! 46mʲ4!o9h۾T LDn+n"}q#}#--V,^4!sJ 8qGPaTc"Q`WЮq^J\MG lAUAa U_w4@=/zPp^EC15S=h*Gh2h7b&agt@A*5է6ez4*`_ۂ>o4O -endstream endobj 5681 0 obj <>stream -H=N0e%G/Ф)?S`9RnecNTur^OtJ鬠IyH Zs>'Od:l\DG)IvK9.4#$+)ωtMC8$&(F5^Zh1D$XbybF|XK.v_TO6S>L~{Q5,ɐPdf Kq/F#O;|SHUt'(R2$qDE%A2޹R}A;LwR(Dm^ltJQu#ޒבK#G;K#3:9͵OW*NxЍjL S{Gp ( `i -endstream endobj 5682 0 obj <>stream -HMn07"7>BrOQgV -H Vl+A]`K|pK\K/R -Odıvozw/}#Ḿ梋cPq?<ټwWdZ~G67_%Q1*&HVH`(EMu8DaaH8q! tA&ɍ(, ,Pt}XGe"ϒIYER)Rr/B̼ϑ6HWBF&02qe@jIܷ(Dn`OI%Z|G%ahuPPcH8XE/J+d'S2؏v{D/QDj%b$A;->stream -H약N0ǯPK!~~PNȀ+02`-Gr|ua@‹۟};b+^l|_l7|zß -vn6+-y>L`ꆿ}\J -endstream endobj 5684 0 obj <>stream -HM0`G]y L۴*tV VXq1︆^D1{Ͽ̈Vtտ:npnSw WKg_=uip~w]<ݛ?ˮ7͚XBdae*(Ɔ|^Z\>~, 4`fr`l݂ ,4){`vP%H!h#Lv -7?%&,%r -C2 Dpm\>+C&va@ +aňG Gy@[s)a*/6 -2lYvd5W_ʯ;6h;צ[Ѥ{QVjbaMlbUIr -}Pp]4Wa*qT  j@CvG\OU)b4ft!bu ؃ -!(P<98yGir|σ9DDX4hF(̫[jaV{3 DrlcnzpF 0_u -endstream endobj 5685 0 obj <>stream -HMN0Ytɨ,Ltԥ i¥W(w,Hk;}/Ĩo1ee[|yT{-oή3zG ӧG͋bo$gWt]%ve{]Ϗj3pxCc*? -Aj$3$h! N" -6RR Id@" %O$@ls#pĶ i6/⏈(H - . '!4س 1`tRL٢3ϱD%,5$ $z(CÙSwjTz{V2C"4})t^)1"J(~[~O8BsEL/;ZuHHz+OV+H^trVRv#!cCQPI܆lx4F"IE/r -endstream endobj 5686 0 obj <>stream -HܖN0Se%@N -Hd@F͛G#d$}w*v?[g?/r)2V\AtM\uM"_".6QYur%;ű2av/??^Ev#2א֙a"4AF9"\+qvvB1dNET$A%N.=٪Rd\@@ }vt9?nQybTTBt}K4#_ GIO8ÿr27weJo5 :3?g(-U6a(\{a{);;z_xr-"DwT5825q*Zc8 M+'WcG 4TN<#lFǏTbHXJL;ZzMK*?9))ӯ(n3(~ -endstream endobj 5687 0 obj <>stream -H=N0p["y 6"F̉nC#R~K/b-bvrʎOf*;_V9Ǽt6\ӗEQg}/:WK>_^d9on'zg4 P *"D 0 ";?c48apaaԞgHGEeOʑ@i!UHHMD`Q /`YTHw&R` &ڒwGWq4FOW((Oߨ,}Q1EQ`ˀSGX8G~{jA@ŀmlPiXmS`ǀq XW)Ul)A1`'m -l*'Ro(>%PW76 0 j -endstream endobj 5688 0 obj <>stream -HN0!Hz#X~'U9&уFh< -ږqq\\0 C;M I2XE 9)%dFˌJ֩ $~ oXxswRWDX Zs(?SBhn aemuǶAgPMc@6(Ab+-{Pa'9̩ف<ԁ~B9C@0G뀀ctF Tzza9 o -D  + pe} D @xȏA+ޛI&"{cMܓ/@4 -endstream endobj 5689 0 obj <>stream -HMJ@)]f#$m -B AW@]Ptfk@k4(*Ey3yy3i6t1;˽b.ɮ˛vvewhŧWi-,8tu>>< G="XB9&7mFeCRB)mUX2<:EcYQ˄4)[KƲjU㉣`i贾c<_X۟p{ K3Az ʊle"XO z Zqte' -sl( -WK9cD M@L(Ķ># ˀNwVS"h˱K`"\ vb՞7*!61V,#uBnTa"9S'r,s@ᘇ.;LL`+0x%` -endstream endobj 5690 0 obj <>stream -Hֽ0XMZnݫV:@b $R"b~?\D1o9I4n/3w~_oNCuzq?7'9⳸Eʝ?=־O"ؾv?b{E˛o奰0k[ -:N4"P64uOIrr ͜ɔ`3ptt6er1>r -Oh8[K`ӚhBiģ%9(M!iUh95^[캹:2~4Ԥ9)`%FӚ$2i5cHcZ0xOeX? $ؚFN@:1ez& $ˤY@ma.cDOv/u }˖lB -pM M8ERؿ4RM@HCaλ/yM48J`fR䗵i7e蝭iQzxh*~2TLR` I)?$XV~.<3%jhBsPBV ygQhSxj.ulY3XHsh_e@?" mMc4VτDStO W|!״xuw. -endstream endobj 5691 0 obj <>stream -H=N0`G%G/$nRH$2 s͂o@2D6#xO~?l,exbiܒb^qzKפM q_eA Ƨ)MN2,IqiqD>$ϖ(gW$YbJ -l>`uuS0 -3%5qtk1Ն ꆂP(0SbBpU$mD{=Oh;ؚ{zmS{ԧiuEiYW4Ri#NyV(^m:A['ߺtf lX7tP^LKy-7~=F0@wEn?e7>Md -endstream endobj 5692 0 obj <>stream -H=N0`G"y ih D$802`%.d` %BHXj|_WX,Ȃ7gk2,VCqGEw:Ͷ|wH\w7yuM^_qu7M8*c`(=4 -4(QAT Ъ:ԪcH5bPQ#9: -@d` 53#-C9ޏ&"BCNM3 =PMZR%]H`&וݕ[=pn%Md a^>iV抵+ fCd7/͢pd_ H~Q얣'7;5%Cn-;w7{c/Aeh+Nddžk#.~L"eo -endstream endobj 5693 0 obj <>stream -H;N0KP_P&S`99Z#xCg/J!RZwni33IFZ}q'7UjU亪E=CE64Msi>g4%ꊾ<>O zXj w1Rǒ 1e%Qa6mb/&q2rm ՇDS]oB&0dc.VUtnhFӀ'3ۙXԙA;V SĚ'4u's,rv/vꄝ#s4k HSfd.C7ܛȝF78$=>Xw|2a/=!Hl=ME"څ4>f*~~Vw#U`{ӰGd0j -$'if8iƿ7DYbֈB~ܐw_ - -endstream endobj 5694 0 obj <>stream -H=N0gu#h~hӟ)R H0q`d\s2"1p!cvZVOgɳell4'-`d.sD5"KK鲢-^|Rty}rVWVTxm(AaV( (H 4:zIRk5ASAfE`Xc0!38b[>AA%z/tG8C#p'@uAQ5}TȽ3 -V}:(ٞğj1bz},tHP{ -4$,=j' -_j+ |{(au`x@u4ħ)^o~ b" 1w  ps^ƾpbm3`h1t^T~ 00 -endstream endobj 5695 0 obj <>stream -H14# `vL+ 4 -aAO~Gcև1IU]3xZafI*Unm=?ܵ{k;x'yO.aVpQ^} 7onޮ޾l;9\_> _ +IyM AN-@̖1O̞[1$58$Z35fz.`lzLE7~ 괡G=,;qV~AaG4+y&l)jPH+oq&tQmg:LY>stream -HMN0$pzCffE2j" ]yuB;q] BKK$N^W,I2Ol}6lV UJb7)xSOx/D9'1^dnԃBjlj,j -a%@*!!QH_'TТ -i;~# FQ4P 9hE@S[vꌕ#XՃvwDʰ`D~"H4x1=hL-$栲̠S5)Tiɓ#9"btИB@oH_1(Tq1T -/+&nv'$%gPW2 -$UWiWCzqB0jC#H狀 tH*z: OiNPh2ǷS -endstream endobj 5697 0 obj <>stream -Hֱ0PRd!솻=v$@()@P'HKQgJHGs)vd<IݮՋ}}yi`ZU뮼<=ۓ~Nn޾Ur{zW(h4P61,{_0l1/79TJ\d7ݚhKLIihGiv&^"{D.tJK6"!hp=ȤT/d-j_$ *TƅFn Oɦ2=-(PC0;NlN Ag@ߗhPF0P$N~*و/SrS)L =y$5{3.x |LTJ0 .'HY/^,0Y'o3ثze4tZ{!i[DԸA*͜ LI 6p 8ַ9eF1)0 e$[J?Ts4#a,zQ&mS3 S%6OV}@+zǃbE\E.$?ȿ c J -endstream endobj 5698 0 obj <>stream -H=n0` p Ė:'i Cdx4& )I='Y>O~OjdGf~ޑ}i>F4?Z0Zwݮ%?oIiۋ/D?ï[|UlLVs@/؍!H$ $:B콩/#3#LH=:(.3AVWA#ITO 3!Ԝ5T.!S.!ti4cr6Ll'ُs3V)IHz$U2Pd*Kh(O -PUQosuГ $P{>O Ѐ1TtV9&sf$ -`ɴ*2቗SXKB# -~q Be$l}hH^5a~xHR@I;&^DN A! # +oH =CR),"QwÉϹ'N2)"1HR?Qg{퀯_B -endstream endobj 5699 0 obj <>stream -H=N0`G,y 63EGL@0;*FH6֌ۉWR*k_^$ͲYv8_d{"3(ߟ,=ɱ흸HwpwtɦFs6^e9.pKd\m?]5L+v%iʀ֤__>DC-[`qɸ vfע[m֓{¦ɸLjݶܬ5k`A \aSh@Y3]3Xm)_]6Ц -8 w[~!ݕXZ5EN+dBgtU -)/>Ђ{RCGM (LBCߐߑtGߘRCX ^!M], `^'E -endstream endobj 5700 0 obj <>stream -H10`y\xF>uuLfH [qڢZ>˒gv]"Q(-'}ױ m^K]_W:ܵ/.κvvFu_o/wgzt}Ǿ^_%93AM14R0|ɘ 1(  Zal%C@bP3|C mgy=, ʵ" x!g+,KQ9v(+61ՖB@TDo%ChFA4!!>O\!lS6!ғ=DWC&`N`ms"|u#`/nA`}̋C]"P#c&n0t@5E$,mU0Wt@qrDӮ\$ˮL6'!EeArP°@bP`GUA8s}a'j?p H GV -endstream endobj 5701 0 obj <>stream -HAn0`#Hp0C"2 -)I΢R.HԮ!Q|/YXvl CCR Sž8uqqsUVu.Uyo⇺> /n˛;עcQ5}/N:~*~>Ώܝ@kviF`,KHUĊXĆؾc oj.XtX>:bE&d׉qtsX9C3ji;&qffӾ[3i-3t'=>lr6+MD&mZf E܃Yndi6q];;Akb}W;Na`қSВxJ':Gsْ.b:1Nh=:ltuV9Z6E/Cgh-ЪA6E eҡ!n{_N7:{rƽuG`\! -endstream endobj 5702 0 obj <>stream -H씽N0K~IKJ3E*  &@0'Y%P S'Eپ/cU66eh{d%b7]Dkl N<]A,I,wWL;BkҍK TQ TAk5c 22׶ -:tSѹYjw-(&lb zH)tD.8QJ Mq}[r`f+[al~Fմ>iy -P6scƨA( ˵@ `HDAxc/G }ct BjԮJFNE'oygxňkw>stream -Hֽ0R&r{U$@J$@K<G#.o|w{)V:xaqq:=^}⛸9mwyuh/,v7F ^޽bw~;8p* -г?3TZjFUyM-+dYnuG[D!`z:L]^{he[^? -]ȼľ -r/Y5-嬅&οAwTg``.Wڕެldg;BxZsm|uH ୛0ުM,v'ͦ\([myiI UW'%LP*MC8b>q#Y֚)&ǝ zs{ѿ'ȢT[3OoiYތ.|>&6OOB*g[6Z&ǯswP:MHMN/0jJL#MJ>jjZAOorh[l_t^y?y! Y }0!88ӿ/ʆU]vD'= -endstream endobj 5704 0 obj <>stream -H͎0rKymnO N<ps͛" |!3xTrh/dⱝ}ۛGz{{wv{8^o{w7:[w}7I^w=w_7bwI׋EwT|ȳ-&S:=%6nZ8!tR4Z={)8QZH:n_Lr4IWd@Boݲ|]\p Y緎ȋ(}mp 渉jB -oն$dNU؟ߠP9^$$C:Z -)^G)1OᥙSO42Pe«t\S=~ISZ? ײOY<;W - -endstream endobj 5705 0 obj <>stream -H̕=0'Jɍ\`{[B$^-@!*9Ed8JES$/gXݡ/UǮKO? b7{vwf9NN]ۏ/̸+mS 4PPL@HJJ`MyL6%hh0&9w`` Na\v~kLb 4Y@OˉJ - mxm@h&M\_CvmF,@4?j4v -z tF 3€cM[\bX`Y&\l\%>ŴTL]XRyԃ#o -|F@2wZG֒ӰWC\F-,5vWSh+g"˵3%`:3Y{$⧛$2ձjQ`eq L2г)4VJS85䀮:ƣRbvhu:jM)ݯ<`r< ؛30R!{ -endstream endobj 5706 0 obj <>stream -Hܖ;n1GP ^ֱ`ZNT9҅oEx- H.4K|rv;o7_vn= 6;={G7||fG»[`?l{wl{ȿ1g E -~0@Dq.#Xʖ  (楊E:im@"и &Hb -: 5ByJs"hUhaRdKHPj{-m?&Ն5"F' HwƣL'Bz jx1)S헇q>֤'BDh2Pr]WІ> ɳN)XzvZAUȉT̥]ژE`U>Ws4Q$U ̀.)B-t[>stream -H=0`RDr#LHPqA=f>2Edg6X=[lggv.pf/<=i_z7Yp#oׇ󼲏_ㅞ>ް~w/BO5M CW"ZNH=&NH3I 41H -Î]7ˁݷn0Ӏ_ vͅplթ7Oy -n: -t [ aoVlC za)&%w F2f*r`u0C1 |@@-p@pa*w4@^ N%9!*rT9j5AQ=z(P="H>/K[{oku:[ rt  ? {= -\1h B +W"j ^.$DxCT6 -endstream endobj 5708 0 obj <>stream -Hս0`G)" %/p;nitHPܕ 7fKQ=c;Y"/?x<Cx{YÝqw{=O$Vx#^ܟ_|{WM;YL X -ցtDlW#dV6uEs61 -RK6~2Կb64 , -Wgǻ`-^c󙞴!Qs\ScfnwDl f]Qώ+yelocxٚY1{A5EA|xACE쳡¾w]gX,S⣵EXR[x_Zb_)gTOb, -I hRˎygL P=SbM:EA3|EG*y|k-7Onk^_Ц:Sw?Mn߳21kI? -endstream endobj 5709 0 obj <>stream -H=0&G\`w&β[EZ@b -$8P"z(8^^$KMlgotH(~vn 7Op7(GYPǻ oFl_޿~6b{x5|8<~U|],L *@ I pO<"}|WAs0' ,q#88zFZ) -Sk}/`R#_r@W#Zr^TZ-q5qFǑ`ɢ:R `K([W/>stream -H=0pG." dW  * (;RX y6v$stjO\ץCuw~W.yNޗ|nOgu|xgu}GW1Z9i'D_p.8 !˫?n.9 t50m+m6Dttv{}@-GFXDu3ULHq5Pp޲١߲/9m9@4F֕ZNS:,0ҭ=SP&514(q+ E$(i+0z hIth -N X%pz40̾ 3qȉԌ!clc! 2P_hsdBF[2-l0P -Q""M"TdZ33-7q5ь)1o9uKHЁdt5Ռa{b[1mhzq`,HTy85,SXR3 ~yʡ^_{[FV -endstream endobj 5711 0 obj <>stream -H=N0eG/J)RH0q`d-qE"JhJ$<$d?vl f鄩'ߛ0޽8%Ӯ Ù5M[ͻ!%]0ΧdtdBFChy򈴾"FQ!KBJ,`>F Yc6oi,>BG!Ƅ (uV fOrDIh!րĨUR<( -?Ğ"xJܱL M(QSR54dfzұқȀ22rMO/Vf_`rH3kS#KrN -endstream endobj 5712 0 obj <>stream -HԱn `LG8Իj&uhN}c6팏FrzB_GaHB.&$Ic =z[hL8{as^Ht}A$tK"Gp-+nDLB - k\M͔6uqI|Di*Z~zBM%Ss$|Y.ḰZG2d9jf=wʍB*YT,&K~KܾHenP3O If:UODt\ -:.b./CB="s/1>?Fֵ>~O -endstream endobj 5713 0 obj <>stream -H=n0` -p Ķ8'iCvR`kh* B -)I ؖ>#IYU}hޭͅܞ/fozOwr^k';z/us9uCo?n`d. H# 9Ha,ez:#ib8;ӇS2g]?6Zl!6<ɰ\doD HKU 2QtɆ0$8.XFUK_|`=B=JY UQߪ -!D˜~3Ջ;2_^b nև$Bx&#bnv>׳_vB=_iy ÔJ{-=ӫ=LF -endstream endobj 5714 0 obj <>stream -HMN0Ytu\8 ]yuBk8Z#살Җ7qoA7-S^ˣ, -VMfp8ᚭPgZ53UζCQTmꎕGtu㒮nl#^P#  AOH2Bp dA!#A 8BḰi2!(&Dw7v" 6v@=6c6zߩ1P @6}!#dDHVH"Qz|A AG`"E1puJLu_RCoAjѺps%!!+;K2y¯Ml~UX( cR?ҭ[;hlѻvHvjOԴ "Wn^n-}`m -endstream endobj 5715 0 obj <>stream -HձR@e(GL2uhYJ<=%qb|-ph>~,V?[/y؏>bOl tX,$=펅~YxG) g~ݕv9xp0]J`cdu b< 3|x@nBnjia3`NYPe=[,,U$%)d&gnG|& (ĝ}1@lLv$`OگjurvA3"#+qfE-rCWNLΕ̤ aA#Zɾti1cc K Fi!_ߚ(Sr樃] `) -endstream endobj 5716 0 obj <>stream -HN0.Hz Z&LM`=z3<GȁP)MGKP w!r -w=3qlIߴ?IiJ^1щGwGLJkpݓ`V9+XP!U&. ת!5橳\4z3@s96t<@b5k)TG|%ZDp%\% ԞGp5Ӗ5%_'.wDǗ L$zMBr1ɿ3tV@t_Ѫw$ 5Hr]dRSc\l R/-*ϐ݂ ՐZHγ8mo3{ -endstream endobj 5717 0 obj <>stream -H=N0`G"y 4mZ-S`9>2fbI*j%ʯu0:|l<BZ"WC`XW0'།΀RMs mjڿ/ڜw^C?0b:Tбg{9#`0Embg# "X`r!OchUF/szK -endstream endobj 5718 0 obj <>stream -HMJ@( B\M֦ Yvԥ E3GQr,gg7?/R*8:yyLZ+zm"Z^~-g\lzhmhǼ]Bgq!#nɣF;RjQ -Ab8`f1`Ca1TJGQ_RmQQ'2LvsudJTXv$ei>!7rJu# "YaAMS0uu1FE{mi U -d9Z$s6dR*WAœ(":NH9&D&ÍM̱0)"={I\7&zoT۴,߮~]LѬB!2[TS[OhSޟν%:QIXr\DF'? ٥ -endstream endobj 5719 0 obj <>stream -HMN0GXt# _ʌ"5~h iD-Vyr^Fv')y&,ʙ 1cOdSg$bixssR푔D|(\P Yϥ!>R;ITi>b<$-&1ILj8f/O đj4TEFh n(G6 -HL^b|q6-^E(^ '$SAX -endstream endobj 5720 0 obj <>stream -H앱N0*y#/4iN -Hd@( G - KuaW lŷtSϋήǜ67\.4! HLI@掐%1j$FN>@":b$1ĨŬc$HL4R& etjXᮌ;ShatfMLWo -$`9,ŜblͩJ-=hxC -+8 Ug* -MHLvT  [ )XO>W wIs8DoANNb" j -m]vwp H7!nYZ:#3>.oO `>stream -HԕA@ ui69Br6-}H Vo, W'.2 ;g+oq ;Gg}潺}Qwj޽|RTj{yQ|坺9O){L6N=3Ji*J44TWԐnMՇš*Ptˣv#~gn*a0rX"㐎 ꢃ6:nl6F dHeV&dB-;JNFoe_ ? zo40YamJԅo/!pZ>~ ̿x[-0^? UD́ -endstream endobj 5722 0 obj <>stream -H약N0]u%@RN -Ht@FHlQ=X1_cqC;_|yb^-j3_d= +#z5_&-74rmNӂfΨ&KXKv!HңBbar[AHqO$KF{FajG5)$x^VbȫCC# B+ I+QHD" G]uKVd -~HZƌsjBhZdH`~n -FC bpMl)zJwPA ć}%D:tKJT4aœ7&L;4T\M0JC|?O 6`^B}mv#S֦Ԙ+Ԙ5 :W4:%P֩귛Z]"&ONKnCAG2!iёF@w 0c -endstream endobj 5723 0 obj <>stream -H=n0'hEG0/J֝ P)H;vhСǪz^Gȁ>0<%>Kz{|{~qھ:=xv{p >ݣс>vxv;ۏx4X`C\È-|` PXȱl~hldac!l{v13B,7q1 E_W&[[3 Ċe>uyn -+3el`6 PX/>9;m׆"^y(=QKSi^%,JSB%]Cw,oc\) Sߍd{j}^a!xvVn uUZV~ճv/ZOh_վѮmGm K4 TOa /w>stream -H=07JM\`g& 3T =lZQ,q)B{/O_L7=6WtlΏo[Ng]=toEnA^4Zn^=mZl>x'.τF'm*32}pE|0!z#K1`3yq/p;OYS1Kc`h[5{tʌLFQcqbcJ 5'0-<^9s xyT]"/:_<Ϭ2ۂ{c2ǐ`θO&)]^6f%獩7p7 5ǹ4;oxdSՖzf׺/l}"Oa}m͜g,Q*LGG͉.#g_ed -XFS/2%o̘CsyzMs+ %<@8ڮXp>stream -HKN0'ʢ79Bs>-4J$@,@%U| Y1y؉?H*Hx8_=3I OqאG -]D%/‹Y' P'P쪭SSpI 0A ''(UΝ1h-^q&JN#jzP]>;- -@Y$rKaU~#&(I1Ph' @ʐO`o8)zKjFd@R7F#V M($Z.i4}h@jP˪"ɦi(B]T *5}CH F_/ oߘI8 -D⬊وc[v>% -endstream endobj 5726 0 obj <>stream -H?N0_rm@ "ȀFƵ|s"$k=#uEClr-EUXeE/炽uaѼqm?f%o,Y -7w kMO):"pF`@Ǝ<u1D#HѸu#I*"0V" H5$$c E)O/DbSl| iFI,;ӦsMHLpj8 | -xi;ً9z>ݭSΤ;[Wp96Lݸ -+YڐZ_6H"΢Fta6@K1Ѵqb1m\E߯>1C> "V?DBShX2 P)rE-A/@bgO1FeG*r`7 {`? (˚ -endstream endobj 5727 0 obj <>stream -H=N0`W*y 4Kӟ)R H0q`dT \) 7b8~ڗ!H~ϧҞh-zӑ D\ϝ+!c 38;x|x W߶k/KΘG1Ƃtd˜D%@R)`GTf q)0)((fYS &E]_G1ւ5EQZ."@R{KvF\c;Hi1E - Y(Gk⭣L29" @5{>"R5|#ztgDfVj6V%k6 Q\0'H_4G@yIީOSq[[+=B&rϩ{=P~L-@"E胒ZMwJV6~K%, -endstream endobj 5728 0 obj <>stream -H=0Pa ^`m mJfE[ śGanRA.)@RLch Mk[]wnw|ή;LN;smnNOw[G3?G-ٿ5?vd~G~DD_ -!C%`S"0zr\LDe- u.Vw,f i&.ѪR%88 PKJFViѠ -y0HȪqEټ㌶$o5T|OƮ{Tne B!)ƶAK(=&4½dTȃXM۟-zWdG}tr Komfg^@HTfNMrk /.U!77zKDbCvo2dE<5 -%GZ.k݌uq|m/-+fBrD -ub;KX"f2VT_7xBi ,h -5s؀4"AK$ϟV]CI] L>ˬ -endstream endobj 5729 0 obj <>stream -HM00h  Lό;) E]:]LKnVߤ:Zq IYYzݷ/:noLz۷JoQWp!z{v?i;;~h&7p8V4 #$@橰f5 Q:aBsc0C7࣡Uᩰ o'<%6:qh0)ŜSAhf6.31󟬣YE+Jl¢6۾dvˢ>8P 4z4:z9iKA ^ C۩ oasipfܚ,%e|!?oznæKe7_)VvGxx)! >p$^SSxgi<{צ[ (_2'BHoVx\3sokv,aɐ~vax,䵃nrAe#-- -endstream endobj 5730 0 obj <>stream -H0 SP)B#/3]V3*- 1$8qE@pn-I{ˡjI@;D~c;p8;7;9 Aǻp{߃͋]ǧa8~x~w/Ao~WzK[>KKTSN.CTTmK#;!0D\"F+Hכ2چ(R٥M<:nEr fsTED>F 5%ZAȥ9@RH+i6%cq"]B6&HFc138FF(˅  Hǰf!bt͈eNHBJME;<$"r#EՖBRUysD.5$l -Q-J`g1D QatXɂA)GKx2t&嗊egVRڌhz][k)+h&#  tNl,%ڢYht2[[i4[#]!>5~.5fդV8 'J|w#>'yƸUF4O`UEҕa*^w%[-OAlZglnVbÓzUZ>tG]݊TrLRa 9 -endstream endobj 5731 0 obj <>stream -H=N0uG/Ф)MH$2 #PE|,W,-/l=[NܟEtXȟϻUJ5 O$TaF I^秗{o\0p,&j!c;h)T`D/P!a1:kUl:ё[SaQ]hSU$;;(ӱCC Ǥf"b -L{TQ74BQ]ew!*G?Jzѯitibz$DsI"WB'pHdN $eSӫ!Vӻ~ظ&K vJtsO٬ю&iYK]M,;\| 0ȹ)" -endstream endobj 5732 0 obj <>stream -H=N01D#hڴ )R H0q`d\ƚ1Cdcǎ@DPNO?'9ӔNOIJ,^&Y)OV 臦qHi#YdvK4!Kլl}}F]qE__HqN ҏBLy$'=9db*1՘&9?:D nގC%@S;7aI]iI."L&=7dQءv=7 6pC5&I#v hHxG].~Srr w0mcbH'>m!H_h&3qpiŪm Hi)lVs2ID;yHDCOn[=!C̱ޓd9"{K=Eo1} 7C% -endstream endobj 5733 0 obj <>stream -H䖽1g"m/p,w ))Zൖ7Gpkc"J$[x<']_wa0*uá/qOF_uQ<?~Q_(Z3l$Q@łY2I`eE#,J05aKqqX-'21N Lxi VXNJ{LL>5 `ͼKتj`CKP DK0]Fh B U+NԠļ - -Ay8,<gY] F]S_ӜC5Lj:&aDKYA9Hfgo |qN'@ŃPLQyV-皸ɎCneiAC'."چb&P{8Tq=Լt[?/R74eUOKH5ag$Z4 'h4&@Ԕ͟_zG>M\{).5HyEy-l/ ? XN2Eʾ`#|]>-gV`~ -endstream endobj 5734 0 obj <>stream -H1n0PW H^r|NBHi+R;m,(oH? a;eX,CH|-:i^ o<]0pЎXiO|QՆGB%vWB(/<z fC*{!k e면u jhWE*|BU lmR>jF$c#O?f;/)xl @g)XdIdwt&}]ETϓo 7Z%yr2 &jK40鎋ߠu<P@GCѣ=FL:*)UjB9}$IgRO}@`)~ -endstream endobj 5735 0 obj <>stream -H1n0ohG.9JԞ-Pکh:fhb*k5dER)1 -B(/i/6ywrj/M}ےdlq&|#7{R-Yۺ%_1 OT%bfƑ%@a b1s"{@,Gj"7`b2Nl$&i3~jCbA ɃaVѣL#猙 - H-1H`&Vz<΀e{5 -b_-MDY!x9d}J*?wu*\baܔ5lXl8m"E:TeD;Ʃnf{ӹuLRL*KW|2|fҭ$\0JoUbE&g+841jfDKd2KSM,{uL&}bR 'c}d"7ua -Cr֘>c4nG̫i{A1rB'~#AgGy* -endstream endobj 5736 0 obj <>stream -H=0&7 LXT * c39BJQ9.v7:og{96ٓݷhC>s;uys? 'vawM;M/> BRI 4 PsR<1)9*R#?G;H${+O]? -iOoGS|; -VuFEh2yS%DIV -wB:]OܯD* Q]x'\r+kҦ?N9 -njt1mxHJ &d:(REKLR0%HJ g=Yݘ{BjEͭvIM*s:LI5^kbY/V߀V[K&Sl d//--? -endstream endobj 5737 0 obj <>stream -H=0`G)" 7\`g&2[HPqؒ]\%WRZَ"-ZmovmѾ8r=?;v}ï7~g]盛oNϻ?n+H`)0REd blA[(FwoJ`0LTJ N U,L )LTEA6 >.V&K-AP& - ~ B].& @aǖc$a%.`(u_,Yv?b+eҥ*b؃E~)@Oxri<868.8Q.8. = .T&oҟ@a$.a %@úwAUq,3LM ugGa=`eprL0 rm=ܹ!;llmR{_mQ"ni 1[TX#Pfc㘳|(0ag @О `X,Va5RbF:E5,F@k[<Rs#( -BNUl4OO%%/E -endstream endobj 5738 0 obj <>stream -HJ0aK֭l;M=zԣ7 " -^r(I 05`7 Cޞa/XbkD=%{"mC}"vt|4 LE$0R-OL0?;pƝ/p'0{!K#l| 0 kz -endstream endobj 5739 0 obj <>stream -H1N0`G"y iZdD$802`Nnҫ(X;vl؉Kd'dB't6 ]">,V1LI@9nh>stream -H=N0`W*y 4ۦ?S`9o|ҼJ k9{y-B>Ol -7mpU.ErÄ5gMm#e4e|%㋘F3i]Sc̻qEB\E#(& -,Mg4*?SM|R_ѩ4>&_ lE;q1D=~JFb@AS$x(T' *mRx?[1FB6!YVRbXzK͒om҈֐$(L57[/ehE+)(n_A21 ̢DQ+@սgSK":aI` u0uEFoE -endstream endobj 5741 0 obj <>stream -H̖͎0e?B6uhW VlH`]#^̼%^KKX1 ْM=7q~܏xsNx:oo4mOD~{ޜzwt8ݫq:qa@f !!@ˑH ԓ@@ ,@h *:!^@<f±j:dX +`WYy -w[g M(Q[FL})116 6%B_O6 -6 -(Y@^TP=r@@l ULt"?*s i 3бfHE+_$,xhLg:7OZ(s3"3道f -<~Asmv}lrW44y@ I4.ȠKft k&,[S E|yy&yS(6b]s\G+ΫPJ՘vq"ʗo1 -g&I9 9QbIx5HQj5i37a k -6ҡ 6узhm -endstream endobj 5742 0 obj <>stream -H=N0e%G/$n S` )fٸFA T*P<4{YЄftEEJ3efQJy$y$,;NO=q@+P2_P$^^RF%}~z'38.;0!Dwo.k@{{#xhٗfN\rP"$@t3 P zKCF"1Sz%#Q w`Dp65L03Wǡ\ú@D?mY޴ʪ);fXacM]*]qvi- n?`d(-Kn -l0[UkD[Eb"ʋe롑K -hd~G_$vhPK!ݰe 1 D ۾#{D߲{ IH )_=$i | 6F9_k%<4 -endstream endobj 5743 0 obj <>stream -HԽN0 "yo@  - +02`x,̛c.M(HxpۉG#9\ 1(dNUoxuXlk>u{ y6dhۭA/\Gۼ4N۠k]8:awн]1% i>mzYďTfM0IMh2E@NG &IL0H>ʇtD#RaG;iR7n=hԴWݎ&\L~eskKYԵ@((RL8Gd[W[MnKU XV}6'7E 8Ց;{HbGݙBF* J=)->ciz,aԭ-cEäQvǑYNqKLzDr|$u6\F.Mz2ĤsMSkˈ/h -endstream endobj 5746 0 obj <>stream -H=n0'h0EG.J֞-P)h;Hvr^GU[9d%=O c9gOnͶY5Wu{k۫~ea̝;}}޲7M6͏?[f*J}FH%HIQ2H*\H:JO94@NS]NO70By9 i |'IWBd%$(H 2p -H -.# uF#|E -RCFB" +3ԧS1DS1ܺNl4DnI쪌>stream -Hܖ=0MM\`w3U$@, pbA\l?gg5.fyv9=5̓.nnX-%:hflX+%J, "52v΢0`9!ђ2r"%ZB- ECBy HV ɀ/Iq KD5vA\ZlP*Ҭ -$1.({)xx rJ)R%U@:48;EdMjey+;F/ʑ?0RL{QxA>stream -HԖM0'R69Bs6v] -du8G"njI-43g& np۟hpg/K{}f^nkۯ9>vo~Û罽_߾~Ԟ_& -GP#㳶4+*9+ )B2!":$&d=ѡD3I9r -0HHBB!I#$!x( tHJ&w9ɐ꼮m^;y%Z9R2*4H+‘)\hPa[Rj11c/T0 ADA/&Bф>stream -H=N0`W*y 6-dďD$802`N`n@Ng=JU6>stream -HMN07I7=8"5҅F4bG`ɂPOFY(y)<^=y,pB|'Y:g*O_]76Qlv#٥34&jnΙO<>`= P`Qh&(RcP,7^nR7Sk*JL -L%j"H?S-Y:RldKeI"2Z!׈$$ONi8Fd$DҤ0 K %"BI}O9i韥4F:g(>LT`1T}#?_&0pnSKvrLXɰYX[2%]p -endstream endobj 5751 0 obj <>stream -HMN0NXt3G~FtdD&҅F׭7#q5HKB)?ЏJH( IHbOb"Tcr$rV_δ=ަ8:9by^yz|)#;Cg/z4,(kA sÜ|ZRMW5\+`́sr/Ljf Tawvվ0FL.+h'ѲsI{_jMLO.)6֛ʝlazX&(g17돮ٸb XWviriUXfZM |+!rM -endstream endobj 5752 0 obj <>stream -H=0%erΰ*S A-)@!%7H(>B\D s7 +؊7{mvzWC}u٨[{9͵oOinv>ߨGz߶-6;uo9CyzY.!("DF B,''G:i.D"XQ@ "H-/zw%QƐ jnCW2Qg C9NBdNF3eD 0JT>stream -Hսn03<G_ C; HSS!A;KZ "ڲj`xǿK H6@8lGߝzȣvu]ٷz}4O;za—+qϲ>ފGY7X]X/\dܙlb2&(.h y<2kx2~}x2>hG=3<9o\"yV5zsS6)9ѣ;RBnE|$kѢo$wd tzIl|U[w'ܾ2weeg`;ć$~ 0U" -endstream endobj 5754 0 obj <>stream -H=N0pW"y 4aT@"L@07J>JQدBB}C V;YYuZ,bEeN85G_^Xw];gqyOpyMbeo/r"CfvwGh -ol!d7( V -N7yL  sG[{eT#gO8݃̿ekD!0 #oGSb3?Uv=p@ -RB"@hJҁL!RW) -j-3R`IlH`f_%<0MN tӀ$)Ã"6Г$@D͟!)ɲR"ك.mT@STwK'}]n -endstream endobj 5755 0 obj <>stream -HֽN0.<}zw7595D'Wut|}4|#CҖ~AM_@-hQf,V.K)BYI(~zbSJ@?qί =Z|8^>stream -HMn0`G䍏\3ӆU@,Y`H\R/#xEc=z=~~qD(W&Ɗ%0KF#" xWºrY<` -+ -KKZП (/7D{.J<+>stream -Hԕ0mMZ.9EBZ-+@PoF%Edc؎g -w8Cw?y3|7yp{uGtG/O*~0CGy}+fAY?uX!hąa-c/5cx/a%,1N9.fJ qK$%zWT<ւp(2؂܍W(0m6V}uz&ӔjlMY6ճ*g6=OlŋU&aI=tH\{&NZdY_{_q>fyn ̪uqnunN&8,kDQ -kr6;7KG6 -06F?8Ї~=_\6p%s1S>stream -HN0oف^`cD&zԣJ_G豇J[_6Yx4]-du8z5䞬\*#DWa<_:I8c-IO0.Qr1Cp͊ 0qLMdh=?Wg00qL%#mgԔO&)$ " -endstream endobj 5759 0 obj <>stream -H=N0]e%G/|S`99ZI1C??"T'~?V$a [DEu6htYFKf-<xۂ,_NJ ҸaoO>K2qPCAVN"h.jET -Ko(! 2\u͍PQI2Wm9Hm3=CqTQ> -6ߣBY;o}*~j1aJCBVYpS$46;ru -uҏz"_#Cѡ$<!kB@b⿱&GPMRH=\%Bj/ŰMԗGѣh"y|a߮SBBGV -L%TĵƨAh!qu*l%5zF^)xB'%?F*bf}kwS Ge -endstream endobj 5760 0 obj <>stream -HMN@.Hf!G`.P(UD7TMda+.]ht 7U' -$@,TfФv[ |x} i2;)EY̘̣=&fdR x z@AB~F[:ȱ*] eeoZȁkq'^s\c+?ڕG.'o o\Ay`oEإw shWZ'59w-~j2q-o֡ZZÞ>*|'f9CȹJީk\7̷u vFGb[RpՈ?XЫo8 -endstream endobj 5761 0 obj <>stream -Hֽ0`G) *[ A%㵂5.]X1gB4HOdz8ƛiˇ<wzNGyxxbf'yy)]q Ǯ]tV/-a<b2**ʶ^^fb5RDkADhM\M(U*k؋`E;̇[{k*kޯ<0[dsYc$ʘ~cDSzS?TLY5CJǖC3a{ҰD~+LlA"k.UNf)뵋ٖb0M>stream -H1n0P -pQo`] F@=HءEF̔cGQoQA$E~J -(&??vͮٷ}sv8oзڃMGWvEk>~oH_3n?6]NWn?~{L'B'ejIH,@,yfy,Y -jc6Nnk&~y_?e7o},ҽh&'l& Jxzd|^^`i+.YfGdA4M 饮!h٥ҋإ3l>I虼B-:DPL5@,}&:O=Xɝ~N۞^avϡwAFzn(S`;<›U7¡!<\m2p4{wE* 8s9[ԄE*ڄTJ[к]Կ55m=\n8MVѣ_Y6aåV,3<8Ŋz⥮Oyś{Xo"3yP[z揼2#L -0d͡* -endstream endobj 5763 0 obj <>stream -H=n0p - p Dv'i CvڎR٩cĭ`o*{Epp%Genٖ]vp=Mv4#s^KxOotsdžyˆq@7wo55ih6P) rτr/RMHN ee]y<ٝ, q@`~ >|a~l% Ry.dT)g?I2T)#mi7{7&AxƋ -8<#3{Ya ){-GWx*k(W>]AAE>stream -HMN@GX4 G\i+,Ltԥ L'*e0Yp>) 069e,^tYuJ78l3u'6utGIrHrIYzBe$ɯI3vHu) D% - \]d Ww9ĬG|,vS2BDcGs (e6WPɅBSgUkJM -May"BUؾ49tWEX:-yMt=LEnkơX Pxo3,pvT 3s1ȑ({3ݴ{p>m:]y0dKn 䘫Z3<+égӮ il[=>stream -H=N0Wu%G/Ц) S`99Z#xlb?yy. -e(H}C|ql~/J48\b,l92md/?U,Q቎@#Q>zԋK8!Q!y/JU}aݳ/[۽ -endstream endobj 5766 0 obj <>stream -HA0`G^D&G/0mRiYE@ $Xq,ͬJ ,,8IV-|&eQBV[yWVj_K(vOy=xQ{A,7~/m!wRӷ{qx+\ˁq2& e$]lX pW,wĖQmr75j2_|5&<|w'[Xe/r]b -lu ?N uw9p3؜Xo]n?pˉS u+4V`$qXn rV%}MІ=7[BO,vm9ڤG.C9ZWFVGsM&Cw9ڡ.+vUE-GZ;q񋋅xw_Ι5 -endstream endobj 5767 0 obj <>stream -HJ0Y<,̥мͮTXAГ=(zNm#챇ژ6v[3D\!%ӏiHiRyyWv-2~̘X |BS]_;q]arqɥ[gL`R]Ѵpd4%6JFQҔ@4HW4CaQ9P9CD~B)MAh12tȟFK_{uyMvԗdkzxRM%$&\O=lgQ#$!@ߤ)%񏨤$ K` -endstream endobj 5768 0 obj <>stream -H̕n0 zȩf6@<h<@1Ct6G(UNvˏ(mT2mu>]QnK-F~*x~yU;Xզ.$x4eI`A$"Ո"Qnw:]TaL]6\0fT!CVC"5ӪsK>o-eH GǓbP.ф?lNv -a'C:G6} пޙ"x* hy;4qoGDPQk0sDS;稑 6MEHΗS'{!#qT0 r*|ȑhyjJCbD47ȼ@QBh #‰i[]`eRˌ@VFq-U݌%8[dK"a栾 ctV_ -endstream endobj 5769 0 obj <>stream -Hձn0p#$/<@\V )t̐*q>F_GqH])S*xgb7ba:W./t[{~<;^9呯obô˫7yOna - N,I2Vz2y,AR T_<Ո.e^Ӡ4 2TO;jXVPq|&*KoHżD,SSާ&@ P T(J@JwfFF,s$5J)L[lÚ@b"eulPSJ)MѤd/J"b*}R Ȅ'd%d2 Ϗ%ԑM&ud: "T T@M$S:r\{}"l_Z|o H@"W!?5)bЗ+ҷk=o;*  ;O'w -endstream endobj 5770 0 obj <>stream -H=N0pG*y 6:ECL@0'L\G0&qN_^r8\4_ts3]yro}0Nv==5ϗk:;jIgǗ'<z|Sxh)0e!eD,7$0)L  -c'$O_,jKO-tYX@jү -z5cTTQ$F #"20 2B,&s5#*cRDd3 1B)"[[j |GҾ6|G/k -balxCY i^K52ڝޓJsT1D܀Z@<iA-ܭ}u35pC:d+eg2OPxrJw j$CGPAou]R废nR膶iɬ(A鞡mP^b"k\݆1')E*Q-BU8>0dг  +q -endstream endobj 5771 0 obj <>stream -HĖ0Ən;ح" Tp ʣRl챽{vCE6g]6/_vW#> 7ힼ:\yg>=#wMϷ߁o>}~|埮J 4Xa68*5k " Q'Q$!$DT5!XꔻHfDNl~X@VG&# &(kpѬvc"  -r3~H`ADXxnk5"MFdmfVLl>e Vzbb-b!w2F2dg`W"@|6CT, TgEZHUnH "mCcEE:㴜)˰zm%W8tA x"#F=bMP(͖rYE"I)JOB4a&s3$WL7~b2$32:r>;r\`(JioI`\]ₖCq% -{P)(hJ)"tO v1KdL36bz"BN>ȴ5|."b%]HM:F} -endstream endobj 5772 0 obj <>stream -H;N0XMKQD -$8PRN$.)SX N8BP 8<<9I'|ˌ/6aܭ&2c^ny%ZgvY|,a5O3n/x?u$@1>50l(V $"d–TM5j ,z0IYirrp` L944 - -*D  RRIQ $y 7 54RU$; - 2@r8;qg"3M֪ժ4O.iRpO-:H+Nk{'KD>/&I@Jn8MkK>stream -HԽn@\~ܝsrrTH\)S"Q -~-]v<%Qwό]oնjn}Tzݺ>mOVgRvӀW6t6oi˪V篗J 0«@&h+(^V=agJKfHSi`#9yr9-iã'=n-?n| -sAԫιzDx-lpSp߲|,CX=pAXw ۅCqt::dKw+m U(:d*)vLt%fEa0nm` ?5dÂf뒛j_ɎjɖR<܆J7q; -k2nG=9ynVh;c|C;Gt7}'{s3 {^` -endstream endobj 5774 0 obj <>stream -H=0&G/H HLq៱c{!VW%ߛn[ڷO.{znv=_f#nD^<ݷpx{OndsKkyI~O67юl9&?'{~8@Ey,3ϙUfOX= HҮb`Xn\b7f֣ٸ|$DSa1ϙV(ل`N2N0ghP -*oPNyC)^>6(P KY`HЙ{4&sz]r.S,3Ajegaub{!S\ͭUcfln^laյ(_}pS#1 S'̦xGI}M=Y]`ˋ7;z_kKg}3,q_Fz>֫uiH>?4ko㹍87 -endstream endobj 5775 0 obj <>stream -HԿN0HZ=cjrj"Nљ>r&R,|צOQJ(mx4M:{d,5u'wÔyx G6 nf%a8nήJNDBCF YIjڤ@-ӯWT4\xzjS,v3W<wP1#ܕ>LT[^>-ϢPe'1>A|M*y|Df!~G Tsc_Kj7a`RvfR䔆, |˿;@(3|ąy2 -endstream endobj 5776 0 obj <>stream -H=n0pGp@4%%RJeN=@۱CvpzCQ?!`% -C? :E -.b4JSD_& 긹rYz=mF~tk{wGto4u -6F!2DŽ.c G+pOI O$1Ujc*&Qyq;ΧPwhDX#3:ZjA$\ّlhfhXThJMRi wԌ#ZLْ$=s<=AB_|Gp"ᇎ!A+=bG&q  b{|iVlS4opN< Ktt GI |@ IFJ6rŒhG1keBDRR#tov$XghnU}nGo=~jS&7ch/9GJߒ"##Hk:[E -endstream endobj 5777 0 obj <>stream -H=0M\`w&+*.HL-Xq-K\G0պlJE4Lc}3ߏOá;tW/_]w2NC_/xtաx}_?ߋGqpbz}Nވpc+j-sM ;b4 v́{d֫m'D?ɔ!rClR.ȱ;siU,0;fGfavu3C2+4(^ -{3U8`νmڧz.s)'b/]QkZ1jn}J1r Fu\[b15^L)Yʱ2PĺR֤^3?ccH߱{M-W%-._q 8[^<y2dXY`;K5Nrdǂ]Mm?-ʖ=JAOf?Mؔ郝eeU#7zذob~|/mpKfoO-gc -endstream endobj 5778 0 obj <>stream -H;0)0FG0/7&"@Rٔ)$'+(< ! "MOOr}ױj };>C{cO}{`fZp躁}FNtݽw{{tw~R"#G8fR P<4b1+c$ъ!ToLr8U3-0$!Nr H䤲'ydM@&2V ܆s D_"Ha5]n8 ,lS㤖U74Pf| ":Cd#ȵ -p2//!p񽪯ˢ5]!4*Dg˛ H2Ҍ g' ,%Sy&(1ͅ^xHޢwNFU;^ LqD&Ԉ|nұCmc3քJkB[t³wXK܀tٽmVV] -բQML]A#ш^6#noS2+F7J Q*EM*ER!CD0N -endstream endobj 5779 0 obj <>stream -HսN0=B&mV!!y)sZRv|9 vrI#V8TGŞ7Ҕ2JyC{. JoH #>vCH BhDKcJ'%O%t-տ:둹m+E(ChJPP.0qL32X3_"~{%#$ -endstream endobj 5780 0 obj <>stream -HԱ0ЉRDrOXn8,JHlW:̟D\b{Ʊ'졕#8v˽lg9n_ޏ<܈ql{gdӽ;GlbwlN~8cWaHe -@2 ``r.otɁR&0~ U#M0`#h`qZ0L`8#װr0|4_( ,`r(3JC -sd< Efhﻊ"*_ X5rvvQ~ ,[]djPkUH]%ގW?c .+mIMO +,*c`Lϝ~4Ŕ.:3d1'b:U -fKCLCO_ׄ\z84G -ThU{*#?~I1 _t5O}t&#}Oy5@KH(jQAu :EmT% ˮbN9%@ -endstream endobj 5781 0 obj <> endobj 5782 0 obj <>stream -HTQ=o0+nlP b(͐jː!wM靮{Ϡh8Cۻ&4^E8c;hz;oڃ&:8];BQ(Apw4=`]GLy"pt3DP`tRz@Ћ<^=YkvBal[JqnW(VpE8NL'‰h$(xW*VK$2[svű;.ϊX1+C8aU©(Sd2zʣdld-Y&D(#q&[Es VGI~m - -endstream endobj 5783 0 obj <> endobj 5784 0 obj <> endobj 5785 0 obj <>stream -HL= -@ ) !kL!hB:f{2 "SX'ZFb3h&-NgV{TU]alvYb 2Ru@g4$@Y1|xz-BQރڸQ -endstream endobj 5786 0 obj <>stream -HĔ=N0mmM_,aH H@%r(3㉔m)/~~RU)sYR^(Sk!>DyH/܏㷹|yZdP^F"ohl$/1p;1dȶ=ٶvbN< -" ,%܁ -6pls/C۰3RÔxK} 91w/Tv/pܚ;,hߐsu͂pmLr15֛W9k'ԫpE>SlŢ9H]! 2Njs -s» Yp¸œ`sS -endstream endobj 5787 0 obj <>stream -Hܔ;N0Q&G/@a}THJ - %WBD"iWl z?,z˹\$9^\.Iv,.<$ -C)n>stream -H10GL֤'D<::htFo77?}}}+%LDR(C_AVx}]r?accS 06s&0z1^ jChRA`V[$5M_.׹srָN~[87Gk\Ě8[W=#mT>xh>.:%_[1n% -endstream endobj 5789 0 obj <>stream -H22U0@=3#S(bUB]8L'r9yr+r{gPRTՓ+ : -endstream endobj 5790 0 obj <>stream -H|MJP#io`r~Ҧ+ U.]yuBѭ x x,Ǽ-4W^?S:bL$e2Yej/OzU4]c*7q{Emwjif7I:_2Ilw}<ݕ1ڇ~yJ;`@, F+#żo86'bp(,}c;k?ȍd,9$9<,)uGAS?xx>stream -HҽN@![l QHa7G()e(v&C۽=XvY_6mSj^Moqg/XInlҦ&odz>stream -HJ@ 9G辀IӚDO+U=yԣALi;mDW?!U?Y-ŏt48V.df,fCQ-jBn?TOk:\K#0E(x b 6p _ -) -endstream endobj 5793 0 obj <>stream -H;N@'ri{8~$.@I{ ҅YC$ -@X^}yy)ˬUvc 8)l[2}GS|+V*Z9f}cܬ/6թŦO/=3!Z DN#u h o,qq>,Ѓc)%68jNON9̄X'Ie ?f _^})sW44&M}T4AEtf -28f0*re~/E@_h%4ϓcsޚk! -endstream endobj 5794 0 obj <>stream -HԽN02DG_M"T$2 Fďf$1Csw R2ž'y*S\xy*,xHkVeh^ Į^\,[dw{!37Qԗøk{N˼{;*_-,zXiRA[Dcw{ƎwИE:MԳt"',7,lճ7;J-'SJOZ̥NT)ߍf|&Lvv2T5򓸤BВ[ 48S4L>4Dve0|jTEMG}i:4]E\N| -03 -endstream endobj 5795 0 obj <>stream -HN0 ]P)K!yHHt@FJx3D Ӟ$*۱]y5jqQQuTT[tyl|^-EqVKV}sY^J ƶoK<@9e:v1`跸Ž`G, -endstream endobj 5796 0 obj <>stream -Hӽ -0H>Bl['|Ӿi~lWݶoln]{<7Cl!72 -endstream endobj 5797 0 obj <>stream -H;N@Ǹ`_ 8Ig)D -$8PRQ - ˼6AAݝv|\|4_5_jV0/f^5Z,ǥóYo>_:/+3Y^䅙ln'4{y~04i T=iP(.aa0mK&5J3JG#.ҿH@&П@R= #. eˀJQ.B޷xb/ksVfatt^Ce|Rhkp? dKTHD:@5(H -G}[[U!n*DpS;iovmz0 W@scMp(:܈b3,8V,b23<)s1w[hjH -endstream endobj 5798 0 obj <>stream -Hӱj0 h&!h@=کkHBJ7Q=wuh g,iO7j~od49nQ5 -^d٣󃦩x -9-95T"YQ%W%{Oλ${媣 Hjċr%(ap:>UbpH65eI}qSV,JNȈjx:o̽+qSm{_0ݽҠz`jG* -endstream endobj 5799 0 obj <>stream -H223U0P0342U5367U0233 rr! s<͸= BIQi* $>0 P= l(@ g;H@D` ުGv=?F =8 %c``?r -0@ -endstream endobj 5800 0 obj <>stream -H= -1/JXL!hr Eks3=Ja!3 F8RdEh\O8Q~=h>stream -H씱J@pE`XZr.v)lT7oQ)S{98@.aٙb5=-z&b5͗vUm`<֥>stream -HAJ@?dxa6VCWU,]u+h7Ur.s eY+ν}gdtY_&}KՒ}9Ηdk? o$}l5;=HkB[G 1Wםp`#l7JQ=)5gJge+闓*SjҡjUNl6lǀ*R#_m-#a͵ -endstream endobj 5803 0 obj <>stream -HN@Pl#/pp]S)LB#P^AYT8bm矅Z>ו^ҫ*O*]J=PH'<y\>stream -HN@LJPL#&&RhFkxr DžFߙO[˚n:,U0.+Y)R}o0M;\%@!#c RHFfD8J242!SNB7|ywa<qb(h)x@ɆS0C0)+xpL - t 8XR5Wc!U\C+ccD6V`L%A&!y>h:/i:Y,G޷ a -endstream endobj 5805 0 obj <>stream -HӽN02TG_MR5?CUK$2 #&2zrܝ Jԁ ,1'|*_Ѓee/zQb YcxxVJU|mҤT¤*n.boхWk˪ =;@@jH1!БjAh*ذ4~4f( n4syqr@:xq>݌IQG1JΖvIݗ½wq=)}/v\dN؆"| MZ˖dmJ c9?duN= %gRWS !Q[ -endstream endobj 5806 0 obj <>stream -H; -1_,i WBVQKAEB=JB' I`~+qfEJ:YĴ!|EjhU3ꪘtv1;f! E&C냒NBa-pu[Ō3J/'}YN̟_$ 9r,1).m -endstream endobj 5807 0 obj <>stream -Hd=N0'rMn}*)؊%&{%Gp"0 Z<<.oҶjږu~+.sUmZM|^g/٣ml~^##  06cfE&B+'28te78'X +;G%0|#l= -A ,. -"NDFbXgr[X -(鬤E%ְ?0$A 8jx^'bKxtFg%A%M#yZ|]hz}п bV= -endstream endobj 5808 0 obj <>stream -Hѽ -0pKy~L`A'@Q;Q4 -l\$|&Y) h(i' -qyb˗ ~Sj/evml:ym!;%5ِ2![Uj /Ͳ7Ӊ7Jt> ^ iSlEF \ -endstream endobj 5809 0 obj <>stream -H1N@1bi -|<7P%RH@eDIsGp"0?AB>stream -HtMN0GXL ހ^@> ̎dD&҅Fwx3&e{}'Bh~m MgeUj2Td55ߔ<_EԮUެU~7w׺Ty{??^U{G$X!bLObDHKpX*AL40hѪåDdm;Ul-kHDՁKg^Ʃq||kH~G/S,6?(v(S7>iG\hK?ů3L1h%R!9 UG= -endstream endobj 5811 0 obj <>stream -Hl=N@F'6> tH@%$87ۣ\<|;aKg{ff_; u?tz \o3/7k]oV݃~ݭ&\'4)fGτGj?J lVCϹO:مBIJ E'[rDg!Z2ʗR _ɴu~P8HuI -0" -endstream endobj 5812 0 obj <>stream -HlAN@d ۂ&,Ltԥ [M]vy3oJBK:;|e*i'Y.2 1"[MFNbZi&߿z#for'9O 1[˹mߧ<Я DA$Z=T=]U`֒Bʩ7hA1jI=f$4%F=V[ -V,eFBh#:Qc뵳QdU:ԃSB6S dE6oMQҁ l<6joT#%XS71W^uҩ ڷ)3V7(d"sɹAg;?3@:k/>s?C:jnoH -BˍhH;|g㖡x܈W/K! -endstream endobj 5813 0 obj <>stream -H?N@ƗPl <S)LjixGn ¸3;Q -~o`UuV:-AV{*R{.ܯҍE٪ڠ$Ff<٩*\f#UȬ9S79 bk Q#24P#[SAL 9ؠ(QcL! ZZ"m&@S^]ozޙKd cf2g_3d;`mF.3ޞ{}Ahs>stream -H22U0P0ӳ07U54P0342U0)\\`B`fr.'~8Pt -pV0U()*Mt0o`"{4***jr -0( -endstream endobj 5815 0 obj <>stream -H223U0B]3K=csS# =C#SC= bU173ɹ\N\ -&\ -\N -\ -%E\.\;!) ?@H{B#?lH$#dF' CG`8Kȣ ` ztrpi !r -0`4 -endstream endobj 5816 0 obj <>stream -HԓN0p G/@u5Y5|Agx4 ufBبG ?ӖS2iUMYiyQHY7U$۔;Ob_QnRWR/9:u=>X0ByuMI>:7Doq0 T|kc X@hCa =9tfMw_#0b -p~{30)=` fC8g^Ɠꖖ9N -2T0v\nqc3ZaE7x_ -endstream endobj 5817 0 obj <>stream -H?N0D 8)R H0fI}{i(bïRjβ(I7\,n(W&0M/ {gi[[&z(k!Dɟخfy,=ze-3smeUx 2j`mP[z*\DS&%ZäVj5) G{ΫFL;BP4WW޿{awj3}Ӄw3C3?yQΉnIUX?C l4哲J yhVhR{4Ѝ05(D]͞؏ ^ -endstream endobj 5818 0 obj <>stream -H=n@A.,M xq$R("%ULa+Mq=–}`HP|2MřE,3rx/w9#[*=kQ 0Ot()xiBҔ((k:5 ֠* [ϰ[KϓTC@ڢ_ Cm0T0Ll -6̈́q'MӨXߢ7ZϸX3T;E\G^lf7k{ ,,Р2R%3Ʃʙ|rV9a3_BH4$ʒ BǸnJz/Pu -endstream endobj 5819 0 obj <>stream -HĎ; -@'l!LG\$hjihx%GL! ffyqK&E22Ś x'aa]|uwGVnŎ8]2NN\WsƒS}ђ]wӜFO^EGelh@E}AMU馏 zph]Vi_(^T֋ -endstream endobj 5820 0 obj <>stream -HMN0"79B}& 4iwF$@,@^fQafƱC" R/7?6[徑v_6rהY#?j*vuy(6큄f8vz^T7:]ZTݭ|xݕە@1 91 P8@`ZRSͼ7x{5S="PRq-{`:PC6p9yM@B3('A@j?[# 0ța=`!C!;>h`k@FfAk>stream -H25P0P04Գ07U533U0 -)\\FzfF T@!9ɓK?\ĐKCK)YU()*Mt??&?`DDddI&o1v4p Փ+ V -endstream endobj 5822 0 obj <>stream -H;N@E4^gqR HP@Iڡb[RdFـ)FwilM*ټJK>+y7 fie?u7szfˆeYvË|%/X'\1T(;F@ZZCK@A]1WmhMa}dS"%[)YiDlc4$68PC hmO޶s|{OR#O.:h A \ s}r- -qBX@ЌHhSfOh"c͹nfo=~ nzHy ~vݰ{-Զ -endstream endobj 5823 0 obj <>stream -HN0/ɋ!~MiZ6K$: Ċ G2fjK*R~g7+rM]7nUl]ꊿ\\Vb+j-of0G]孫Y\m;j&Dy¯'`GT0D1&= B[%Y!#&K#'Óf }؉|T33?gk)i~lF#Y飇\|1X5lŽ] zhrʤHT̢3tMȌ:i94v?.|ނ똩8pYp\&Cı( Ed.G2=bLJ,eO{Q{ -@cdZQG~p5 M`:vv }{ԫg}>VAB8@hfG{din| 0ڶ&p -endstream endobj 5824 0 obj <> endobj 5825 0 obj <>stream -HTPn0 tY Exu]hG@L <#N> endobj 5827 0 obj <>stream -Hj02b @d˒my-Cms_Ńu&[}s%(f2Kl*ic,-4IJ*G/SG-OE2J,J-~a]@x&p}w -_?"OӪ,IiK{KA-%knd̲Ztf3_/ob&ܹa`!5oIkG=;̩^^PA?)ʉC99-o.` -endstream endobj 5828 0 obj <> endobj 5829 0 obj <>stream -HTn0E|,[u6! !h,PvfH YT]q5:3,Iq~0y8y0 $t^n'뱱u^p<~<9/ -'G\n0gğ_^8^tafD9O<]-䳸:m5挐GQH @ fMi{ݸ` H -sU$}[Gı]0oH F[4cN(b 8 լܷ%=5sn1d=^eZrOwTSīaJN˘ _'IF~I(is~c x/ ~ׇ - -endstream endobj 5830 0 obj <> endobj 5831 0 obj <> endobj 5832 0 obj <>stream -HMN0KX4#L/0"5وBPkkT:h -, &xk&S`#Tb VEv.E`MNC,aM4D2Df.C$jEa_Eg¢(r|$J?16>j>Ex}VI]@,׺>.t=ϰ_f;L)0v$u41;9B_0HkP`E֙VOTrrِ[!%G -endstream endobj 5833 0 obj <>stream -HT;N0&JlH|$@%h>2fSh*y̛_.벫ڦnW]CUu|i;ʪfnx_ Nv|soˆo+L;Tz"GCDP嶌 -J}X{`=rwL o 0=V -endstream endobj 5834 0 obj <>stream -HT1N0uKP_iҤM3U*  & cn=D60aE_޳^ʢ`,6]ݖ͆y4;TWnM]VMˠjM[>l=㉮XY4t}n].YIק[LOWT4¨;Y! PY `HP?JaB2HP,I'p%[FFR]7ƀi40M r_6lw%EܲPv[: TPH;݀:<ҀYa?s, #m1q-/ьeWc̱|Ҍzȝ$*0P>)3>stream -HԔn0et@'|NHi+R;ڌZ3<Ȁpm0n$ gL}$ti (& Ca ] -Rw{J!Hw/>xB [hZk[M(ȱRgІ6ۘڴjTe VJR3%K2sEeWdc)*M95}Ҫ‡F K)^]Ƌ+TNStI;o&7f F.>stream -HԽn0` H^x'Pmڙiε`,gp@ -l:5 <j| ҝ"`]w`65kL]N pg̤SN4.nM 0sXKnDŽmlG>t)̋jnO XV~=a #ƀMͱB׌ Ŗph( O -/Ẑ{Vh(F(< -b]@oj -endstream endobj 5837 0 obj <>stream -HԱN0pI&̩ &: 'WBrC_~\˂yp6t0/I(i() }g^mHa*]{0E:ĿĿ,H95}}y{$+4 qސ jh^Sz՞B֩U汪xCf3ML!/%e3B3 ZXuDI,\R bY/CU2u|AY: >c N8(G9ܩT?vqMTaMRY%eȽ7].}vPx8T*!U"1G()n)ՙR`b!ݤxƖw].2@5Jbx&zW@Jk& eNnɷ\ -1 -endstream endobj 5838 0 obj <>stream -H1n0PG"y1RJeN=@۱Cvqё )JϲGXD8l)x 4IX ߬bXmR1* v-37ٚ[n >L(qwTm"!KB=uu-::Y4f&!9x?042'g7[ | -j(P94>@g0sPgdEp:PiQB꣼Iςz -r/o8Zvu Mu}j=Q&[Q"6$igne ˃ p#IL%TSp% c4)`W̷ -endstream endobj 5839 0 obj <>stream -H; -@ a@MZ -jihG)kHPqo9rQ_pnn - 0RB~|!LQHUy~WW'f(6*,QKƸ얐 'U"%mɱ5%cQ__s9Niq*韕ΟӷԵoVC4X2͵:m'zKa 7P6 -endstream endobj 5840 0 obj <>stream -HMJ0W(MwINgڮ -]҅ZsrOPa7G()IJ$-R%2SR%tMJH3WR?jEZ&8A<qS`xKJ^6M<>`qfʛqCvJKUY}_K*_WwA:IiZXk;'s -CUɵl[2냫vT6c'>rPy0 ʹa[N/\K͹E7%Á -endstream endobj 5841 0 obj <>stream -H0PG=T%`IlE$8 G Y$~$cg;8Uy=̤mZ׺kS]]]nt>_OS#k:kXvt8:/&up"u#wIzΧ1 LxIB%(3 b'K]²E>C%Jo71xeP`ڠds"B" -<8,PX\WdD"pC3&BO`# sk>'(91fpG(B@m@@xQMDk: %asB5f@Mm(鮠NZo"xH<-a E` 4vC ,/zЀ`ͼ_1Xۈ638j&L! 0 -!V S&,%Pts'|smSJ -endstream endobj 5842 0 obj <>stream -HMJ0. -Y M:a`T AW@]Ptkzx.(E -~ 猳IQt - Bϣ=LM>Ca|0NbvD>% efTrܭCAȿA"\`kk( />*L@h6:e)@JD J%Jn @m* m@Ʀ#~rfDiHPX-q4Jh9P뀚LizՌ JSQ"rUWDf3аeFoW,E -endstream endobj 5843 0 obj <>stream -H;0 Pi\~&`G02TVjIډ)8GQz{^`~e8` g(ux֋U.7KpH\3SN<"# GpT2kZTKNHe'=m^jTu>gd-%kjR.5UIԿ^Ԅ:p`OF -endstream endobj 5844 0 obj <>stream -H쑿N@Pl#/p+pɩ&ZF7#E ().3XX^ul?*Id"8UIU,qm)M)xeO}VNS+B ȱ(3*vXJ -Z*狨o9ՏT F `4ϨQ ##DP1%LG7=) ©b4sEgsQ[fnrS8+l8T$p -@/3/8aiőq½۝~`R#{p;Wy$B7/FfԛK`-П9jUQ -02. -endstream endobj 5845 0 obj <>stream -HMN0`&pza(dD&҅F7Ûp <[ןx)V|xn!{~8]߉!5J+Mn([[,@cVԆ}DQ^Lq5ŷ A& r )a͐j!(c-9r0#9DXE;( c2 sd%uP))b 238j- 6F 4GCrYY%8RAC ]:i } ^e D -endstream endobj 5846 0 obj <>stream -H0ǽD"<.d7tHG%̛u"J|qZ3)B*][mw_תkKyn:UriS)US;+2#U>~WyʲnW;ۏZx҂GAF{ {L:Ğgiɳ4CDSLDd3xCDHTH8U$~9D QhN-DdS0* (B&OPwH'¾-0fL3۔id F0WEO$B0mLU-G:Fr;@iHFdi~wUz7 6%k$"vܘ>stream -H;0P#Hpq.`b7 }o x'3sqȹB ot04Vs)vz_7.`[VCl05*:t>9y-T)~y[ݨ9+ʫY 8 -0 " -endstream endobj 5848 0 obj <>stream -H10P#; 7XHP h,\@HL[FR(E'Ic+0Lq7%fVPhε)7 b@4ԍDq%D7(['EWܼGi]:W|g>stream -H;n0)FG /`J'E)l*7QTATGyJu9FߵjxԝOݽ]uhF?,ۦM4޺ F&,w?k -?=|ܝח_Y}KY*@͹Ll`@qG0l K@ WX7vGX0ҙ(.+R$`(0\,J wU Вng]7*@ SF@<%K @fЧM:Xbf*4J3̗*EgZ`)/ptH >stream -H1n0P/!V*CvڎZ38 -U/ȀLmDB"Rk)YӘnW$,SyO8>QDe>3F}E'm4*,c%}z%l)KI+*UяR_8Dha;zA`_<$C+ EohW¿|plx| Fm/4!C"܆ -aj6 >`rggp 35jXh V:(נ7mVU-1h9FOJ6p,q(hU|j k7*Pz/gaaP։*McԐq$TC!hi td IT - ,p/osT=MMȯW`AF -endstream endobj 5851 0 obj <>stream -H1N0PW"y G/4.ML -Hd@#f( QCU,lJu.26~RQlOEU9D)sQ0O=rB2Rx,yn0.JLw ]3,tܰ'\a} C'u3X7 -{$B&eS ΈǚH(1-'ÅbP堢r\Fca6K% pPFtģ^So)$WoI2q ` -h׿>bͽǀx s`{].zC8K7ܐFF%r$ - -4tcY{Al3ZCF]Ml P5>sE *7~p7UCkVC -endstream endobj 5852 0 obj <>stream -H=j0`-@@;!H[B;m-f:у$9 -4B=Dﳝ'=922Yٲ:O`,k,fUUo.+WV7 %Z4Y(%|a.::Sw|eKں -endstream endobj 5853 0 obj <>stream -H1N0e(ҕ@8MFBT@"L@>stream -HN0K؄]MVM`'@=zy9`}G P֍ɚs:ә:iJRy^J,i\VT4Ί*xn*D+.X~K3Uc1##΂ 2 -}扺%̊gX\S, utKLZXg:שg|f#G:b x _.|:#9?#/,̦ 3Zs9S:7 ]Oct,0!=`(w -endstream endobj 5855 0 obj <>stream -HMN0` nÔ$&0ѕP.4o7t' Bm_[yř4G_ۄ1hMt'VlDֶ45 -bEj2/lMv`mo3WduOյ,R^Pꆾ?R2ЩN. ƹ?1D39 -_:@B }d!kB8aj{E#f95\,lD'hb h4:P_P~x> endobj 5857 0 obj <>stream -HTQn0+ت HJ.9&ݱTeȁz=c?@ps68܂A`9HLnM=Hj>5Tt8Na=% ߂йoM@]FGKy=Zj=X6k*Py 3Qpǥ1WW& -g7s"1-|VW*"c-u) d%SEƺi$x{a2R -1{X|ΒV̩sx??KG2 - -endstream endobj 5858 0 obj <> endobj 5859 0 obj <> endobj 5860 0 obj <>stream -Hԏ; -ADJ<}{~;ݳF@74P4:`D|ɃE8hYR֘Lԓ8VR;bI^u8؏{i> ?0;`͜L^|&<{n¸!Ib| -endstream endobj 5861 0 obj <>stream -H223032T0@= NrrA8f`<39ɓK?\KD88(O_T.O\\\ -endstream endobj 5862 0 obj <>stream -H223032T0P3320T5537214Q04@CB. - K L!g -ɹ\N\ -\ -f@)YKW4Ӆ2f'W @nF -endstream endobj 5863 0 obj <>stream -Ht=N0 ]uGh.@4Ht@#旣(9;T @b$/M;Xӭfh/\onmgukhzҩwe/{*i)y0T{So0I;;=9<\N5ӽzUӍJiL+)t:ʔ(B6Hq8AG*#p:RLHsj?زbeC,s(aŬQw#o x _lf ؐ3pgt%T*2投(bźMZS!_X -5շ -endstream endobj 5864 0 obj <>stream -Hѽj03|/PR$KPCB=ҩvQ;\O(d-TykT6PM -9Z9!7V[UhLE;Z.x9Sn^/vCoz"g`loHc=+v-/`e< v AhjL $,ȅ|Biy#߰e%kEBľ .71W6yUQn1 ,K& CuBNd;ϑdG`aL -endstream endobj 5865 0 obj <>stream -H=N0gbirgH H@j [XQ@K$';37e llSWdnP-=ƨrq$vV+cm׎_a~$[a~K5ww5z7–`Øtbd ~`#$"9`HYOO i#5) )i O@"z_c~i%6cтsK~f]`kL΂m8XX3P` -endstream endobj 5866 0 obj <>stream -H=N0g"4>B|liHPurGH"0c m-S7M;*e>6etmF)-*s7c㣌 7<]tc0mF [$!!026D̙)‰ ^.c/a,I8LճF")Eb爬J%Y]ύxN@K=ypJPh]ݣ -dG+g$ ^--{ KE"p ݀O-e/ -endstream endobj 5867 0 obj <>stream -H=N@F'Jai=SpJ -Զ|!e -+7 @Td=ϼ(eHu*O]&y7gIݞյ{\SI+US24hNUͶ3齫r^2­Mݸ']ݒ(vD@g잖"FQ#ى c1(:C0XCأ7A6~{YZ K)}t+DF=[;RNJ><l Y1J?Ϥq p"*#0gVkfU;tesՙ;)YAN -endstream endobj 5868 0 obj <>stream -H;nA `- 0;3 $[ AHAP8 -G@"ACO|XBE -SE+ѕ98_^aM# 0a\N`6c6aNh\`а%9k@ @HC%t0 MGHNWR^a> r{$e>?ysZE|RY -endstream endobj 5869 0 obj <>stream -H|0&t \6]6U$@J$@ ŏG#L9(wZowvpnn87}w}s<\LJkܜExiCTc~k}zOU?;}}M+?-T`UHTF<.BZ(.@YR¢OVgTȑE 7&-}F،bJH rY \tzYzfߡΔP7mHTIdK>Mt Qt A#Z@P_SR5W!Du¹eBDK# `+_]rlǙЎ8M)Qy$a2s*t "͉4EڅȇiU!]BQ:R~5(HxfyF.&Z2vJlg #蓩Ta|79 DR f\[("Hs#Ho((ϊ bѷP89b!<&Vi,Z -UI۲,RM!-uga,VjN`.*e痨8-{Z䌕sFhJ "aƋE48MCBۨü#~wӟ?ڍ" -endstream endobj 5870 0 obj <>stream -H;1C} -_Ll)@I/D@Z -~^: 2ISg-{Zs<.(d}^G+ܞpk7n4z?.1>~!bD -endstream endobj 5871 0 obj <>stream -H= -@ 49sO6V@--Ѽ.nhl{/IQfae,es5ȋ2ix@.E+\\]~Gg>stream -HKN@6@ 1DW@]9G,YiH _)]vƥu횂.ueӕ;VEZT.$i.WIo0{Q[b&pO_8 ^a@$a+ $| #hc1c$ڶ#1Fb$RbZ릀7[Fbgh3fRIκz(b[ZmN`~2 d'zgb 6r:yi*P)? _hL -endstream endobj 5873 0 obj <>stream -Hԋ11 -nr*$R A"] 3QKe2K;JfQ%G͵-G$^…ք04vCo8#]- -endstream endobj 5874 0 obj <>stream -HAJ1E3N$aT҅랣QL8ISkW5!2.Xk|ׇJ.fG ptϫ'YwRiXIPJחG.l3fhG6-(l>|oA& MA)?F>gv?;H,̶l5g{ƈ2 _DV>;u -endstream endobj 5875 0 obj <>stream -H; -@ )i8tnmA*JQl_[gCk!@ƴ{ofJ2t)G00G$3:+ā3@@R C!ti%T$ @h"DWAބN ܅Ix՟Ըז -endstream endobj 5876 0 obj <>stream -HN@\q4<.˞ɩ&Zjyx RgW@YUj~+g;TVXmPYxzxFե6Rpp8x; <`La~OxC>?߰fh?=ĨCYx1v! ȡv? 0@=r3dDtHXҞBc~C2.K$ cwU?-ZFdLZ,CV{j1K#7[d<.]֑.g]5 CU -endstream endobj 5877 0 obj <>stream -H̑;N0'bir։;q$$R -J -r=–[D ~$_|ֵ(* T ZNB$-ES*H”nE;*Vqo -5PjZ}S;̟HߓQ7$1[d avڡgr !e(Vla ߞ<лc򓫿eiG s.aL]żKgvDasQ7q𲙂 Vu,ڝ]G{;%!:|M -endstream endobj 5878 0 obj <>stream -H1 -@,xlvDSZ -j)(X^xup1*i lE^Xc82J*֩HsYm:\6IL8B6xxZQBz^w?&5--x -endstream endobj 5879 0 obj <>stream -HL=N0'JM_`k,-  *PRv)SvXʧmuژ^snk*[:Pw`l:Y9nLˇ:ϪyնUͣ{՜tI^!.9:>stream -H=N1Q"i -r2k[ *H(S9ZG2E?>iB4+4m A.3uWm%4?/ ǎE4!OO6>stream -H91 @H -AHtX*w.&e9f ZR(*ֆ%hӷPo8hOp -R{>stream -H223032T0P3320TЅ1 L -r)\\q K -CNrW04N -\ -%E\.\ Փ+ } -endstream endobj 5883 0 obj <>stream -H=n0 Fx%G0/Y?dM3hh>CV2-KodZZfq딨*4j/ -<~*86"jCH+a7J :=~`BAJZ(l_ G  -!S2늹D6L"]1GX\c4pYsf>a%aY?ܛ?Z27v[q3u$짙.yL^; JN7CO-{B -endstream endobj 5884 0 obj <>stream -H=N0FJ4{*)@I9%GHjxgP@GdY7t%:g-n*H1W -U6wFL)$q0>} vO=wzp -??^HoDE4nÚX2CE"ro9aÌCya -3s3E;}?5̲k=ȝr陻'o/]&wr?d&)[aC -endstream endobj 5885 0 obj <> endobj 5886 0 obj <>stream -HT=o w~Ǟ:-CK^ڝ"59dȿ/pUk?5ܽtw'Ǹ-€'86M{Tw;2$;9$;dOSQ>Я1@pjruM{p"Fih$?'1۰U*Q|>iP+s6ZvOxb[`k - -endstream endobj 5887 0 obj <> endobj 5888 0 obj <> endobj 5889 0 obj <>stream -H22U0@= 3#s8bUYU"`\.'O.pC.} <}JJS<]szrr?3 -endstream endobj 5890 0 obj <>stream -H22ճ0320W0P0ӳ435U5346T03406*rrYX4("P  s< =S!BIQi* ׇ 0P`ø" -endstream endobj 5891 0 obj <> endobj 5892 0 obj <>stream -HTPn0 [tqP<:ͮH#Y#Eǣ<ͱ&j1@g8W썅4mTز5A:$n)nbS <ҧȿDs?8 c~]<5OI^JAa'_cMqUzvY>g .8#|)wSj_&%N {ej95'tc r - -endstream endobj 5893 0 obj <> endobj 5894 0 obj <>stream -Hn0`G fmO -H N<p+ICoRq+驩<̌6D+ıI"_b],f>/lŞE!=\ϖؽ8顽A^m]l7m:U:哼Hߥۧ)vv:B6d7afPtQ* .j!KC("qPzj'TԣAMAA?B.@t x0oz -j -~Hf`54'Bw + + 8 -*!j d! Vh!+d#WlCYr]9N8$Ia\ l'c{HSChg f v`GQEÎf 4;Ptp> 9\6X61A[+*=*GXNS&4A/LRrm#kGN>stream -Hn@raɍ~\ι䮲@ -$x@T (l*J^Q^`# -0~8[+%9;󟯽unvl6'piئٮv߼naS'lw]z/ͫw<}ٴ۶<}ڴMyzQOgޔǥk6%HHcXd1=:&9 Io. O蹎HGDTFYDTHfZ)hQ 5$&Lg"#霍H{ 38=Lr%dLΧ[2&2Vړ,K?z*'r¢]fuHaܑnd.=AfZ*J(IMe7T牶 +hTS TH'7 -<rͤ䳕4@)tIo 5 OGۈ9YLP_V"!yBXISi4ILkkAR&>Cn'k!,+qTGC܆;I|d#3-0lzn>bS Qδ*r:VʎZ֊ #x050R뜉Ah*lu$ObbKޣ+&d6A,v pp=1JO;啉ł@i/;1>stream -HԻN0bH}"`[Gds%qN`p͗ 0$!I딬XQ0\R|ϋ,4i~q'L5ޯppNhx$8?=  !71BР=.hӾiЅ=$jbUYm@^uf@mh `Df'jwQr `KEȻ7!8d׀KPLWZ3tP{VLVvF,ߺ3NC-2լ+p1U&2r`p̻pM`k2u۫|tFFx ¸- B t.  _cC Jջ0{x|T3-1~ - -endstream endobj 5897 0 obj <>stream -HMN@!,Hf f"DW@]&]x,e ( di%bE_B'_y{aJ822I(hF$0!7 $#&YB&YG}vyy;<+pp՟qpFXDq08& 9y|x 术_jloԔBkWcmK-!0igWnd9@.wzZ=@jQiQnB, @TzZw`LO]Z/UejlSvǥ ZsXP-iVS&^ -5*yS5*y+NR^C}ZbCU6r%mf^x_, -endstream endobj 5898 0 obj <>stream -HOk@ qv@Upу71=WJ?7 -!޼ɤSA š/;oޛɳ,(fpMz%U,g<}ǯb2[,:W,[_,<}2>ӧi>ӇgOOKxyyEw }`AȠh=*q0ܲPt@m0344VP[!>BM:N>4U]l#baXYUH&gW).Z跁;ʴ>N`"H]@sUJ8EHd}Z`rgZTX(^ tf:^ DwD41f&pF{Me8d5m(m,D;2u: |pɂ[Z/Ŵʵ҇‡6p]܇/>]èP_+Akc3M -}|P4_V|ԅ U"Pnhu`^ݾz'P`&Pd*40B*B3 JQPm%3*K^EvTJ%2j([ h-CPAr $QjNI@ìA -b/ds:0ߚ~|8rcr19&>a;)~ĿT\ -endstream endobj 5899 0 obj <> endobj 5900 0 obj <>stream -HTPn -X 7E/9X;H1F,ƹ(0Yf{KlzL08o#. G`IGf8mI8u~A)ߩE> endobj 5902 0 obj <> endobj 5903 0 obj <>stream -H|?n0p#$/p? )m2TjءUUG(уKU> BzW7$\o~?z% -OkpC%/m"PkPSG]%cg\UJ1TkV oTG{),}UHr^ꇃĵiA+\wu`q-XX`Bt[ ;$\6Ut -L(P8L1EF !Hs1"Z\i+7UG*`aq+BOPȧdN9Bj>RuEjBWw -endstream endobj 5904 0 obj <>stream -H22U0@=3Cc8bUU"`\.'O.pC.} <}JJS<]szrr0} -endstream endobj 5905 0 obj <>stream -HѱN0T"ݒGȽ@;tT@"L}`dl}2fj'l,[?G3VI-NfX%JɪRT(XQ\ዀwBTl.]. |~Eא/oP@<+4`ȕ.{d7>V!c4hڙ&zog59zuS~Hx '{2$'PCfY?vpg\஁'`W -endstream endobj 5906 0 obj <>stream -H;N@\XG^ R HPqAmSq-K\Kq(n(ǚ٤:(,J2˃MRN ,QqH=uD9O-M<}yնR[ju^H+s#?=, @GKCv$$rZQpå.p𹓋}ɑd-\(j;ӼhP}6h7q!JI'%n㽔v:Gy)͆YH# 37h$pIls`ko?p eá~VyvַҞ;~j?]m6G1<[2:~^oRi^U/3TQ ߊ5 -endstream endobj 5907 0 obj <>stream -Hԓn01 y`LV*Cv;jgh< -Ȁp}w$!Ic-l~D&rV\"Zge,rT[\>+&2iD^G*)v|zJďR"*)EJ՝x|յva B'hc6l{g٠?yҎ ؎#pL=`hC2 ۖg£<1Oq -fp~{=Wnh\?fbWiSz1n}#= JBt G=XV&hZO׃!l荸ă` -endstream endobj 5908 0 obj <>stream -Hұn0P H^H&Pm>{;@JZH/sTۢ g.-T1|Isćؕ*C.qֶ(cS@3.NHe~ɽTi!T"os}r΢EN\O`8E 1Fd?!hEцn9ߣ߆1&/K{#,W;;4IX_rfG}JĆvva-% pգ-4vaߺ]]q=h*m蚀-~` -XFC~giBb# M='S`1Z6#TGZ<_"o -endstream endobj 5909 0 obj <>stream -HN@ǗPl#/ _U$&Rhu7#50]wwv0iieR|6줪m9+ʸ&?flu1ӏ}YF뚥qjb7tdϪ&,K+NYF==>*F_&B !h" 1рӺ4g.Jو>BHc KCd[p H p'z;/5#hW ?~E,9 s '- -$&L((&(P>>>5jFԼf -Q9FDN[ -h~Nk t p8zF5>jw~ -0 ^ -endstream endobj 5910 0 obj <> endobj 5911 0 obj <>stream -HTP=0 +<>ĐKҁQx{%u"7IJ'ӝ-w$@ -4 c5`cu u<(>`WR υaxf-@ [jqD5x)@E!w只CIᮣGXOj6v{4! _Q7X6w[;INys6;EH|J% i" - -endstream endobj 5912 0 obj <> endobj 5913 0 obj <>stream -HT; -@@'Xx@3&k%DBͣx a̮t70Ǟ 9Bm*þrFyˁdO2}CyPL#6أ^ldQ5] -`&OrQ0̻L1&O%o2 G? -endstream endobj 5914 0 obj <> endobj 5915 0 obj <>stream -HTMo -7Im"M{aZ)8RC!4c׼Wv6 n0Bk 8S쬃,cu\%^y$n1b_vdc 3<6OG0P|L_GA`ezS]> endobj 5917 0 obj <> endobj 5918 0 obj <>stream -HMJ0Wd#4i;UlA -BЕP. -^G貋Ҙ|Ȉa`c&}%I&4]f2tq"S4w)~E,dVTrO͗LFozf9/h*p:)7o0q;fׁ$B:!HK-6ψҋ]xWV)N{'7Gjp#bGNi 8TrLҙ|u 2x%mdbHd,,t$0 -P(-I5@nJ-%2$c3 Cݘ `kL,TsRT3ΑVbJ%G%upubTԎ4hĖ.+tg -r?'ԅ#d^GzGZ[*Jh4>ahtZ25ˠ|Î -endstream endobj 5919 0 obj <>stream -Hսn04G_ elj=H  -$Sm-Y웱S־A4j Q"y xd3E(KB]^/0.rt.=|gw+Iom׮j;X+:ŽV;X\ݾX/O᭰.Lmea -A=Vf06s|`-$`Ab -zDh 'dS@jYXdl -'9hA%šQ&`pjƠGi.~LCwIP`Y. r_iYg LEWQeC6{s: O~Z,N'R]=-)?>؇k"lmEAIK나?8SPP `6&7h.)aF`wpxBUA3P8q:鄪d4 -̲ Gks$:;x>e4ey~ז )N`Mߥ -endstream endobj 5920 0 obj <>stream -H;!a:iu 6j b@)*nencW$.YǗ>{2ِ} .? -endstream endobj 5921 0 obj <>stream -HA0B=A;3YX( -AГWA= -* -sXo`=d鼗9A$?$mB/Z]]\˅w?ZKKgW-/zu\kf?)4!] ݻN-g=X:S+B=.w<χw]61?}5}l츻c7`oЃ;ǹիz -0 -endstream endobj 5922 0 obj <> endobj 5923 0 obj <>stream -HT=o0 -:$~w%F*! -0Ub=y~͛w<6,gA<Ăg5]i*Zݠ\>E2 Wn'K^_{ - -endstream endobj 5924 0 obj <> endobj 5925 0 obj <>stream -HԱN0*} MZw@ @?߀G1C!b/W[_i.M~XYdE^,_B%엫l)a>yZK=GoOkCѺbSLIC!1,arNZqcd umPLGR>kI'i%$$CB3VHx6\N\qݘbZx]F AL$;$W~o8틴_#{##R; 4fX3*C5 UʅXSͰIj(ЌJklτSP\bsb9*pϤ o=1;ި뱿-Gy -lm;\n~`z -endstream endobj 5926 0 obj <>stream -H=n0`[-=B|8"83-G22 \q%M[_翄% IȐgd%yQJé` V 8͈rؽK c)ocOfSl_Pu(UD'!~i;|_㎒|wtWxU#r՛BӃJUhʮ n/S -m-[W9tA:}ntԿlmUz臘iO~_:eFV.#u^6*%Mk# -endstream endobj 5927 0 obj <>stream -HԔ;N0&G\``YH H@%h>2HVL J&?x*ˬPu]eem?=VL i}V{^\gNjEV^ߟx{5ȴ#[@3E0A /,-$yBG"IDa) p  3RD@`>Jl%V2,D8R,Z*Vґbxfj)']K|DY 8 '’~,!Eڏ#l{)̸P593eNagؑ'ϑD'$ {':H8bDd;E2LM`~m F͖mʼ^d2mֿ1.lo=^ ӷįX5I 3@ -e"nGSY%@4, /Izt#VZQV:"i~;)= -endstream endobj 5928 0 obj <>stream -HN@!0{Woʏ--$&$U9P4z7q#:eibC&:._ofin஼u&`}ʋ!^l@Oxqū%98B7"0M ݥ -?u>Ev%T2DŽHǜSSC툑SRJ,6YeYCg*0Y'!<8JK>stream -H=N0p %h>W:ȀF|s]%bBRy'tUl]2S:-Ox&W|xV.s/bW!)nO.eRM>Eu%td8Tȏ=R` iuXИuvmf8 ,> wnI@yHG?f+TA} ҇$AJddbloav9:6UqlY.V\j~aŠzAHR }ugl-,Ivؕ4L -)[i8ؽ9HIHER&7>stream -HԔ1N0]u( h -HdF 8WQrgv#|ogXd|yY$yE—e3ot'ܼ[˅6OtSlE;rnie/MԣѐىDZK1#$%#VTn iHhr@1yFt -lbO BEAMBE(h^v=^eK~ -};5f薵@om)CN˓fZ$KIZ1imm+c gDR@h`b¨aj4Ku6k\hd$ش6_$[@2"@!qI -5Ň|m`\ /O >}a{z[# -endstream endobj 5931 0 obj <>stream -H240P0P030642U57UԳ012 L̀b)\\Fz  źf.\.'O.pC3.}K .}gC.}O_T.O ?c`'O4O>stream -Hj0-ď~: iC!9Zhs lKrZ>H7QȖQ`C{I>f44Md9+<9Zb@0#?2SVfQÜ~8<[$Ks9]}N29_}I~չC+oO:w* -Ck|s@k\: S z vʤ1f`j~N5PYQ0U:!:i8Wz5y횫))= t=u(B촥L:`/:ڀI$a՚ -3Jk!B4>|&Md^o՛k?Q WTjwSMm3_{ ;T9tF!s+ZM& -ls N}HjnՁ]j_J{mQ%HE8Q^|`˦ -endstream endobj 5933 0 obj <>stream -H=N0'JarĻ6[EZ@"T()@P'GQr),Ď]!B4g{cӲ$%tS~ow}Ej7\Ru]VW|lqH(V-%.W⢽#/J'B q,D#D(b(j i 0׸5.{kNAK=6golҔڵAΠƀh6so:sC.xqd!-8IB=%Cn$|ባ_H%doqRf-63p> endobj 5935 0 obj <>stream -HTPn0[&Ja\\I$<.}B,cS}^>f;=w7= (˼:pQrPZ=KQN x:34~w}s -6c ϯ@?8sP8ھ*&fgpBfDh!Ш5R]'.\;S򀳀ˢ#>VsEN?Ğqh9 :ƨp3/W¿ҭh\6 -0Պts - -endstream endobj 5936 0 obj <> endobj 5937 0 obj <> endobj 5938 0 obj <>stream -H240P0P0133170SЅLM,Mt,M -R - M L-,@u\NSss\.'O.p#.}0`ȥPRT @j! d`U( -endstream endobj 5939 0 obj <>stream -H=1𱦰4&|D -$8PRŃ(#UQlGX-(Ŏa=L"%?ϧv$l1j:LGj̗eZ.E![/FS:>骳|XYAN7m6~2?ǯE6>$/Ewi`Z329oiE8S@+|w]H 6 x&? Ut4ESDnsiM`̀H9љě:2wM#Ze0)ִٳ>0&xj:RDYD5PFppWז*';qXfEy%ag`)i .:}(] -ipk=6 ?${]ợEXwƧWX^k-{^eE=V -endstream endobj 5940 0 obj <>stream -H@7Ja~$.s%,  *()@!W#(Dq=(dOavt)6ٻ3Eֳ|YdGl]zl5[${b"dth-w`sww>q<I3)T\ױZu Ugij[34$(`֦"4&Gnin|ةI ;khϳ%Uy@w'뇴^tFVCk`>~ -FJETⰛv\ FE#l"b)9|!5KᢗN@73ctDǖ2qwa:0 ͍g -m1br #ͯFkGl!ۀwCE0\aup埗n= Wk]|.C eWI -endstream endobj 5941 0 obj <>stream -H=N@!$pºbEj"V@--4ZVaXYz1^N #7,{+~| PZq<;XI|DpGtQ:w2ewz&MszG*M_PJ٘ h+X:@kD'@ڰB-`ck'W=_)'ߖl$ޘ4Y2yo:J mvz;MHM~k|G-C6"ڌ{1=' F -endstream endobj 5942 0 obj <>stream -HԱN0`I@-pINMd09'@4G:Gp/m!m";|RJ& Ky '4iRE(eA*3,Ͳ8 YXq}ϯТD#cJc.<7w>stream -Hj@ Ճ0ynM -=уxG|Ghă:K -a>?3D@k2ߟLZ,"_E^NjY,|S6EQy9;Zn"{f(rup\Vnr0<;fyQ}E6>_|,?}|GW{FL=&whrmp2 -[&piz %Nz4XY8xP=֎8/?0w̡m %u )^9i 55 -0/!c9-oSymFYͭȆE"ۢ}Hu7m\y1R՛ʓ޿/FRzh"?:dR(mx8V!ݻZ!Ӑqȱ O.1hACgw ҩ2={PHhx"{:j骹ORt{Jy)0~1keô]e(_!\Z޽"krPmDwhP<ܥ')b#%SmZN%_֎> endobj 5945 0 obj <>stream -HTQn -P$%<` a\0c]'s6<- \ppjٌ֙:'i'hpNqnFIi G *h[3~|E<ĶƓ9hQU gLǥ7_:*ZcOeezôFUs.~{֍$TC!n-|5Hlo1뷄)~U= - -endstream endobj 5946 0 obj <> endobj 5947 0 obj <> endobj 5948 0 obj <>stream -HAN0$2TGpV$&0ѕP.4܄#)љ0 64mQdʒ(͚ѢXYaO|7 x[ x@Y_ۧ[~_?︾X`KTY"ibb%P6w< -zyס==jBNU"lgI\(,RȂjAs m`> mCi#.X\Y%q7,)X+prA6At'N}{;w3wl6]ͬ?3˱S91YksSClGwƭo[ƞҍIs - -tʦ/=gGBkaN -endstream endobj 5949 0 obj <>stream -H22U0@=scS#K8bU嘀U"`\.'O.pC.} <}JJS<]szrr@ -endstream endobj 5950 0 obj <>stream -HҽN02TҎhv~J$2 #f&JqwI[ C>:;s+s_pEW):,<[dOevPM*>CUmǻGhLvdXL>Ck -__Lsn?x{ CMV! Dk89-M`K bW1⏈,Іc$ZLĹaH;]n#9갯S-hJV܃q(Hh/hc'G[.wMZ@rը n17S -endstream endobj 5951 0 obj <>stream -HϿN00t PH`}ut\/k:2j= 4xО~_˔FYu^q$yZ5y+f=NJ<(/>stream -HKN@ G/0dZIzL${3m![ h -[˘LG;ig$٢דMl`=U9uS%X-^ -endstream endobj 5953 0 obj <>stream -H22U0P033473U5437317T0Գ054Q03170TH1*2432 . 1B!9ɓK?\ЂKL:8+r{*ryp70=pzrrf. -endstream endobj 5954 0 obj <>stream -H̒N@Pls-,BErj"V>ZZhGQx+) ,wXJHdgOt'"WRf|"#ŕβKLs,SJHiR&{#!50g?cɢG,aKwZۄ@El -b6;"S@1v}{ykd]klfYmpk} {&v+=dUbfz+N6[^l~<;ܫ=tk[l#Iz \>stream -HJ0) -A  lU=у7}>JJ̤. M&QZ*,I*(¡LeeU$8s"DEIRYkb(hbl.P*WObӈ*bUu)+es>?_Ds-Z @KQh՞4h)ʄ 'Z [KpY59llv|LV8mO%;.x}f5H9v H A3RNMp!c; 6kOi‘-!.ָ=tJb܃p<;'ͬ]Y8EyxwPՒ5¼x+ʋOyi49u<,n | -endstream endobj 5956 0 obj <>stream -HL;N0Q4M@QYZ@"T{A܌%Gr{y'q)[{C~ZXz2)5_SgC8m O/Rߒo`?3N*3@SP}%s+>stream -H;N0ЉRDrJ* |H HX * (9–.V13)W!ȋl4JUYTcUJˢS]׹Ju#2, -3IquXPkl]u"ɵlHv72I/o3Pxv/v-񿍾|vYN996XsJDO@v\sDa/0` K&gӴ舋E h@WЉ8 -~8za5!SS6Gl4#T8j7~7pggW\uV 0 4 -endstream endobj 5958 0 obj <>stream -H|?N0eG/iL -Hd@#f(>GQ߫(EUϖ*Md%W CV-S-^EMkTVEV>$ C'ݥE9g1] D3 `Գx!І:Y s 5 )\ ;Ղ&q-e_ ;vI>f,ҾMhJBI4\r|pM5#(a1111=rbͮ2O/9w9w#ڑ4!=gTZaJlh%']uP̄kI)¿fד_ k&"z -endstream endobj 5959 0 obj <>stream -HA0E /4@+Ɍ^DY,H|% W$ӚH mW;jlfX XjuWu7 gTm_wҨm/mM}Sݝa&ʇN/:}cNOtNO__A,DBKT2(> endobj 5961 0 obj <>stream -HTn0E|,[ua !5E}I7@ Y Q.kQnR'\v8OWZQ zPmǫOx4eO8/ns>xw`z/$//f -4v_}#?ZmqRG(ð VF? t+i;-]Y8-*ϑ&cҋ<$N< ;͟2<7iʞl#.={r%QC Q4+ ֓g!{^|({RP ~\ - -endstream endobj 5962 0 obj <>/Height 229/Type/XObject>>stream -2S /2kM@'+B+h5 ~~~~~"z?A&&/տJxo_&Ou׷׿^ { ؅xk^ (3|@ - -endstream endobj 5963 0 obj <>/Height 232/Type/XObject>>stream -2[ /&!2gjI[oվoҽ&oVoI+[aޕ7zM7WޓzM77ޓzM+oM7Z & ZL-֓Z UMl&,Z`7@i/K_TKZK -aXXV -  - -endstream endobj 5964 0 obj <>/Height 230/Type/XObject>>stream -2U /pj%AAAA~?F?@~~O~~~_??7~~߯~4 ^u_^{{uu~^}K0 ׵/ xaxax`/CfyU? - -endstream endobj 5965 0 obj <>/Height 232/Type/XObject>>stream -2 /A&xeim??A_/?'/?ld} 26!!2R8d x` ?*2(@ - -endstream endobj 5966 0 obj <>/Height 229/Type/XObject>>stream -2&j?V@mA@A> "PAAA}I}&M}&_W܁^WX?_~]w_u.v׷]iv v] Av% J.]  .  5s? - -endstream endobj 5967 0 obj <>/Height 232/Type/XObject>>stream -2 /+A*т< D=^A&oI&~Vo/[}+Vվo[&o}_ k'[[0ҿuۯ޻]Kzw]m-]Km-[ %[ ,0 ( - -endstream endobj 5968 0 obj <>/Height 226/Type/XObject>>stream -2$`Uo]o4__??IH0o0aM Aa | k5k | A5 }M}_d,X/k`P ->Bx@odD xk ᠗A4 k_<__Kv~/B6 -°Q a -<=Ja 2iI_2}à  - -endstream endobj 5969 0 obj <>/Height 224/Type/XObject>>stream -"%%k_/KujZKTj*T*T+ -PaC -(2A_K@ - -endstream endobj 5970 0 obj <>/Height 188/Type/XObject>>stream -#$'; Ou_^={t:yo0ääunAIL:AIIAIIH7IIH07A0& & tnaL: H7& Äna 5 5& jjMB -MBPjMP 2ZGh !]i& - - -endstream endobj 5971 0 obj <>/Height 224/Type/XObject>>stream -% l.J%`+ ˽'(b0 (0P`,2$x-JP wXqH*ذi d&? +RT݃dZR - -endstream endobj 5972 0 obj <>/Height 232/Type/XObject>>stream -2Y /󳆌K&R< xD4xDxAA&oI&o[oҾ[ҿ'__{^޻]޻Kuۮ]KimK Ka0kj- ,,0`f -vpُdP -f@exl;T\L CP -@ MC$2;٦C Y S!(&@fX E<6_ xg@6Wd$ٞCjS  +"H?  - -endstream endobj 5973 0 obj <>/Height 229/Type/XObject>>stream -;ڀ(vp<3 xjɐ -$8@8D -CAMH8D - AAtn : AMA&tMu}_omo[[uVi[JVJJJa+ a 0J V aWYP) 0(` - )ہ;<gDP<v\ ``;UQ2ASXiPk___^zzt^].Az.AtK].  - -endstream endobj 5974 0 obj <>/Height 224/Type/XObject>>stream -2 /E@/& vj9YUO+@ @Am8}fz0oH7 ޓzM7}7i O_n^׶nk%K털a d5..  (K(+r\bq!K?d2o - -endstream endobj 5975 0 obj <>/Height 194/Type/XObject>>stream -;(2N8,0< x p@D P"AA J @Ap A0& & &7IMMnޞ}}uoۥK}[]uuVҷ+i[JVVҰҶҶAXiXa 0J0`0`Cl(d†@8PPA -v NӁ㝄*d0k ɨd - -endstream endobj 5976 0 obj <>/Height 195/Type/XObject>>stream -;*>/ProcSet[/PDF/ImageB]>> endobj 5978 0 obj <> endobj 5979 0 obj <>stream -H4˱ 0 SbI잆7 ʓީKH8 [U-"2ggǩt+Q;+MMA6E;=  -endstream endobj 5980 0 obj <>stream -H4̱ 0 SblNOC Fw"t3o(),ڑ)` Cko2Q9sвul7 -0Qh -endstream endobj 5981 0 obj <>stream -H,10 D^ DH,k4!i= oH@mRlLMȫ Gjj(hT>88<[^`lh -endstream endobj 5982 0 obj <>stream -H,!0 DQSZdiyHaRO'M&Ag:X-N=0녏O*58Hljrb;3^x3 -endstream endobj 5983 0 obj <>stream -H4̱ 0 DS$񥧡dhb EɌ@SL}^ % MkN+ΟO) '| -endstream endobj 5984 0 obj <>stream -H< @ L.$E@Bt.l 328wlTfXN"G]QĕYKȈ`v + J -endstream endobj 5985 0 obj <>stream -H4˱ 0 S8ɧdh# B`H%[&Z+p4 V.еd/xĦ?_p Q^# -endstream endobj 5986 0 obj <>stream -H4 0 E{O= %3@!oߡ"$jiJ\yE] Cj`r莳Ӽw]t+Z -endstream endobj 5987 0 obj <>stream -H41@P E>x/Hk@k7 "-]Rn mtLJsM.ҩyE]>؆Q#C0b>e[k- -endstream endobj 5988 0 obj <>stream -HḎ 0 Sx8힆7Q;eX*b=9h\LO"ނ_U I6׋vz6 -endstream endobj 5989 0 obj <>stream -H,10НS @KwGϠkcbL"4 q]0t7USTҹKK5i;cO -endstream endobj 5990 0 obj <>stream -H4̻ 0 Sxm_OC F@ -fd,bUsȂlx -IR񫦁Z zp -endstream endobj 5991 0 obj <>stream -H<;@P @>^C>Fi o7A}fdt -RlQdj&XNȤPI.|N\|WI~\p0,q>`K@\ -endstream endobj 5992 0 obj <>stream -H4̱ 0 Sb;힆7 {jE`͚-<uDGq6)[xˊ }Wm֋vz ~ -endstream endobj 5993 0 obj <>stream -H4̻ 0 EўSĈӤ v+ٿ $=WĄI`dgE4 +.7UsS|ը*~87=^[頏I -endstream endobj 5994 0 obj <> endobj 5995 0 obj <>stream -HTPn0 tN /!ivE1%࿏8):<ܑr|7"{bΑeኽ#(Jĵ[t> endobj 5997 0 obj <>stream -H2231163T0@=csSK8bUB]$ɥ`ȥ"ABIQi* .WO@.V -endstream endobj 5998 0 obj <> endobj 1 0 obj <> endobj 2 0 obj <>/ColorSpace<>/Font<>/ProcSet[/PDF/Text/ImageC/ImageI]/ExtGState<>>> endobj 3 0 obj <>stream -HW w6;=pRF~JvIBtb%V}_&˽>I3,4]ݕ3jk~=1d>(=P`G"94PI@xi!ҀFQj_[Ep:OAt|6>0"\F 2~a yF9&yDpu-K^H. imq]$e{˜VE[TjnwZAN"=Ń*(dJ #Ձ\.Y# bxD&=7o,:p v# %d`=w7.9!BB05ހB!Bfz E} -ȖXIydc{jUеM@PR\m鑂q9K>ry:VAZ[.zQ =h5N+-9 !7GY˜ܽL8 3]cٰE{: Ļ~|@G#Lg-Jp:kk%$FDz4\1Yx9Ny AAqI7)ܟr:`vU㤳 jƖѧs_b1+T+ Bp3?m* xQ3PC* CN@E xj-.'lϵbq!OD.F ?ܶk%>~!@zi&PX0(iO:)7h[?Kekb =X4#J#y<l%1xXgJظM6?P+W"fcT {q:N'O0)W,nYFL'@s.dO('-p\Loߺ^}\;?UcL_ sӉ[_ml8W=9,Y[ \uk/,^9mCx7 Fͥ^1*C)Vśvv}rkz-l9pr ]j֯lpjo-7AGfHIh͇!>UG">B_9 4NyH$3TetSt5P[e wB?$ܜ$IbE~B&ÃX LE+1Puv U)3 -g]|^US|r|\[g»1nxfpXo }ߞM/%b/3B/#f^Bh'hM )UsBg%ܴOϖ}a_xGL3srE̮;FhX?{bbvi]5b^^{dk9>ExLЫQ`rXq |RbgaK"5{^9~@~ȆʱFm"!ƻpSaU~i\\3,₣>ii6Fe @cÀ |' Z[ UCs5K s _Й6߬" -J5YH"%yVۇU D("s0 >{{r$x`v dFMLc̔1yаd?iz"+9 -_>U?KMl$ 2Xܸo#ޛO#9\ -Igss"2A5}W*3[zK`ǽpfхQ__B} Heu<87+G甴Jݼb|ѤnuvCUI ?๽.>{O|Mv@s>mFښ8p9(Jkj{* -ٮcʆ^j%V^T'r!vpI>33ӽEM>9>j".36OnUupm3E-Ns_rl(?[*11xaS% 2C'eTVnTHkbkӼ4wGΏ5)Z1wo ad]l0"f#ȔY.U+kWlg)w\RNGd{jUGwgsչl lb6Çç4I#}yFV~nޭU&p1\# -Lsww$Ne,ܯAOꖓ90Η0`9 BR(XHKzr|JDyT'Ju;nlf7&K6:U[ʎHY\k4yd/8 ܌598Kiy~P}\,,"ͲE`bw纷oM|NgB'>2/27 …ߪn3/RzWul HqK<I6yLEdh JHrޯ.͎bznpWACgw>'N QՆ#羋|oB{hqi,cSN'c]Dqi PrmγN9cA TY'h;ɜn{Vѐb5QRBC"pGu(''l4;!9Cur(x4>}^8Q S?urw]$LSv)Z*%OtMtr|h^d2fԟV 5۵M~SsC ew?,qQT2 L Mn28!w%E("?RE)xZyfE1:AHw߁0.8mC, -moe*0[v?N:91ǐac>6z --E R*B*Ha3fby4n긂:oq:?*D˘q˵:&i2>Oi}b؄e0js ƨ䑧PX2kxCR)Gӎ9&ɕ,]+xp}{R sG&#&,{[dI.Z2m^ȏcO2+>Qs>O@N?xck53=И)dN[%hQBUTazޏ ,uxR>ڌ&a%̣drIlĔҸn }*+H@>"ݮt- -| -.̌]1i܋1򌇪a1lR?J4R##b兾h - <). -f+T,#g?7=L+e7^W<$/~~PӜ}b1J'/\MSѨf -%S| 9"\:N+sx-m\y::zYPZ H Eh٪kJɪ<;3PQtRLDžy#V/^ --Csfڐ):"̓tP>Y41XɕmtwhސR ̟+k:XM96( ix yuVU'o>n E㈗_sYkw%U"Oʘ,O{vԤOIބtqݪQ -huin F0}<.Q+XDqd3w&wDCfhiu41҅gJ<|ծΐ4MYV#_K ̽rjoFRY0Ar|/JAEJ@' ŔJLaY[hT繳t9aCde0gN~wm!>5QO]28.qք?F^k^M[\ms YERAf(\eZ77p~,p 0J< -mdp4i875Ij4K2rN2~ -tnVISOT!fQD:+)*di`a.pT]A;*I:R \y,jHi%,Ѥ5N" !,bPvh#grdJ`5CaqEfe*}:AY0{o@GG2́cz'R1 _X^FI`m4;h$w`KVDz83  EJ0Ի9Yz@be l?EM ^/mvM{/yHx1K*Z.Ǟ%=ƍz 5%p _ Lo_h]EƋ!hh'y.Kw34OiѐQc_KG -9W|TVYYɼQq‹x=22 nce6=̽% //zN GeD5M}V3aCYp1yd?5^7Ѽ . O"W–5ڇJEsbh<ˤ`J55$!*%J~Ur Uܥӈ.cDf-3s+2qi<3U}[?p9UO޺ ; TX<UfdQ'd-,CӸ -]qe'_Z KkM|6DQ R$ -Lb`\dΣH!!Ei,`YP~J%߀ <;wv}^ص{Νs=wd/Gpdw 1 _GPJǶVO:rwP9gSV7}|R:'DInc -EZNp5\#8˯ks7&B͉0USAF0@!Td:P`>X`%S(v"&h -rǀE HX^ )W s -[-NR*!*Cn,>cFH3<[3p%UJЃ _ =jYW5)AHӪ#jQ@NW߫X>1+1y=DGF01Re*r -SG|'GnYb0%D\˕Kn=0aU$ǧz -wNBo#3X'1-[B25ujܽ[qb7*.t. `c#b%{sR{LA`F+4۶__2Vj?뮧{N7A/zH^A7Kՙs2-^f:wl/BWɠq)d4+'Yÿ`MWo[_'*^v -ʐE0ԃ-NfI.b03 RIR!5obµ&N_]-{ӚzFC-9],l֮̊UZ2E7i|ƽg`G.C'\Ԧh2ۨ߀3e/~ʡK!~0QbhvQD!~ը)B64^4@>stream -H{PTqi]e{.hDhEujNe2ZDm:hV/(jfԊjR%1xbPP+.Pd -awy멯pܽBGsPgMٰtъ-x; Gg@,LJ52!183jUevǃoxVEƆ=q=8UYy+)˫'j[=h\5w0W9RY|S^SU;*)RjgùtJi?*oWO}ߺ)Iu/5HYqIQi9]q:5C?~φꛩ}xzScѫ?v *ˮ4֮ ۖkPJ-kn}>^鑈;]!QXiI]]o~ZPF"x#wr/5F٤UJGٍCZYȪFgBWBF_>Hsj ->y3G;, **Tn/=4O7 ^2:7Imv{>ͪwIzȭolXՖҭ~]ע'L-GNGmjs|=UunN֗tWl{zm5Rx$S,rft}"Oi>hyDu}FWnPqW{KeܑegmsCʓW37_:wn7 𔼕sm:Zם_VQ0{SyQUYQqu+Z{ţm:oG'd/ռ_ݦvG$gzCwX]q矝<{^c9sN={; <*g;{Ҷ+9cOxO~?_KIHOɼlbtPzF?.ٚ6 ->r޸t9pKjxW.>i*~0oQ#b'L_ -k:3iS;<[9l%o_Ͻ45aSg&e?:DWңO!2nSfAt{3o[AC‡}pi _!5oK;2*b֦ -{E7zH9ߩ4F+_˺w['n̪hWE;og3/ucg-kkhmEwJWw奇D&AL {<}^j a;Л,ך6 -J벒'=[E"fzA2z"%WM;sKcc8F2-q}+:!~~n0`Ќ=μF\+F]B -2EG|/T}|$! v7sic_xB#5]\_u*q 4𕹫tj99^ۣuӭRyATNge_Jm CNR!~`]{D@ָN$ƨʜYcc1;W>Eb#'2byiݑ> -&r=:T{IF$~~q8"&T,ό$sUji޵_gff^Vhl٣f^bY%{&Ⱥl_EAY' 㳙.e^I1p<|G='e®̡+WQ񢖍>k@Sq`~R*%D '˾v}<+ykW#1QkO8sj*̛nn4ZyqsrNа=Ή1bAwm_bpʢ{dъNzL˳M?}<%/V8:2H$$qOEm^ -QTJ ]&œ%s<(rtp0B^kgնc$ WU䧪4=7߾:R) -φP^1;sPM< |ٻm$t|P[\g2"F6*"@,IZ4Y߅p8zx@IG|G-!] *l ?۩~A{V{(9j݊LոیO3olrl Ӊ,`u-݈WxQSK KjC `r wo4?R6=N[QoDO魕:H\w}[M1R¯j\fxG9t|@ -۳[ Z=2E3V3xt=M']x.tqpv7Ѹ%K1^HgqjooB]'~(P'i^['; 4ƝK4DPg5,oϊ00Q:} Z#/IooB.߻>R$0HKk+Dbg|5<6ްQ+*Dd{ ɼwEbhj#] 0 /w-[֜UƮ]Lҵ[${qQZ8|8mW#1tzUAG0nRor1cWf[vćQ4jX~@ .#1ß2|XkN~+ׇWΉOf$];F+DA~Ar-(%h<Ο/N?֪#G/8xSn&ϩ'3:+=#qէ1 -IT λN|'Ҡ׫'1of;:6:a?X_DF_f3k"qQ&+q8%0[tz5/>le `L}"s&Pϑ^#KSxjLs}/~;Z 4ӦEOLim -E]ly Λ*xJ؊{ު"q2_#pjڕ ,ԬS_b5菵m݉O dQJ5^ϱ 灯Y2_Jײݎ/TLEu4B$2Fu/tQDnn%n٪h{F_%HШwT-WSicx6GRtQ yOADĝmՔ3Ѱinޱ}۶7hfxsucar*Qsk^ʘ!Bσ$_#L/)Eoֳ UI\ٻ¢יI%UiлGt7k5,*]ՏqpD-׽T7=o8gU!^w13%h~ypqf:ȵW6 }j "XnCBU*m*t VZ<Ã[E!hH8RZ$`BHs@b҄$+0$K|Ȣ -у2o1h<;]l7jM&stL94e(1aUM@Enq힒SlOP_ܸH(;duɦc_A~ -Jޢ.9`H#˲Ȳ$76b4ͮqSkbڲNUkT$>7*֥Hj4Ir-xbb5y--~mqz7Zo⾶pK(+$NQh& OrLam4=4Y1k%~F5=Ep^WNA^_yv#QDu b* -Ig]hW-+\ԣM5K4)vs.8fdlS?!jFmsLL=_9Qۦ:)?t;nm'ޜ57=lgJ鹾&\w[IW>hQVٕerNշ[^_Sx`n$dؗQT/ӌ] UO֤0.SW?(9#ƥWns1cԧ<7ooJr'sCr8WtLܰeKJLJ⻵=g'8JbwIˋXd:d]ol$~]`"VD۰7W -&tA8ilٺi_rh&u¼}ig5̬Ԅhi),&{b\QQszc05$J3*D1=UёxRj5aVU -AVب0k|vѭ1mG}~7\?8mMO+&WI[ j )5Ŋwއf&\-7 ESuR͗nQ.th5U#ʽ'y6zokH\]]_JXK$c׏v!Q~uS5*v^^oɩ}a:(ƽZ_Cɉ4Q$-%JkТTnUSe9OHM C#1ŝ_Tv6%'# r뙏r>z#W1#>e +Z>qcyH:1,-' Zˠ#0"7^5#559m=]uCCQ{ S~ۙ}4{o@KvjfWr>7//?o}sJu^:sgٿ|p.+Iu,9h;Iu5}W@@sAk.cM"V+x?|̲lA1`0ĖrbiUjcAGn.Q]˚*"~BۼaS<b SCuz -0!ǶN_-9ڭJ~va .➕X02Thw +Xd'.V%=nQr=]JcDcu\kǝsyuO]6q;e`m1`C'(_>Br[c.3VKTJu~߉ K6z2DurӤYЗx -0]?b[s#|bk6V'ѿR8śS{&Oy'1Ǐk֚ *KcE|ɨ+w+#iIL'mʫ//wcJȕLѪ;d O{UvEJ!u)I>|AH?tFXjt{mŀAhEņZ<#,l^#ߋ0Ŵ y7n˺u<T%b -%L˦s~4AV?'7:yҒnˠtw?[MU-Vݹv{3 i'L Vp__/wa[d`鶶CiK2?k՟֊;41hhod:=w<#{:ULѩR}.:ԨJ\|:ffgl9VnȒgy ;0-gԭg9b#FXE/:~"ۼW xu\裶Q`ntm.)'䦿qw[Cս|#ҟwY:GXeZi;4NZa+i8T$Hܽwޭ711"0G Z/ +, - +)톏i4@+5nڜkODl>~Xg'%%Muԙp8R!,IH {rBqo;G ō3#ZW\At.; :hUDD ,Ze= !$sX/7_}GkgڵQdwc'x<Jqu!C+5z-Ga<R3 Uz-Fj*yc]y{V' -;xP(|x'= m6kV^ 2$p NXzGX -]0-ح?'nې6?E!""1j{bVZLRk _2_z]kPˤ2YSڲGߝJu:&ݟ!cBgl} $?W&eKEaCJswo;نDkA9hiUZoEO[]!3NČnO˵1=u S4a ndЪ{OT:f$[3 85dI ^U'gVk҃0.R  HG/-~61&rlM99/Zdz~M\/EQ~ѡͨ[nĉ/t#u SB:sڿ>&Ʈz\lI$㼚AzfUO_\ڛԚ;196>n>OP,<˅0>eJ1@TΜYynk -iCMx\΢Pi,S2zL~iZi hb;2uBL0EX@(C`QV$K{6.N.\ߌSs~z=nbbз4>81zR -{b@7h5D|mI^ $FtQOK/$9cMPt֍jk5*u04=#|,"2|PaP,pyxVw.+O,5X gO&1?2KPG?kPAY ܧ2=Yp*aB/E:Lyiw?Oۍ1tNqvLg^2ޢP876[;>oN(O/ x}:`%o=4]k ҷ̵M9?=eE{ /w"9б~3Wl=p,gcIW ~H|Cc -c|]leS>aTG]FSWkNs#Of%M]d{R2 $燼EqUMmK޲N8yڧydwZQ`B,!]99[' ʼnl87Ȋ7J]#$_62'P!Q_s -~̣0 Q`V]_ntQ~0/Dua>uePA"ٰ|cO=l613Ck^n.KB]Y36X\q)kRdeƑ3)hMrth,*=jf72aB.2-Zg|)Ţ @i| 8 u>VdxQzA3 -YL ܅AWc+y)rXD+7-'ۺBb>7u_a7`%4D<=uv>J_w0hϳv}ru07uQyZ0{cbsOjEuNuQVv=a/kGݝee5Kɜub4% # 1 O.m%=D XA7mԆE\wCjIcmjs~}j.]Yfwe@|g"L,pg?+NlTna-eJk`2;߲bɌ^CWXvq׾o sCuwe@tal&1鑅qQf^m~qBƸ˕*û/|Ȗd<褫 7tҫCy?G,¥ջ~5uq3.`a [< -BٔU@* ;VZZV>]P("°VA@ ~@y\sw%:'5i:4أeV 1}~Z_59j -4 "m~Y|=呲ې欵7+B9;e.LWo^X?{'1R=*lI2"@O-LK --IGM񷆎Q| "zˊ[ʩr1 -Țy%7tf,F> a^xex5bě~Qm䠛C ͅF:,* Y]n?dsFSC!~6`P)?z)?>p'_d0!{?-q075q_<. %Wn:AZO{cvA ~HOA^n-%ѩT7*?<8< KE}GI.'ńj@tj/K t 8zoqs p"i) -E ,p[ -baDӽWU~QC<4Qz!4$e{+=Okw]`%Ba0Z&S9=^hsn~_9hlssQpexm@H)LwҼowj&Uo570 YDڪ?0ң[)E%! cgȁgbr5dmѹ_.iG3sW- oE}24S>'4X(H<2Iv| -`X "`K&:CqPr$ & w0l!B)o?olQ#~9c=@3 .{̙,&zW&\8{or+Pߌy?D;, Wp*܋8G Gg gP#՞ Dx;󱌗RӼf'^1x g1$( -&i5aM_g`NPIk We6I8on5VYd*\vqԎz;ߔ<+k&LZSPbqx -q8܀v\@ί߷8$}/P_20 X]c?6e 3p ?+P>gizK,-a=W.obYࢅ,Y$(x -?Ў ^v E 30' "vRO0dq᫦ZhYF;΀5ar*,:KD#5 ]44\8+7S~rUG-c+X6aZ+6Q!dIGδܣv.7yjqSBOn']%phD"F$E;+Îaa7sXz8U:4*<9s[zјNcm i6$e}xwgÉT`eopCrד7SD"Tvbl_N^ +&4^K_*Dž|7Ɣ%8z},ZLsT\xlFcorWԼj-'4YFԖΖVvҋF UzQ|b/J9u~y/.?}:Jz a3Ҿ"]$2D"z?AYd"19 qDlֿ% }K" Fʬ[Π^zg -yg7Z铈([Z.޿n,GG7c jʵ\NRJ'Ǯ4N2'Fc..I$*){Vr'njRJgP(MpRHKM^jcfL05`GLO6xI.mfYIXC1h"?==Vc)sNTOS˥[lfht%UlrjdWK@Z]8L91o8c>>k8ci[7O:(+#]\Puc [:U2|ž 5B=m2u }N#d`IJ18hQnֈj:3j,鑜w㺹D=٬1H$OՊe:8gM0iwTAQ0JcW\׈.%1*5Ʋn"Ƃƒ5 .*q Sc`FXi|]YB}{ zkmuY@>^^7)\߷oEW-[kgv?[ˌ# 2DH %sR91և]B -&pۯU^`{EklH]>lѣ)۫Mvh2+HPk3.JqbHō}1Wl]dpyw!9`>F,{WEIM]bFIa  #H -S^ Իlc,N,}ZxNJ+Ag,2xcGR$ Qx&a؍A=` )|)*y`dtG8BJ-n`ՙ9F)>TR%Z"`/*!pXA{k hƠ}Zӛ"p֢ S2鿟gAt-&_ߌ`HgʶkU7O! d.ho$zLP IÓ|r04>ՓMv:;R!iT鹒#£$kN^ё3厞i]ih}yIeU7p9t `oڌ`lGOҶ<'}zHƸ6߯:mJ =13y 2{NWC8M"̜b -km\ X6 -ճW(7,Ye~4+7=Ӫ/~lstX?|TsO+W{_p{/ѣPȪ`aD𯋚}a;'AĔ\qz#nƕ|PoLNt -'$&uiyn "bdiW`(A -ZiO2w)*s3bgY Q V-Oę -ht+'ì?ٿ(Mbpȁ2SY4 .=[SBkJ?+1-+( Wm(e)0Ȭl\ʾqsGW|hfh vw9Ċ5"o[$`?OcxoSUE zI\)&#)p?dSNUov_>Mw[Pnܡ Xuipɪr'QH(脲C:"3*G|r sy]ζj|KvfM -vObN1Z;C| 4 UmyR{HL"}?M14u DReOSYG* 2|X(4džI*rIXp>).FvFb -n,R+ӸpAų))hZ^$H(@OxH191oF9ӌ~ѣ8]Cc.hM :|FoN5殳Mm>tMxE>VW$}. sZ^LC$x,lT;#1vYQ߈`c }ӆ -3W{*ÄxMo -8CQFt,W_zw–ۼ `{+ur!gu#-ȹWݘC iPFvGb"ڵR$`eϏ6t}qyoç0,}BBA9tN -pjļy9GOP+񑃼)%hoUn bcmҸ?E,s-hr>"Y6o>W@fR8)iN`r[VF^¤I[@ (B8"iyuyi=fegc@BxU)^D[kS&MHjb#ј$xKOl *ZKsYv.%<337;EUJn;np^Qj knZFwϗ..~CV|b*Ε" \Uc65񄺳d{NKH -d"[J e<' ީTB2 < 1go9~Y_踰ƆFnU+⚱::8ICŔՓx==0N2h' B9rFA9:xǞpMO5eQlN b4RO{rP%>ogfxžY񤇫[[="Zɞ{ULV?Ē:y5aR|]'՞'̓~IK[Jo1Qnf,IrXu BXD}ܺ~`TK%Vl̔ -ɧv/VJ.TW[:/SW$gMxCUѺ{Ƽg#Twns{iw ;WlF %T-%)90Fͱl@TlEO߮RWeԉiɂ\=LY7Ν m:b9jLP޺\;J==3zRFM)dIʹNch--qrMsvٕPs%).gY)DziGXˈ֗x?!{Ue96V^o-RwKp'irN @$G&絷QT1c~` KִY*Ž_Jy1;TSt_22y6& K.6bß~MH/n4LMF}}]MNWUotVñ+mrT%njJDkX5o~}hDF^:x1kȘ*'T;Xa^H)$Oр;pƂCDҞAkgiL$Aēwt:]eZ3'9yU/CX/:&UXF!be3%ޣS-=/uZ@CQVX #Njy@Ջ.?M[aJ0NB(5Q9sXo'eAӖjʕ.xWQ1N+477#Wwu;-9hbo%.Jb-G=Z%xVrlOD%[HRMK:U5Km֦-X#K]xwf^g{LB("Otk&\+y7T`j4b͊)s MueDWzIxNⷩ߃/F-W=d  -ON߭kĚ:?|%xVpp,c)"ػ񼜟MEs5AA_٬] f!5덆l:+A MbI˴K:v1+Kym*!?(Ý `k2;1](Ms SGvGCplCyV˛ri(xo:8M,EbPvU R^u[FH5u3C fе@:@ܦ2hhʪFS˫e_f7bD::@(JLM IQeymSO -5d/e)[EO FW6_DU2Ĵ~^g'W ( #SX6@ג'/R;8Xw>jtz4};KW;Jq*4==$r\" %+jhoz-M.Yi黯(sM4Z-+CETbS,"aqVS "-"( -8d_RJNXzphk`9w - }pǸoҸ) 4x saT_W(Ȫ]cܐ㥉ldi5ˋʿGyr5#(75ðrbZX7Zz{G|WcNcyʲn{4-pl 5|0V<?+$$%%,"ؙ h8-ZKg\ꊂ (huTֺZ `\F%, KvBVhph9~C{TGSUܳ > Gw&kMv3E҇ғ{H4Uqds_ߤ?rK]8KS=K~Y6L X/M%ؤDCܽOj^^7 ;*)6E@\%&1ƧQNyi~v/ETG ٫ӕ_8*GEuFNV)-T:h2#9bEɧxc}Haksm !ٺx'\+A$!5u!M!1_ؖEF8a+m+ɕ}"-TbVȲWO e?,ڥ;6(4V `(8BFk7h*4 +_%(Kړwjt&Ω# >BĢ-*+wG9|͕qAiS VC\xqD7:Y*`w%WJ[V]>DrU':"~C8̪ h8YeXKuf8\c:5SuBO1Rp u^|__l:Kj>(p3XB1)q/,4Gq\$9QWlouk8eo/ftmP# Y.b( 14,~" HH\%v<Йl -Wb}6 ԥW'+HTiΤ']QpY.AkTz@/"qOaAtsRt4Yb1b0A4SY!">?Z*1$JzE(+[H[!@$Bx߉@gb;T-D{a])lrlhd+ :֤3e^eF :.aG:CYE1č"Q@ (B!"d "$IB -D}a[Pڌ@:,׶rVz1 {PwtzZXr`8ZJD$@Ȗ+1?)"?F`ױ{thB.ÀqWga0\V#C=nl!s46~Ffe%t"-Փ"q3 -1n5O7,!խՋE0]7ܠȥaB>1=cWGЙG<*1I:%MX|ۨdA -;Y9@WEtUpѧ-//wP ǥm0o9$s|ȱ+(-oM5kR!M ̚?Xg]]1{Zيw(/o_4ixXa^GY3k`>:&@ۙe{PRm VK{V7!!бrzDl:G֍ -^ZJy#-][|z[95/jsN;a޷?./))-.?kKKKKKZ.M ҒV?u7o\2s|Lܴo*q#U"[$LJuN6=>$dÓ_ ӌjʊQ?vRipහ[lt| lCS,;/]]ˌ)ՏPg3ݞlci1h-Չ")mo)~TQV PA>GcʲekIqj=;OS@?}sQߗ}~\iZP*To8ͅˤXva,.o8䋿>v>VsPyN(nߍcNݫtV9@KpR~/ԟ/)كy9ٙG޲v动Й~X4k Z-It!b>?ڌqr.a(;XǦ& -5x'{>cMHiN#Py}Փ˙$^9NԀ>>сjGjK6'5b#&9LWrY މ+K &/cT"(6ZNn8Ҍ(`~gth*e\*i76=u)?౹Z)U'Pb(4欟7F1#lgZc0Rœ{3);g:79qރj /& KmZw -Gl+NIH ` ̎n:F E޶*ZfO.w+DVD q=㝦fR>;ЫsSX5`6Olgifp0<]rp,lL(jC [0҉j(D❦Ħv8/b5,HUrBX,Cb5S`(4"ܭ n w g: Nt)dZ!\jh|j>,8K:QJ|*1<:"Z1G/dTZ]ZKaH}!|UGGܓ1  }VE TznJ PEcy\'8} Off+b0]8d}bƪu NA{Hf@qYˀ*~KQ:kJ(d?8hq`8Wd:U,&)RIZzD`7QYcQW%8T΂puW4VU\ai~-yQlkA:LTb@(ZfgAt9%>|\m! 6=+0_aMgmA%@9'9I'7*NAuPM7-:PPVE磨-KT@Q"@HIH2b uw9o0Vcnm>, Dܫ1WBlTbin4a u?d-xeP=qG}7t/Dþ++=6VsAj(݁2fC"Lj%@%XٙHg pPHg2K7v,K}URK@ov#=\)>07mG( =]2vZoG[ j*1Jh -B]=0hLrpDoX`!*M].ٵ;umZuj# ƼvTߤ5'yJdP6kHl TbPsŪ m^ނk'4 .7O6jy*ፙB q% ڝ'w yfO -J Ɯi^Z5A]PPJaa? -8ḩmV~o5q ?%}7?W#7k4+h'[ּ1Pk96]'BEWJv'G?2q `ۏJW^idK_%$*=*1w-Շfq -4p7/y$*}Y`p[̣;<z,m;N'y􆹓\-nޢNSb">/`OKޔ1GК56 *1?wrGn5S"ɣ:]ΧEN_ Q}@ҞU=V@؈WRevNE#KU?zoY`S$9#(;BX9H0K%g iv~?%x\&FKSQP̝Yi{S깼E J O*AXX҂w=09lάIp@oaߋb914SoV.w1L8-MHuJյ_GJvA%~;Ƌѹ\ƴ+ŠzU7D"9G_WtYu3*xZh) 8'EcRw@utJBoFGxn={:2mLҥQ-hDO\)<]?W*`:iHlMw9|<|㟘&?kTJ*h`qkk:.5-Q =N4g+:|?ŋe:'tOr]hexL/1CU{AU#k* zw~MsNդ¿lTb+G`vøH fg2/h:~XdL:.<\VnB94aG'lO쪖 N@%XΗAvKĸꕃy|ZqeaJtm.3 N3z986{ |;JkKtA%X֕h7⤓2א:0ΐKVA\;0y))FzXT?,ʥ,2VNFX@3" /Gz1IA%X}Cc24xG1N]Ja`\ϝ:>uvȇqPOߩXtUl!{^isO#dXroVؽyt`1ڦ8qxG94.t/trvǕhuqphoPTb>5M\T9ۯT* 6̞47' `~P"j -gu$=k}m}{,_i(߼-Re^~~ɂ"UENJ,@nwo_Ws7_ӣ,XBŔY8@TbeH?tpnx*jB7hiVI%"G.c@$Tu(9$ᕥ}$G 25>[xnKѠ{_^;B",;W禸+6zNXo#(Equ5|ݽעlD[l?Q); W$ Lj|pRF5ш)[ky _.Ξ>Qѵi}}mb8 *1Ī6$[3(\ C-҆rD#&)xPb%/Ia=52;fJM{ZN-J 8/YK+iSG UTuY;imv'y̥{'IT=Q8d8>LTfx7#ĤF)KR`Xn3) -ؐ,L.B.d; -t eV{([d!$~W;3`mJ Oo/?K ~Scwl,_W:vI}f`/oV_y/2Tb`\ٶ.Bic4I8pa2jcf[G~&RzR~5VXJM?Chh7RslyZR6Tb`.ar_ C:^3eygj/N) RvK%r Uׄ5$EPrT^bC;Y -g $bAn C%.b 98 yΨ^;}=5"L>8$P2OORSWU݈>RCXoPNa_/fx5>r8Tb`-qkN;_~cׁ̊ƗlGF~΢[UhH" $w Tv#HR%\UiG;K5!.b壏 -]evSh*10D3fCuUUᱵ+%3ק~X);M}>Wb/9r{t- 9G(9YeF Sn~[J k|Gp,G|Gm˦ -IR^~bڦaMfֹj,褉L=6VTÌM |p$ uI)Vwb"nDOD1t&]AhY`-P!Qư=4 r8IBc8pCEf՝fcU;O8h -W'7֞/6s!Tb`pk ,kT]v l[ٳCj6G=Tbh?ڬM(,QM8ZwGviTO+mnbEl гzM#?;C;CI5% ʻJyK-S:w3-FqƋlOPݴ|GqlGǓ~4hgShqԞ&IW8n̓nNf&(*T+"hj[Q(P"Bc!X" -AH8B٢EggѮۅf6&Lu.=}w`f,Wn_?؀ij,.7?GReee;=ts(mnj5.g7g\&{L*LNAY,K&`04 D&I2J"AbYJ7[;M\FxfIE}e5 o- G[Da&z~‚Qֳ!E..2b##">Yڕsy9n0pT5`3 ,\T?½cA"YXhY*D˗ƒ1"(*6HƢPXBD['[GOU}c}3 r맹lxayffCn}#жޡAc{y - Q /3f8'^2 i y"Š+c:D@tX Xbkosprq8Myz-YB9aMy #V. WUU>3 嫪v>}]=ú$]~S%dx,‹tV}~+*t -B!)1XR#I$ -A;htkbl.7ࡶ8;'T:uB.x­ ڼ%d{xT$.cNIS]8o. QxTS|8i^=iy} ];Cy|$`1LU1Xb{)G6Ξ%QhLb Dj xDbX[X Dgܤ03e -BZ`jܪ!a;¶s/bc%|~[\X6kn2jlhln+ϻ􏣻/M'b@ihKO4}gз}6%HDɁ$`3[&z38Ë Q&;aO|WqbHŰôdqF<@ m]0\f(xzzʕ- Xyfݑ{0Oǟ<w2)Uqr:WPhPT\bPZ1ViTi~% -lX}%Xy6|eGDBX,u^pxW}HnF8n=&!Z'13dɿu=!+rY4*li[<mlv99t˄ĄS)ϧgf_]v..-śL[uS73Pc8]́6~ĥQ^qZ_S[__-BM d.NQ(08b;JL7--]Y1R3ѳ`r P?CBفDbnĸ3P:#uOv=Py|GґCRH&W(XkGg7M{R2U9 ju~Aޘs ?54oڠжZM٫^(RMJkr/w${E`HfPH$*.tZ+ͭ;ƽ zR}%Oe}; EBb7qb;PXO=l,<3P,EB>Mg. 9xRNaEHcT^yhh׶|3MLD戰 6'"XBj s5-}zo_;},Ef:m1~qV S$1CRGmc^{dWˍ;,RLC, h4*X;|ׄD;yEꪫ *F)qK_$!OJ_WW֖\8}4joXȢQ4* -19\~K:p*{_AQyS+[޶KSA)E0sK"O=cr`(%$%(²(܂L$h^}>=,ߧe+iUF_},xwW1%`*@%>؄YYxvez b<ڼo/Vsn?>f`DR9):)/媋**K4$G_ WVɬG{{8`0$JD<G:D~+WS^ETSΖ 7M6]!GNTbr1Cz$CRbc+hOܿvt7%0$Ș3wr(nAjt/S.,4h\ -eLYu-PQ۴!fl۹ S#E?sNd~>jSr1'/; ! -2V;Ot@y,bb7=t'PJJI8Nw5@7,NMf^M8~`džs&C"dKa[A VE<}9nϪt%{ʆ3iu7IfQSWEw -bɮ=Lj-Gfq EBPL2ngVT e@ߔ_YIhteUEdzJblmRbZs}z\MEƖcnHSC㺚l93惍u5yo=94J @歳.9Ƌ|CQ%*A0ȭ|P9z߸;lPS$ItuJEfgLbSXD,ݷ BN>Z}*n|qO]-M/\c:*-;Pgٗ.S]tj^g\0AaJLcp8<˟gUw"N'p{_銊Ԥ s8lds8\6-pѺI߽-OSh2xW)4J olo;PPSɸ%L>0 Cs -^Ғbm>S|mqYYiIqAƵӇ]8e[@rDmid33m2.QTQS֎wښW'!fb=7'Ƙ9;P- E%tg -3w.t! /Û\ ?u֊cɷҳIE]K'AR*ݛ/  8\H,(" R8yN ^dՋwiJ#+FuopdcI޽rCs]/?_2V&䳙,a - o=wFk 8mtpx=R鵚~b.u H{og8J?N&TbzUc9<|^s Y7m&d` RJq1I:MieEI!E -2̅ IUaQYUeQmL^NJϲeŕ)l9rviS[W[P~(s.]} `@%1P6>Aw*\'s]rA`!kuI0<8$ [P`IEUp JIdr4!v3GQU\8sz9O7g״ѝLٓkܙN25?ɘ-,f"h̤5@lygc"Lyq?d[.hbHC[p3;ˆ=I!mb㋦LᐈŒO]HC`^^A'tFjŠ->{˪arD -E~uS}@,?Uz -OgS@&K诚 c~ر9DgozoK>,B.84ktY7!eG?TB|> $q[kkk~ˮ{O?W}ߵ %UT@e$/t}<`, 9#n9hl}NF9Ğ&^'(~„Ih)2r`%x9oyf2~?>Fx<mUjSx@E=ivybgFFzzy.̬ݻ2wg:Ǫ{nY{M~OƆB^|,Fd -IRv?UV[MS`ϋǕf2#=I K8Om>T)ذr\¢x{.qT2|0y,#N#>aWslCj%C@w-~Z`~ ~pHvꭔtHhZr%Y p<׻+J7%AbO#X"*K7,8_:Fm,4Ԫ6.R A#u*%{ܪ$6xjӇJ#-ɋZj1h -nA`_>XQ`j|d%Flkw?'0awXc^0ͥykAlG=S7Q'9mp -"Vr _gƾw  <:ꡛ9ܵIAoգZn烻7qSTLJI$0"4t:vrjQ=1Uae/$Xѧ`;vNitF/$gr$&'_rEQha+ٵ7,A*S@7m.w\&YI-% "ڇ ]Y U!BqyV^>ݗ$"I!e}'$d>y% kֱTC헗p j <=}%2A s(up:jL8nggM7NVEQUeUъQkULN]q-fWx`QT,H޼GlbQ ȑTfڮJ$|>hf>#_Ds6W4:Avk&$?^ ț`_1J򛞾JM'! <|fS:veUSCĜ>~|D@P[w al'C!͠g@Pw#P)AWrk  /&3t=:E("M̓hjîl2ƣ%J\IYw}34O+kJe,*傾Md{V -zug0Q BTą{;,wY!I<{t#JDX $M[؅2ѱ M=hc YC[w$pw-$kv\ٻhR& 718E}s#BCdX6;3.8 W@PwTys4 }8qlV/&'!^WGnZ4.Bx\^*){TT,&H+uI 5 8a3yI-kl&7RKFyҌcU|U3dzIWBۨ- Hĕ^t|(W@Pw-с(!GVAPT,u-^luTdEb9¢~uvT:$m(_]I?]khӬ^G Ͳ녎)/+֖OH#bVd5Bz*spA$~o)}v]S -zu_0R9U7+AR} -/{HZ8* QR&EĮsi1w>n$e[.߷sr4Xr^d~CsYDL!Hʮ}81Wb?>/L`U 4poo\0է -B]/-.񲖛P #qAm>P@VJһBҖ8*zyWIZEǬؑ1dK;>L=G=ʢ?AV+dlfO$ip*B1Wr=s\Ԁ w@ &[2cPoo#7 -^݊š&+ cHa?_ APB=/)J,WLgp[ѕÄ8"TS):CP >%c@;hYA GeQR,jގNmZ^6}26q}H_t-\$ n^{~ -^FkN|=Zn姼AJDgmS67 -` p%w{#^i ^FyxaΒ[xS.o o|GYX c4:I3cз&YG??CZmq SqyfUhK2ڢ+6NK(\D$vfЧj6>h/nWU^ 8ׅց-\f}<6߇#J\5hTU@k܏!aL2$̥Ԩqt E]>  -K*wnw:ձ9,Xd檉zGJ<4\ |-Wj9Agp64ALb7LVeRl ]N̒I!IEr60 A - S|mIei -D.xp -_\V/uU==[!2w0s;w!TAŊuYdlj呕 ESOv6w47PPy/ϝw0ۖ񵀕vpW|iW/J'bf0c֤qI -AӼ%SۇS߬*uhG}i j cH5+ziYZ=ET%.hA=eEVq k_lT =noė68Y 8,IC^7%5 -R)R^dCA@BibIę/ybH6ECCp A>OL\^&Z>0,tN:R90#o󓔡)gWkjȧUrf& ˛4fؒ*)xqrЬiie}qEyJtLO@j-G&T_:zꈨqG/K%Wb^Qȣ&,-j Fyu o~3&AJL #pE'\7weg^hM \u[Ѹ8ic5E\K` W +TFoЅtU[NHmC!}\:[O6d "05 [9k I'M.,39MuYO (2>yO -u!B_߬JX"4cm&&!Ƚ6'gxӫR'YyoÞ7F#zFbb*nИk6?:,ia\ yx%Oga?vB W1U=,op5ޟ?,mL'8t EK׃NowJ,Bs}qvEZb -F"u KDL.:Zx\Z|mޠ;NCّ="3T'hqL{GݞK}owm_|rA=J5@Oi!%RA\hf!s?~!ġvEg6|m'@6]u~56jţ4>bjE#&JQYk/f,&>ZFQ!]16+𼟇x;]@&I<'$Z_V(Wk@$J}/VT |O2\Z5X@g˳d}p _ڠC0,[2VVbFJVhql,bөT죹^ljMݻKM|lKV,фDac|OWў>rŞ:#"${sؓ@+]0S#p>w>wWRd xs_8[UhNwJUyC1Y˧7L9{揣Ưg8+O徒aq `}3+}~WV E9x(uP{ӷT~qAmېKbOHb1% 7ĞZVwR TG: 9xu)2~FTL%4e wmZaȍm乴0VifcfXc[hwk&$8&xVւO]niofPHHQ~bo6Gt:!KG<{h-BO>ٱݭ)}up8?vj`eT'Z h񨖐㙇jN QrTO\-XvYL8%8.NQW0'}bA0ayBs[1Ofz,8%!u]nAA]N$Ⱥ,(u!/%7Ş䷻姖-}TEq}oZw7bG2>IIL %STb+w?iι*#*WY-9K(m›J ߝ3 U?aT=YD3ٜi=U}/*ֶu?q9/#ZUϐg\ BqgN $'EP(q5GV|=L˾( hc-R& YGXj5FG{/8 -XFIvK)7mD8sM3?9D<(R3t^b.w8&HL{v+[hT.߂* LjƾWiU4,1H:tN U7Ua犿{h7ƗxUue=QqZT"$ -}ۋxaa1-lPJ%gfFnGSSf)G:mqG I٬eݻ=-HC8=C`X#_$1*MbvcoZQ szR'3p.+*R;oV+rÄMeqG(ޥG $I0@ze{hIӳTT:p׸Tg`V)f>,x=k%5~r,%I-OIE׏MR<4}cZJoL" Yk_ 櫮}Fܐf#}}Na޲9-|hrxpREtX9ܔPٯנ4(u>yQA(jK'1Fgb P(! A=.m-j|j,LDas~_vvg9w z ^.lz -3-v|h87fcFf|~d+Fz NAnmsnOH (0!1#JPPfFp5N_TthdL!'cS_eڶ 0tB:+gEa962̥3#ur{+MU\K?D4(b@\sWW& jC|'f8rA#puۦzSO B?NQ Ǜ{hckcL!}ԇ #wNŏpq5@P74y ipկқA]Yiwͯ -2BfMٺp83ַhiЄYhbM, l6Gd+]'\, _rkF4H@JLʔ2V 5wyW59$9/-{L AixŠ'Qsߖ: -uWN}Q!pou6|<7+^XVSy0}Ækk eOxF+\2boq8F,}P:va6{IZJ?ѬՑ3/XdQMR-פ/X=R>S:ԏ>)ujy9wF ~vx~IfxpyjW -Xİm;zj}eƃ/ m׉) y+yQL{X -N&b*€Ejmß f VbO43"It7A~#F:F4[Y0)@)J*byhF`̴wDV qMeJ%EηWVՆ.naMfۿ[Xv Lǹe_`1ϳ/_iu"6VϠ̵X=Q(CL@PZFˇP _K;ھ|Zdȸ&o 0Hp+50 Yvtw:^U0am?M`ēWN>bᘞf5UMxO+\Kc Ҙy'H\Q AĞ8(ʀH:u+7NP'|AkʉF#P\ELy -xv<{i#pE!FGי֏;$7 $/ńL*qxs\>?,)>7_0`%>Sjp;P44W,Ա -uBCn! (e2Ϛe\.9u}zJ"1 "R" M&8W -3𤗣v~aMo\52ǓJ!OCܝkgH[(!,,1cbcZV%k>/]Ҭ-WZ\ GnjE@dk%sݬ+%y"}%FmPX^>*y9@[=O7XL>^y|Bj4t91R쭒&rv\""U -#OO -m`F6a︀%NhR c.2LFVbI VƻJL' PCb<66x5sL`V3&-OhU.al%ͺ;2Pӕ K,]#k/]dA:%^5eѬ|iO;wq4.@R)1~sf+Z9qQ2 CR]ҞPeɚ>ik)mY7T"Z -O)7(]<[+HUDxO샽jE/f_4/sN-s9%p+q -67z<ܽOZ?~p>e_ -]1]uQ~Bj Nm rOj|tJL1cZeXZ\BEYnX*UdjE5RĠAϻt^%asQh#?MB  tW)pB8V4F4|C.C!Yg|l(y6ڮf:!ƕĝ|TbzIm62#-_ojBdA"7֓]c8*D9dZyBL ^[vr$dw ,m7nR_>{Ow09vL UaҾvޫK8!N]ܸ5EeGӦ1~AOHak[ -KQhaoZ+q`rJlmwfxdz삣F6hJ8"K٘D&ɤ;}Pm90ګ\&@2ԕ&j?{{+nv{{ʾ@he-lCJʎWt+q+Ct|Ga+zOuLcO̫02LJrq%3'WtO&0A(<0gDs0]:y ?>pLZKϯ.BP ]; vSi)ߝAD#|gE+ҁ8NN(1Ѷ7B!D-~贲ǽWc_=(xSyyサSW%jJ&*8ID&U|4fFChTl5(I3*&qoVA`zWgRD`م_α~s9*$bwSqfG-%9 r%Iif?[K.ZSJ;f\;zhј̅)%RxԘ 7Py)Y%4e&x촥rWK[@5r'a8nJ0 &'aNpL72mM! AΠ>uVbKp -&"=(z^bebGi;*IWP%ZW+-A X(}y,ÿkTHDNw~ǑP=5مQ^kh DCCTQL3*J("uq8'\*9z^: į$LӐ׾"V'5'qJRko 0Ԫ3ǂ -t4H~h0{'cG8,L¨҈@nbǞ -0~WNqTbu5xḫ")ԋtnn_& "ӓeV^a : UC&RB1Mm%86lRrUbUW|jfH(rٮtOc2HȶMḢ@Otܷ~8 !˳J'u}ѨVW1ʜ FA   HPU4l[5d=gB/-w& ;J=؇x |#W8ϫN(iZḏ|/Xt'u"#: n) "(H&n,ڜ^< )ױCyu:=a.=!JҊv:ڽu#rXCGVb a/ 5cr8nn81t'\=*JE|hҌTIѰ *2JD4u[yQm88Hמ7Oc8:0-J-<8z?ݞ0WU%aXw=?jJ0fgq0\;y񸎆ҝ_TbvT݇Ұ EP?s¦g!Lc !0qSA=te y3C%2^SQF_yTiWg%}pw !H`:*t')`"sk+In<@07̦J9KU* KηRNCkOo^&HA<]3xJ?C_eJGg6?+qC^/*yXig.Fnh鄷|x\7qB 9а2"+>2h7MæNP KcLj;=8IushRLh@O7ӵ;~ޥkƻ񻽯]w._͏MJƺ-m}vA^~N۞-7qzC㝶_oj}SEW뵆/>?qF,rJ|/ڙ~fi<$?H u6J˦ȘJJ RF< a4|#($Rx_4RdgtMs.O? Z~NS?JEC}jZ3Ҷ;ce_.a->}sdm3Wܘax];nڴgef2jL8bH dOnn$IdG@@rTTpA ȩKST:jT) #(t ;A;2 2~y߽'Ϸ蹲ݱHaSy]wd˿Py+bcKo"Rx|0v r 5~S^.+RmRֿ|r{k|Փ۷n<'RAm6Vl#^wT'swmMNV(6'w?%%mP}{V12H9@1 -A+>3f f', ?&)*Ә: 4pQ ‹fR~-/ZΓH0xj٠t$sar=])ذASP3+ۇFGO'zdL.tgqer]]6*$LiӺھt[ݝOf%jOO p)dBpXx/ OEl|>2lxܻ~+eT.2Wo5x}[x~%5m3회 7B7cRƋ+C71*dKR#.@~0L1ˢ6c@1"1 -)v -0L,Ja%p> 3f@uwL]:+l.Œ>W}MHx@#K+LcK0bm N K~)dJ%&o>-5}7 -m՞a B)v8qo%\b+hgeeme58ώ -87u8uC+"Uׂ7lSc"9MuӢOBL[kKXkW2q&҅c!L#h=^@28Ǯ/!J"ٮ1ޣ(*5jTaZH,lrIy1^j3;6XZ$s:KxxCCFK &3?-oƀW:|D!Tr5>3fto v[Jˠ ͼXX6kՙS9(JM 95̍WYe = n;*MCMn -dA{}c(6t;ؑ12L"`ړ7\_mm*#65(1ERH8Վ [P*`QKC%ķa1AT\&087: w+$IJaQ5nlwݬ6 !l[##H4LҊf qy̖BRLP0CDym}ۋ\)Ȥ0F* /]$)4dvg30{"K 0O?ᆟ31 -R<̘y?w`?jj=Q :FlEF1uBu A5eC$-MKC蓵e90rςQ 593Y"ybo q}1v$sCAG,Y8YJtKgWyWtES }as)VBhī0HLؘyF2뺶Hܒυa6$:vdwEyD'%cMGl/*?u!ݒl=S=S- .IB&P,QR -N3b|*sG, -iLN#\^0x͊ѹiذ9{"p(?6/Z -HR ;ЩYJp4cfhquF~ቤ0A%[յ9d(7~lA.+5n:@lY~E  WW I@@|nv!2;LA9oHCV5{]+Ǽ""A V5N\~ASZ*aq;Y& V.2HO44_3f@Z'{|}t -!sֆ"mTeXrSуktÀ҂"OV8Ja@sR7U~//&4yzBrM2IL tţT۪]ʊ׊GSo<^(VV b`áxCHghv";|Λw/9#PB+l.߂IAה^jKTP볿RzҀ'_>2nhj-NbTn-p`(1 -ˮ5bwEZ]z'yNvF"dIOL+ۑQpZU"![ḐayrFT=9%O̤?3S:5Ӕ.2}%G`Ѡ'勺$%S.o*JL:TKZm&T*/#

.", len(lines), 1)) - return issues - - -def validate_chunk_frontmatter( - markdown: str, - *, - required_fields: Iterable[str] = ("title", "document_slug", "chunk_index", "page_range"), -) -> list[QualityIssue]: - frontmatter, first_body_line = _parse_frontmatter(markdown) - if frontmatter is None: - return [ - QualityIssue( - "frontmatter", - "Chunk frontmatter must start with an opening '---' line and a closing '---' line.", - 1, - 1, - ) - ] - - issues: list[QualityIssue] = [] - for field_name in required_fields: - if field_name not in frontmatter or frontmatter[field_name] == "": - issues.append( - QualityIssue( - "frontmatter", - f"Chunk frontmatter is missing required field: {field_name}", - first_body_line, - 1, - ) - ) - return issues - - -def validate_caption_reference_anchors(markdown: str) -> list[QualityIssue]: - issues: list[QualityIssue] = [] - anchor_ids: set[str] = set() - - for match in _ANCHOR_RE.finditer(markdown): - anchor_id = match.group("id") - line, column = _line_column(markdown, match.start("id")) - if not _ANCHOR_ID_RE.match(anchor_id): - issues.append(QualityIssue("anchors", f"Anchor id has an unsupported shape: {anchor_id}", line, column)) - anchor_ids.add(anchor_id) - - for match in _MARKDOWN_LINK_RE.finditer(markdown): - label = match.group("label").strip() - if not _REFERENCE_LABEL_RE.match(label): - continue - target = match.group("target")[1:] - line, column = _line_column(markdown, match.start("target")) - if not _ANCHOR_ID_RE.match(target): - issues.append(QualityIssue("anchors", f"Reference link anchor has an unsupported shape: #{target}", line, column)) - elif target not in anchor_ids: - issues.append(QualityIssue("anchors", f"Reference link points to a missing anchor: #{target}", line, column)) - return issues - - -def _mask_code(markdown: str) -> str: - def replace_with_spaces(match: re.Match[str]) -> str: - return "".join("\n" if char == "\n" else " " for char in match.group(0)) - - masked = _FENCE_RE.sub(replace_with_spaces, markdown) - return _INLINE_CODE_RE.sub(replace_with_spaces, masked) - - -def _is_escaped(line: str, index: int) -> bool: - slash_count = 0 - cursor = index - 1 - while cursor >= 0 and line[cursor] == "\\": - slash_count += 1 - cursor -= 1 - return slash_count % 2 == 1 - - -def _line_column(text: str, offset: int) -> tuple[int, int]: - line = text.count("\n", 0, offset) + 1 - previous_newline = text.rfind("\n", 0, offset) - column = offset + 1 if previous_newline == -1 else offset - previous_newline - return line, column - - -def _asset_paths(assets: Sequence[Asset] | Mapping[str, Asset] | None) -> set[str]: - if assets is None: - return set() - values = assets.values() if isinstance(assets, Mapping) else assets - return {asset.relative_path.replace("\\", "/") for asset in values} - - -def _normalize_link_target(target: str) -> str: - target = target.strip() - if target.startswith("<") and ">" in target: - target = target[1 : target.index(">")] - elif " " in target: - target = target.split(" ", 1)[0] - return target.replace("\\", "/") - - -def _is_external_or_anchor(target: str) -> bool: - lowered = target.lower() - return target.startswith("#") or lowered.startswith(("http://", "https://", "mailto:", "data:")) - - -def _looks_like_table_row(line: str) -> bool: - return "|" in line and bool(line.strip().strip("|").strip()) - - -def _table_cells(line: str) -> list[str]: - return [cell.strip() for cell in line.strip().strip("|").split("|")] - - -def _is_separator_row(line: str) -> bool: - cells = _table_cells(line) - return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell.strip()) for cell in cells) - - -def _parse_frontmatter(markdown: str) -> tuple[dict[str, str] | None, int]: - lines = markdown.splitlines() - if not lines or lines[0].strip() != "---": - return None, 1 - for index, line in enumerate(lines[1:], start=1): - if line.strip() == "---": - fields: dict[str, str] = {} - for raw_field in lines[1:index]: - if not raw_field.strip() or raw_field.lstrip().startswith("#"): - continue - if ":" not in raw_field: - continue - key, value = raw_field.split(":", 1) - fields[key.strip()] = value.strip().strip("'\"") - return fields, index + 2 - return None, 1 diff --git a/src/pdftomd/runtime_contracts.py b/src/pdftomd/runtime_contracts.py deleted file mode 100644 index 15fd4cf..0000000 --- a/src/pdftomd/runtime_contracts.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import os -from dataclasses import dataclass -from pathlib import Path -from typing import Mapping - -from pdftomd.models import DocumentIdentity - - -MODEL_CACHE_ENV = "PDFTOMD_MODEL_CACHE" - - -@dataclass(frozen=True) -class ModelCachePolicy: - root: Path - - @classmethod - def from_environment( - cls, - *, - project_root: str | Path, - explicit_model_cache: str | Path | None = None, - env: Mapping[str, str] | None = None, - ) -> "ModelCachePolicy": - source = dict(os.environ if env is None else env) - if explicit_model_cache is not None: - root = Path(explicit_model_cache) - elif source.get(MODEL_CACHE_ENV): - root = Path(source[MODEL_CACHE_ENV]) - else: - root = Path(project_root) / ".models" - return cls(root=root.expanduser()) - - @property - def marker_dir(self) -> Path: - return self.root / "marker" - - @property - def nougat_dir(self) -> Path: - return self.root / "nougat" - - @property - def huggingface_home(self) -> Path: - return self.root / "huggingface" - - @property - def huggingface_hub_cache(self) -> Path: - return self.huggingface_home / "hub" - - def to_environment(self, *, offline: bool = False) -> dict[str, str]: - values = { - "HF_HOME": str(self.huggingface_home), - "HUGGINGFACE_HUB_CACHE": str(self.huggingface_hub_cache), - "TRANSFORMERS_CACHE": str(self.huggingface_home), - } - if offline: - values["HF_HUB_OFFLINE"] = "1" - values["TRANSFORMERS_OFFLINE"] = "1" - return values - - -@dataclass(frozen=True) -class RuntimeArtifactPaths: - root: Path - log_file: Path - resume_state_file: Path - - @classmethod - def from_output_root( - cls, - output_root: str | Path, - document: DocumentIdentity, - ) -> "RuntimeArtifactPaths": - root = Path(output_root) / ".pdftomd-runtime" / document.slug - return cls( - root=root, - log_file=root / "logs" / "conversion.log", - resume_state_file=root / "state" / "resume-state.json", - ) diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index df150dc..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[1] -SRC = ROOT / "src" - -if str(SRC) not in sys.path: - sys.path.insert(0, str(SRC)) diff --git a/tests/test_models.py b/tests/test_models.py deleted file mode 100644 index c47583d..0000000 --- a/tests/test_models.py +++ /dev/null @@ -1,140 +0,0 @@ -from __future__ import annotations - -import pytest - -from pdftomd import ( - Asset, - AssetKind, - BlockRole, - BoundingBox, - ChunkMetadata, - DocumentBlock, - DocumentIdentity, - Figure, - Formula, - PageAnalysis, - PageRange, - Table, -) - - -def test_document_identity_slug_and_output_fields_are_deterministic() -> None: - document = DocumentIdentity.from_path(r"C:\papers\MITC Korean Report 2026.pdf") - - assert document.source_path == r"C:\papers\MITC Korean Report 2026.pdf" - assert document.stem == "MITC Korean Report 2026" - assert document.slug == "mitc-korean-report-2026" - assert document.chunk_filename(1) == "mitc-korean-report-2026_001.md" - assert document.asset_dir == "images" - - -def test_non_ascii_document_identity_uses_stable_fallback_slug() -> None: - first = DocumentIdentity.from_path("한글 보고서.pdf") - second = DocumentIdentity.from_path("다른 보고서.pdf") - - assert first.filename == "한글 보고서.pdf" - assert first.slug.startswith("document-") - assert first.slug == DocumentIdentity.from_path("한글 보고서.pdf").slug - assert first.slug != second.slug - - -def test_page_range_invariants_and_helpers() -> None: - page_range = PageRange(start=3, end=7) - - assert page_range.count == 5 - assert page_range.label == "3-7" - assert page_range.contains(3) - assert page_range.contains(7) - assert not page_range.contains(8) - - -@pytest.mark.parametrize( - ("start", "end"), - [(0, 1), (4, 3)], -) -def test_page_range_rejects_invalid_bounds(start: int, end: int) -> None: - with pytest.raises(ValueError): - PageRange(start=start, end=end) - - -def test_block_formula_table_figure_and_asset_construction() -> None: - bbox = BoundingBox(page=2, x0=10.0, y0=20.0, x1=110.0, y1=80.0) - page_range = PageRange(start=2, end=2) - identity = DocumentIdentity.from_path("Example Paper.pdf") - - formula = Formula( - id="eq-001", - latex=r"E = mc^2", - display=True, - source_text="E = mc2", - number="1", - ) - table = Table( - id="tbl-001", - rows=(("A", "B"), ("1", "2")), - caption="Table 1. Values", - number="1", - ) - figure = Figure( - id="fig-001", - caption="Figure 1. Diagram", - number="1", - asset_id="asset-001", - ) - asset = Asset( - id="asset-001", - kind=AssetKind.FIGURE, - relative_path=f"{identity.asset_dir}/{identity.figure_asset_filename('1')}", - page=2, - bbox=bbox, - content_hash="abc123", - ) - block = DocumentBlock( - id="block-001", - role=BlockRole.FORMULA, - page_range=page_range, - bbox=bbox, - text="E = mc^2", - formula=formula, - ) - chunk = ChunkMetadata( - index=1, - document=identity, - page_range=page_range, - block_ids=("block-001",), - asset_ids=("asset-001",), - ) - analysis = PageAnalysis( - page=2, - text_length=200, - image_count=1, - has_text_layer=True, - needs_ocr=False, - ) - - assert bbox.width == 100.0 - assert bbox.height == 60.0 - assert block.role is BlockRole.FORMULA - assert block.formula == formula - assert table.rows[0] == ("A", "B") - assert figure.asset_id == asset.id - assert asset.relative_path == "images/example-paper_fig-1.png" - assert chunk.filename == "example-paper_001.md" - assert chunk.slug == "example-paper" - assert analysis.text_layer_quality == "text" - - -def test_bounding_box_rejects_invalid_coordinates() -> None: - with pytest.raises(ValueError): - BoundingBox(page=1, x0=10.0, y0=0.0, x1=10.0, y1=20.0) - - -def test_page_analysis_rejects_negative_counts() -> None: - with pytest.raises(ValueError): - PageAnalysis( - page=1, - text_length=-1, - image_count=0, - has_text_layer=False, - needs_ocr=True, - ) diff --git a/tests/test_options.py b/tests/test_options.py deleted file mode 100644 index 7abdaf7..0000000 --- a/tests/test_options.py +++ /dev/null @@ -1,49 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pytest - -from pdftomd.options import ConversionOptions, FormulaParser, RuntimeMode - - -def test_conversion_options_defaults_match_project_policy() -> None: - options = ConversionOptions() - - assert options.runtime is RuntimeMode.CUDA - assert options.formula_parser is FormulaParser.NOUGAT - assert options.chunk_target_pages == 20 - assert options.output_dir == Path("output") - assert options.resume is False - assert options.write_logs is True - - -def test_runtime_modes_express_cuda_fail_fast_and_auto_fallback() -> None: - assert RuntimeMode.CUDA.requires_cuda - assert not RuntimeMode.CUDA.allows_cpu_fallback - assert RuntimeMode.AUTO.allows_cpu_fallback - assert not RuntimeMode.CPU.requires_cuda - - -def test_conversion_options_validate_chunk_size_and_formula_boundary() -> None: - with pytest.raises(ValueError, match="chunk_target_pages"): - ConversionOptions(chunk_target_pages=0) - - options = ConversionOptions(formula_parser=FormulaParser.MARKER) - assert options.formula_parser is FormulaParser.MARKER - assert not hasattr(options, "pyqt") - assert not hasattr(options, "api_url") - - -def test_conversion_options_normalize_optional_paths(tmp_path: Path) -> None: - options = ConversionOptions( - output_dir=tmp_path / "out", - nougat_command=tmp_path / "venv" / "Scripts" / "nougat.exe", - model_cache_dir=tmp_path / ".models", - log_dir=tmp_path / "logs", - ) - - assert options.output_dir == tmp_path / "out" - assert options.nougat_command is not None - assert options.model_cache_dir == tmp_path / ".models" - assert options.log_dir == tmp_path / "logs" diff --git a/tests/test_paths.py b/tests/test_paths.py deleted file mode 100644 index 8fed992..0000000 --- a/tests/test_paths.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pytest - -from pdftomd.models import DocumentIdentity -from pdftomd.paths import ( - OutputBundlePaths, - document_identity_from_pdf, - make_anchor, - normalize_pdf_path, -) - - -def test_normalize_pdf_path_accepts_korean_and_spaced_paths(tmp_path: Path) -> None: - pdf = tmp_path / "한글 경로" / "My Report 2026.pdf" - pdf.parent.mkdir() - pdf.write_bytes(b"%PDF-1.7\n") - - normalized = normalize_pdf_path(pdf) - - assert normalized.is_absolute() - assert normalized.name == "My Report 2026.pdf" - - -def test_normalize_pdf_path_rejects_non_pdf_files(tmp_path: Path) -> None: - text_file = tmp_path / "document.txt" - text_file.write_text("not a pdf", encoding="utf-8") - - with pytest.raises(ValueError, match="PDF"): - normalize_pdf_path(text_file) - - -def test_document_identity_from_pdf_uses_stable_slug(tmp_path: Path) -> None: - pdf = tmp_path / "한글 보고서.pdf" - pdf.write_bytes(b"%PDF-1.7\n") - - first = document_identity_from_pdf(pdf) - second = document_identity_from_pdf(pdf) - - assert first.filename == "한글 보고서.pdf" - assert first.slug == second.slug - assert first.slug.startswith("document-") - assert first.source_path == str(normalize_pdf_path(pdf)) - - -def test_output_bundle_paths_keep_document_and_runtime_artifacts_separate(tmp_path: Path) -> None: - document = DocumentIdentity.from_path("Example Paper.pdf") - bundle = OutputBundlePaths.from_document(tmp_path, document) - - assert bundle.document_dir == tmp_path / "example-paper" - assert bundle.images_dir == tmp_path / "example-paper" / "images" - assert bundle.chunk_path(1) == tmp_path / "example-paper" / "example-paper_001.md" - assert bundle.figure_asset_path("1") == tmp_path / "example-paper" / "images" / "example-paper_fig-1.png" - assert bundle.runtime_dir == tmp_path / ".pdftomd-runtime" / "example-paper" - assert bundle.log_path.name == "conversion.log" - assert bundle.resume_state_path.name == "resume-state.json" - assert bundle.runtime_dir not in bundle.document_dir.parents - - -def test_make_anchor_is_deterministic_and_validates_kind() -> None: - assert make_anchor("Figure", "2 A") == "figure-2-a" - assert make_anchor("Equation", "식 3") == "equation-3" - - with pytest.raises(ValueError, match="kind"): - make_anchor("", "1") diff --git a/tests/test_preanalysis.py b/tests/test_preanalysis.py deleted file mode 100644 index 3d6768c..0000000 --- a/tests/test_preanalysis.py +++ /dev/null @@ -1,93 +0,0 @@ -from __future__ import annotations - -import json -import shutil -from pathlib import Path - -from pdftomd.models import PageAnalysis, PageRange -from pdftomd.preanalysis import analyze_pdf, is_ocr_candidate, plan_page_chunks - - -ROOT = Path(__file__).resolve().parents[1] -METADATA_PATH = ROOT / "samples" / "metadata.json" - - -def _metadata_samples() -> list[dict]: - return json.loads(METADATA_PATH.read_text(encoding="utf-8"))["samples"] - - -def _sample_with(**traits: object) -> dict: - for sample in _metadata_samples(): - sample_traits = sample["traits"] - if all(sample_traits.get(key) == value for key, value in traits.items()): - return sample - raise AssertionError(f"no sample matched traits: {traits}") - - -def test_analyze_text_heavy_sample_returns_page_facts_from_metadata() -> None: - sample = _sample_with(text_layer_quality="good", mixed_scanned_text_pages=False) - - result = analyze_pdf(ROOT / sample["path"]) - - assert result.page_count == sample["page_count"] - assert len(result.pages) == sample["page_count"] - assert all(isinstance(page, PageAnalysis) for page in result.pages) - assert result.pages[0].page == 1 - assert result.pages[0].text_length > 1000 - assert result.pages[0].has_text_layer - assert not result.pages[0].needs_ocr - - -def test_analyze_mixed_scanned_risk_sample_marks_metadata_scanned_pages() -> None: - sample = _sample_with(mixed_scanned_text_pages=True) - - result = analyze_pdf(ROOT / sample["path"]) - ocr_pages = {page.page for page in result.pages if page.needs_ocr} - - assert result.page_count == sample["page_count"] - assert set(sample["traits"]["scanned_pages"]) <= ocr_pages - assert any(page.image_count > 0 for page in result.pages) - - -def test_analyze_pdf_accepts_korean_pathlib_path(tmp_path: Path) -> None: - sample = _sample_with(has_korean_path=True, text_layer_quality="good") - source = ROOT / sample["path"] - target_dir = tmp_path / "한글 경로" - target_dir.mkdir() - target = target_dir / source.name - shutil.copyfile(source, target) - - result = analyze_pdf(target) - - assert result.page_count == sample["page_count"] - assert result.pages[0].has_text_layer - - -def test_ocr_candidate_logic_is_deterministic() -> None: - cases = [ - (0, 0, True), - (0, 2, True), - (40, 0, False), - (199, 1, True), - (200, 1, False), - (1000, 8, False), - ] - - first = [is_ocr_candidate(text_length, image_count) for text_length, image_count, _ in cases] - second = [is_ocr_candidate(text_length, image_count) for text_length, image_count, _ in cases] - - assert first == [expected for _, _, expected in cases] - assert first == second - - -def test_chunk_candidates_are_twenty_page_ranges_within_bounds() -> None: - chunks = plan_page_chunks(76) - - assert chunks == ( - PageRange(1, 20), - PageRange(21, 40), - PageRange(41, 60), - PageRange(61, 76), - ) - assert all(chunk.end <= 76 for chunk in chunks) - assert plan_page_chunks(0) == () diff --git a/tests/test_quality.py b/tests/test_quality.py deleted file mode 100644 index bf8bcf6..0000000 --- a/tests/test_quality.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from pdftomd.models import Asset, AssetKind -from pdftomd.quality import ( - validate_caption_reference_anchors, - validate_chunk_frontmatter, - validate_image_links, - validate_latex_environments, - validate_markdown_quality, - validate_math_delimiters, - validate_tables, -) - - -def messages(issues: list[object]) -> list[str]: - return [getattr(issue, "message") for issue in issues] - - -def test_math_delimiters_accept_inline_and_block_math() -> None: - markdown = "\n".join( - [ - "Inline energy $E = mc^2$ is preserved.", - "", - "$$", - r"\int_0^1 x^2 dx", - "$$", - ] - ) - - assert validate_math_delimiters(markdown) == [] - - -def test_math_delimiters_report_actionable_unclosed_inline_math() -> None: - issues = validate_math_delimiters("The expression $E = mc^2 is missing a close.") - - assert len(issues) == 1 - assert "Unclosed inline math delimiter" in issues[0].message - assert issues[0].line == 1 - assert "$" in issues[0].message - - -def test_math_delimiters_report_actionable_unclosed_block_math() -> None: - issues = validate_math_delimiters("Before\n$$\na^2 + b^2 = c^2\nAfter") - - assert len(issues) == 1 - assert "Unclosed block math delimiter" in issues[0].message - assert issues[0].line == 2 - - -def test_latex_environment_pairs_accept_nested_matching_pairs() -> None: - markdown = r""" -$$ -\begin{aligned} -a &= b \\ -\begin{matrix}1 & 2\end{matrix} -\end{aligned} -$$ -""" - - assert validate_latex_environments(markdown) == [] - - -def test_latex_environment_pairs_report_mismatch() -> None: - issues = validate_latex_environments(r"\begin{aligned} x \end{matrix}") - - assert len(issues) == 1 - assert "LaTeX environment mismatch" in issues[0].message - assert "aligned" in issues[0].message - assert "matrix" in issues[0].message - - -def test_image_links_validate_filesystem_and_modeled_assets(tmp_path: Path) -> None: - image_dir = tmp_path / "images" - image_dir.mkdir() - (image_dir / "paper_fig-1.png").write_bytes(b"png") - asset = Asset( - id="asset-001", - kind=AssetKind.FIGURE, - relative_path="images/paper_fig-1.png", - page=1, - ) - markdown = "![Figure 1](images/paper_fig-1.png)\n![Figure 2](images/missing.png)" - - issues = validate_image_links(markdown, base_dir=tmp_path, assets=[asset]) - - assert messages(issues) == [ - "Image link target does not exist on disk and is not present in modeled assets: images/missing.png" - ] - - -def test_simple_markdown_table_parseability() -> None: - markdown = "\n".join( - [ - "| A | B |", - "| --- | --- |", - "| 1 | 2 |", - "| 3 | 4 |", - ] - ) - - assert validate_tables(markdown) == [] - - -def test_markdown_table_reports_row_width_mismatch() -> None: - issues = validate_tables("| A | B |\n| --- | --- |\n| 1 | 2 | 3 |") - - assert len(issues) == 1 - assert "Markdown table row has 3 cells; expected 2" in issues[0].message - - -def test_complex_table_can_be_represented_as_allowed_html_with_fallback() -> None: - markdown = "\n".join( - [ - '', - "", - "", - "
LoadValue
42
", - "![Table 1 fallback](images/table-1.png)", - ] - ) - - assert validate_tables(markdown, allow_html_table_fallback=True) == [] - - -def test_frontmatter_requires_chunk_context_fields() -> None: - markdown = "---\ndocument_slug: paper\nchunk_index: 1\n---\n# Paper" - - issues = validate_chunk_frontmatter(markdown) - - assert messages(issues) == [ - "Chunk frontmatter is missing required field: title", - "Chunk frontmatter is missing required field: page_range", - ] - - -def test_frontmatter_accepts_required_chunk_context_fields() -> None: - markdown = "---\ntitle: Paper\ndocument_slug: paper\nchunk_index: 1\npage_range: 1-3\n---\n# Paper" - - assert validate_chunk_frontmatter(markdown) == [] - - -def test_caption_reference_anchor_shape_checks_known_reference_targets() -> None: - markdown = "\n".join( - [ - '
', - "![Figure 1](images/fig-1.png)", - "Figure 1. Diagram.", - "As shown in [Fig. 1](#fig-1) and [Table 2](#table-2).", - ] - ) - - issues = validate_caption_reference_anchors(markdown) - - assert messages(issues) == ["Reference link points to a missing anchor: #table-2"] - - -def test_combined_quality_gate_does_not_mutate_markdown() -> None: - markdown = "---\ntitle: Paper\ndocument_slug: paper\nchunk_index: 1\npage_range: 1\n---\n$E=mc^2$" - - result = validate_markdown_quality(markdown) - - assert result.markdown == markdown - assert result.ok - assert result.issues == () diff --git a/tests/test_runtime_contracts.py b/tests/test_runtime_contracts.py deleted file mode 100644 index d391f90..0000000 --- a/tests/test_runtime_contracts.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from pdftomd.models import DocumentIdentity -from pdftomd.runtime_contracts import ModelCachePolicy, RuntimeArtifactPaths - - -def test_model_cache_policy_prefers_explicit_path(tmp_path: Path) -> None: - policy = ModelCachePolicy.from_environment( - project_root=tmp_path, - explicit_model_cache=tmp_path / "explicit-models", - env={"PDFTOMD_MODEL_CACHE": str(tmp_path / "ignored")}, - ) - - assert policy.root == tmp_path / "explicit-models" - assert policy.marker_dir == tmp_path / "explicit-models" / "marker" - assert policy.nougat_dir == tmp_path / "explicit-models" / "nougat" - assert policy.huggingface_home == tmp_path / "explicit-models" / "huggingface" - - -def test_model_cache_policy_uses_env_then_project_default(tmp_path: Path) -> None: - env_policy = ModelCachePolicy.from_environment( - project_root=tmp_path, - env={"PDFTOMD_MODEL_CACHE": str(tmp_path / "env-models")}, - ) - default_policy = ModelCachePolicy.from_environment(project_root=tmp_path, env={}) - - assert env_policy.root == tmp_path / "env-models" - assert default_policy.root == tmp_path / ".models" - - -def test_model_cache_policy_exports_offline_environment(tmp_path: Path) -> None: - policy = ModelCachePolicy.from_environment(project_root=tmp_path, env={}) - - environment = policy.to_environment(offline=True) - - assert environment["HF_HOME"] == str(tmp_path / ".models" / "huggingface") - assert environment["HUGGINGFACE_HUB_CACHE"] == str(tmp_path / ".models" / "huggingface" / "hub") - assert environment["HF_HUB_OFFLINE"] == "1" - - -def test_runtime_artifact_paths_are_outside_document_bundle(tmp_path: Path) -> None: - document = DocumentIdentity.from_path("Example Paper.pdf") - artifacts = RuntimeArtifactPaths.from_output_root(tmp_path, document) - - assert artifacts.root == tmp_path / ".pdftomd-runtime" / "example-paper" - assert artifacts.log_file == artifacts.root / "logs" / "conversion.log" - assert artifacts.resume_state_file == artifacts.root / "state" / "resume-state.json" - assert "example-paper" in str(artifacts.root) diff --git a/tests/test_sample_metadata.py b/tests/test_sample_metadata.py deleted file mode 100644 index 08b76cc..0000000 --- a/tests/test_sample_metadata.py +++ /dev/null @@ -1,87 +0,0 @@ -import json -from pathlib import Path - -import fitz - - -ROOT = Path(__file__).resolve().parents[1] -METADATA_PATH = ROOT / "samples" / "metadata.json" - -REQUIRED_TRAITS = { - "figure_density", - "formula_density", - "has_korean_path", - "layout_risk", - "mixed_scanned_text_pages", - "scanned_pages", - "table_density", - "target_regression_focus", - "text_layer_quality", -} - - -def _load_metadata(): - with METADATA_PATH.open("r", encoding="utf-8") as handle: - return json.load(handle) - - -def _metadata_samples(): - metadata = _load_metadata() - assert isinstance(metadata, dict) - samples = metadata.get("samples") - assert isinstance(samples, list) - return samples - - -def test_metadata_paths_match_current_sample_pdfs_exactly(): - expected_paths = sorted( - path.as_posix() - for path in Path("samples").glob("*.pdf") - ) - samples = _metadata_samples() - metadata_paths = [sample.get("path") for sample in samples] - - assert sorted(metadata_paths) == expected_paths - - -def test_metadata_paths_are_unique(): - metadata_paths = [sample.get("path") for sample in _metadata_samples()] - - assert len(metadata_paths) == len(set(metadata_paths)) - - -def test_metadata_paths_are_exact_relative_samples_pdf_paths(): - for sample in _metadata_samples(): - path = sample.get("path") - - assert isinstance(path, str) - assert path.startswith("samples/") - assert Path(path).suffix == ".pdf" - assert not Path(path).is_absolute() - assert (ROOT / path).is_file() - - -def test_required_trait_fields_are_present(): - for sample in _metadata_samples(): - traits = sample.get("traits") - - assert isinstance(traits, dict), sample.get("path") - assert REQUIRED_TRAITS <= traits.keys(), sample.get("path") - - -def test_page_counts_match_current_sample_pdfs(): - for sample in _metadata_samples(): - page_count = sample.get("page_count") - path = ROOT / sample["path"] - - assert isinstance(page_count, int), sample["path"] - assert page_count > 0, sample["path"] - with fitz.open(path) as document: - assert page_count == document.page_count, sample["path"] - - -def test_metadata_json_is_deterministic_utf8(): - raw = METADATA_PATH.read_text(encoding="utf-8") - metadata = json.loads(raw) - - assert raw == json.dumps(metadata, ensure_ascii=False, indent=2, sort_keys=True) + "\n"