From 89ad9953a2740e2f3ca0ee0e5676f5ace4d1a993 Mon Sep 17 00:00:00 2001 From: Sam120204 Date: Thu, 14 May 2026 23:05:43 -0400 Subject: [PATCH 01/12] Add backend architecture, API surface, data models, schemas, document processing, and LLM layer documentation Introduced comprehensive documentation covering the backend architecture, API surface, data models, Pydantic schemas, document processing workflows, and the LLM provider layer. This includes detailed descriptions of application entry points, runtime layers, package responsibilities, and main data flows, enhancing the overall understanding of the backend system. --- docs/backend/01-architecture.md | 255 ++++++++++ docs/backend/02-api-surface.md | 479 ++++++++++++++++++ docs/backend/03-data-models.md | 449 ++++++++++++++++ docs/backend/04-schemas.md | 409 +++++++++++++++ docs/backend/05-document-processing.md | 395 +++++++++++++++ docs/backend/06-llm-layer.md | 349 +++++++++++++ docs/backend/07-extraction-flow.md | 288 +++++++++++ docs/backend/08-evaluation-flow.md | 417 +++++++++++++++ docs/backend/09-session-sharing-groups.md | 413 +++++++++++++++ docs/backend/10-template-system.md | 407 +++++++++++++++ .../backend/11-auth-security-observability.md | 382 ++++++++++++++ docs/backend/README.md | 125 +++++ docs/backend/appendices/api-endpoint-index.md | 152 ++++++ docs/backend/appendices/class-index.md | 172 +++++++ docs/backend/appendices/data-flow-diagrams.md | 343 +++++++++++++ .../appendices/risks-assumptions-testing.md | 228 +++++++++ docs/{ => superpowers}/migration-guide.md | 0 docs/{ => superpowers}/setup-github-auth.md | 0 18 files changed, 5263 insertions(+) create mode 100644 docs/backend/01-architecture.md create mode 100644 docs/backend/02-api-surface.md create mode 100644 docs/backend/03-data-models.md create mode 100644 docs/backend/04-schemas.md create mode 100644 docs/backend/05-document-processing.md create mode 100644 docs/backend/06-llm-layer.md create mode 100644 docs/backend/07-extraction-flow.md create mode 100644 docs/backend/08-evaluation-flow.md create mode 100644 docs/backend/09-session-sharing-groups.md create mode 100644 docs/backend/10-template-system.md create mode 100644 docs/backend/11-auth-security-observability.md create mode 100644 docs/backend/README.md create mode 100644 docs/backend/appendices/api-endpoint-index.md create mode 100644 docs/backend/appendices/class-index.md create mode 100644 docs/backend/appendices/data-flow-diagrams.md create mode 100644 docs/backend/appendices/risks-assumptions-testing.md rename docs/{ => superpowers}/migration-guide.md (100%) rename docs/{ => superpowers}/setup-github-auth.md (100%) diff --git a/docs/backend/01-architecture.md b/docs/backend/01-architecture.md new file mode 100644 index 0000000..27d6e37 --- /dev/null +++ b/docs/backend/01-architecture.md @@ -0,0 +1,255 @@ +# Backend Architecture + +This document describes the high-level backend architecture, module boundaries, runtime setup, and major control/data flows. Lower-level class, endpoint, and schema details are split into the linked module documents. + +## 1. Application entry point + +The backend starts from `backend/main.py`. + +Key functions: + +| Function | Responsibility | +| --- | --- | +| `load_secrets_to_env()` | Loads `backend/core/secrets.toml` or nearby `core/secrets.toml` candidates and writes values into environment variables. Special-cases `Macbook.macbook_llm_base_url` into `MACBOOK_LLM_BASE_URL`. | +| `load_config()` | Loads provider-specific configuration from `backend/core/secrets.toml`, including Azure OpenAI, Azure Document Intelligence, Vertex/Gemini, Anthropic, and Google credentials. | +| `_setup_otel(app)` | Enables OpenTelemetry FastAPI instrumentation if `OTLP_ENDPOINT` is configured. | +| `lifespan(app)` | Sets the default asyncio thread pool to 64 workers so provider calls delegated through `asyncio.to_thread()` do not bottleneck on the default small pool. | +| `create_app()` | Creates the FastAPI app, installs metrics, tracing, CORS, exception handling, observability middleware, and all routers. | + +Router registration happens in `create_app()` and is intentionally ordered. The auth proxy router is mounted first so `/api/auth/*` traffic is forwarded to the Better Auth sidecar before other auth routes are considered. + +## 2. Runtime layers + +```text +Browser / frontend + | + | HTTP / JSON / multipart upload + v +FastAPI app (`backend/main.py`) + | + +-- core middleware/auth/config/logging + | + +-- API routers (`backend/api/*`) + | | + | v + +-- service layer (`backend/services/*`) + | | + | +-- SQLAlchemy DB service + | +-- document processors and blob storage + | +-- LLM provider clients + | +-- evaluation queue/adapters/metrics + | +-- sessions/groups/templates services + | + v +PostgreSQL + Azure Blob Storage + external model/parser providers +``` + +## 3. Package responsibilities + +### `backend/core/` + +Core cross-cutting concerns: + +- `auth.py` validates Better Auth sessions against the DB. +- `config.py` maps `secrets.toml` values into environment variables. +- `dependencies.py` re-exports auth dependencies. +- `middleware.py` configures CORS. +- `logging_config.py` configures structlog JSON logging, file logging, and optional Loki shipping. + +### `backend/api/` + +FastAPI routers. Routers translate HTTP input into Pydantic request models or primitive parameters, call services, and map exceptions to HTTP responses. + +Major router groups: + +- auth proxy and login history; +- file upload/download/listing; +- document processing, content, analysis, figures, tables; +- extraction and paragraph generation; +- evaluation and background evaluation jobs; +- sessions and shared sessions; +- groups and memberships; +- templates and folders; +- server health, config, models, telemetry, and logs; +- chat query endpoint. + +See [02-api-surface.md](02-api-surface.md) and [appendices/api-endpoint-index.md](appendices/api-endpoint-index.md). + +### `backend/models/` + +SQLAlchemy ORM models and database helpers. These classes define the physical data model used by the service layer. + +Important groups: + +- Better Auth tables: `User`, `AuthSession`, `Account`, `Verification`. +- Workflow tables: `AppSession`, `Document`, `ExtractionResult`, `EvaluationResult`. +- Collaboration: `Group`, `UserGroup`. +- Templates: `TemplateFolder`, `PromptTemplate`, `TemplateVersion`, `TemplatePermission`. +- Preferences and audit: `UserPreferences`, `LoginHistory`, `UserPromptTemplate`. +- Evaluation jobs: `EvalJobRecord`. + +See [03-data-models.md](03-data-models.md). + +### `backend/schemas/` + +Pydantic models used for API input/output and session aggregates. These are separate from ORM models so HTTP contracts can remain explicit even when the database representation changes. + +See [04-schemas.md](04-schemas.md). + +### `backend/services/` + +Domain and infrastructure services: + +| Subpackage | Responsibility | +| --- | --- | +| `database/` | SQLAlchemy persistence access layer. | +| `session/` | Session orchestration and DB-to-Pydantic conversion. | +| `document/` | Upload organization, parser orchestration, artifact access, bbox normalization. | +| `storage/` | Azure Blob Storage wrapper. | +| `llm/` | Provider-specific LLM clients and routing façade. | +| `evaluation/` | DeepEval adapters, metric factories, result storage, background queue. | +| `groups/` | Group and membership authorization/business logic. | +| `templates/` | Prompt template CRUD, folders, permissions, versions, forks. | +| `telemetry/` | Cost and session metrics tracking. | + +## 4. Main data flows + +### 4.1 Authenticated request flow + +```text +Frontend request + -> Authorization: Bearer + -> FastAPI route with Depends(get_current_user) + -> core.auth.get_current_user() + -> query AuthSession + User from PostgreSQL + -> optional ALLOWED_EMAILS check + -> route receives current_user dict +``` + +Returned user dict contains `id`, `email`, `name`, `image`, and `is_admin`. + +### 4.2 Upload and processing flow + +```text +POST /api/upload + -> OrganizedFileService.save_uploaded_file() + -> SHA-256 file hash + -> blob: global/{hash}/original.{ext} + -> blob metadata: global/{hash}/metadata.json + -> optional Document DB row + +POST /api/documents/process/file/{file_hash} + -> OrganizedFileService cache check + -> DocumentService / processor orchestration + -> Azure Document Intelligence or Docling + -> local /tmp/summarization/{hash}/processed/{processor}/... + -> sync processed tree to blob + -> Document DB processing metadata update +``` + +See [05-document-processing.md](05-document-processing.md). + +### 4.3 Extraction flow + +```text +POST /api/extract + -> DocumentService.get_markdown_content() + -> optional figure context assembly + -> LLMService.extract_entities_from_markdown() + -> provider client call + -> normalize response/meta/cost + -> optional bbox matching against raw analysis + -> SessionService.add_extraction_result_fast() + -> extraction_results upsert +``` + +See [07-extraction-flow.md](07-extraction-flow.md). + +### 4.4 Evaluation flow + +```text +POST /api/evaluations/evaluate or /evaluate/batch + -> EvaluationService.create_evaluation_model() + -> metric factories produce GEval metrics + -> combined scoring prompt or per-metric fallback + -> cost tracking from adapter call history + -> optional JSON file result storage + +POST /api/evaluations/jobs + -> create EvalJob dataclass + -> persist EvalJobRecord for cross-worker status + -> background asyncio tasks + -> SessionService.add_evaluation_result_fast() +``` + +See [08-evaluation-flow.md](08-evaluation-flow.md). + +### 4.5 Restore-view flow + +```text +GET /api/sessions/{session_id}/restore-view + -> SessionService.get_session() + -> SessionService.build_restore_view() + -> OrganizedFileService.build_document_view() per document + -> frontend receives canonical uploadedFiles + processingResult state +``` + +See [09-session-sharing-groups.md](09-session-sharing-groups.md). + +## 5. Dependency direction + +The intended dependency direction is: + +```text +api -> services -> models/storage/provider SDKs +schemas -> api/services +core -> app/api/services as dependencies +``` + +Important exceptions: + +- `services.session` imports Pydantic schemas to build API-ready aggregate models. +- `services.telemetry.cost_tracker` writes session metrics back through the database service. +- Provider clients return dictionaries rather than shared typed result classes, so `LLMService` and API routers perform normalization. + +## 6. Infrastructure assumptions + +- PostgreSQL is available through `DATABASE_URL` or `POSTGRES_*` variables. +- Alembic migrations have been applied before serving traffic. +- Azure Blob Storage connection string is present for the organized file service in current upload/processing flows. +- Better Auth sidecar is reachable at the configured local/internal URL for `/api/auth/*` proxying. +- External provider credentials are optional per provider; unavailable providers should be reported as disabled rather than blocking the whole app. +- Production backend container is expected to run one Gunicorn worker per replica because Docling model memory and in-process concurrency controls assume a single process per container. + +## 7. Cross-cutting algorithms + +### Request observability + +`create_app()` installs an HTTP middleware that: + +1. assigns a short request ID; +2. binds the request ID into structlog context variables; +3. calls the downstream route; +4. logs method/path/status/duration at severity based on status code; +5. returns `X-Request-Id` in the response. + +### Processor selection + +`DocumentService._auto_select_processor()` currently selects Azure Document Intelligence if available; otherwise Docling. It does not yet perform content-based routing. + +### Model-provider routing + +`LLMService` switches by `model_type`. Each branch calls a provider client and records session metrics on successful responses. + +### Evaluation scoring + +`EvaluationService` prefers a combined scoring prompt for multiple metrics in one judge-model call, then falls back to per-metric concurrent evaluation if parsing fails. + +## 8. Related documents + +- [02-api-surface.md](02-api-surface.md) +- [03-data-models.md](03-data-models.md) +- [05-document-processing.md](05-document-processing.md) +- [06-llm-layer.md](06-llm-layer.md) +- [08-evaluation-flow.md](08-evaluation-flow.md) +- [11-auth-security-observability.md](11-auth-security-observability.md) diff --git a/docs/backend/02-api-surface.md b/docs/backend/02-api-surface.md new file mode 100644 index 0000000..9e35223 --- /dev/null +++ b/docs/backend/02-api-surface.md @@ -0,0 +1,479 @@ +# API Surface Technical Design + +This document describes the backend FastAPI API surface by router. It focuses on interface communication: paths, methods, request/response structures, authentication, service dependencies, and important exceptions. + +For a compact route-only list, see [appendices/api-endpoint-index.md](appendices/api-endpoint-index.md). + +## 1. API architecture + +Routers are included from `backend/main.py` in this order: + +1. `api.auth.proxy.router` +2. `api.auth.router` +3. `api.files.router` +4. `api.documents.router` +5. `api.extractions.router` +6. `api.evaluations.router` +7. `api.evaluations.jobs_router` +8. `api.server.router` +9. `api.paragraphgenerator.router` +10. `api.paragraph_evaluation.router` +11. `api.sessions.router` +12. `api.groups.router` +13. `api.templates.router` +14. `api.chat.router` + +Most application endpoints require `Depends(get_current_user)`. Some file endpoints use `get_optional_user` or no explicit dependency, but still derive access through file hashes or storage lookups. + +## 2. Auth proxy and auth endpoints + +### `backend/api/auth/proxy.py` + +Router tags: `auth-proxy`. + +| Method/path | Request | Response | Auth behavior | Purpose | +| --- | --- | --- | --- | --- | +| `/{api/auth/{path:path}}` for all common HTTP methods | Raw forwarded request | Raw proxied response | Proxies Better Auth traffic; `get-session` can be email-allowlist checked | Transparent proxy from FastAPI to Better Auth sidecar. | + +Implementation details: + +- Forwards `/api/auth/*` to the auth sidecar. +- Preserves relevant headers and `Set-Cookie` behavior. +- Adds forwarded host/proto headers so Better Auth can construct public callback URLs correctly. +- Strips hop-by-hop headers. + +### `backend/api/auth/router.py` + +Router prefix: `/auth`. + +| Method/path | Request | Response | Dependencies | Purpose | +| --- | --- | --- | --- | --- | +| `GET /auth/health` | none | health JSON | `get_current_user` | Auth-protected health check. | +| `POST /auth/history` | current user + request metadata | login-history record/status | `get_current_user` | Records login audit information. | + +Service dependencies: + +- `core.auth.get_current_user` +- `SQLAlchemyDBService.record_login` + +## 3. File endpoints + +### `backend/api/files/router.py` + +Router prefix: `/api`. + +Local response models: + +- `FileUploadResponse` +- `UserFileInfo` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/upload` | Multipart file upload, optional authenticated user | file hash/path metadata | Validate and store uploaded file through `OrganizedFileService`. | +| `GET /api/files/list` | optional user | list of user file metadata | List files associated with the user. | +| `GET /api/files/{file_id}` | file hash/id | file bytes response | Serve original uploaded file content. | +| `GET /api/files/{file_id}/info` | file hash/id | file metadata and processed flags | Return file metadata and parser availability state. | +| `DELETE /api/files/{file_id}` | file hash/id | status JSON | Delete behavior is stubbed/deferred in current code. | + +Important behavior: + +- Upload computes or reuses a SHA-256 file hash. +- Storage path follows `global/{file_hash}/original.{ext}` in blob storage. +- Metadata is stored at `global/{file_hash}/metadata.json`. +- Duplicate content returns the same hash and indicates deduplication. + +Service dependencies: + +- `OrganizedFileService` +- optional auth from `core.auth.get_optional_user` + +## 4. Document endpoints + +### `backend/api/documents/router.py` + +Router prefix: `/api/documents`. + +Request model: + +- `ProcessFileRequest` +- `ExtractFigureContentRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `GET /api/documents/{document_id}/view` | optional `processor_used` query | canonical document view | Build frontend restore/view state from blob and metadata. | +| `POST /api/documents/process/file/{file_id}` | `ProcessFileRequest` | processing result + document view | Process an uploaded file, using cache when possible. | +| `GET /api/documents/{document_id}/content` | optional `processor_used` query | markdown content JSON | Return processed `document.md`. | +| `GET /api/documents/{document_id}/enhanced-content` | optional `processor_used` query | markdown with figure summaries inserted | Return enhanced markdown content. | +| `GET /api/documents/{document_id}/figures` | document id/file hash | list of figure metadata | Return figures from processor metadata. | +| `GET /api/documents/{document_id}/analysis` | optional `processor_used` query | normalized raw analysis JSON | Return processor raw analysis normalized for frontend. | +| `GET /api/documents/{document_id}/figures/{figure_filename}` | file hash + figure filename | image response | Serve figure image artifact. | +| `POST /api/documents/{document_id}/figures/{figure_id}/generate-summary` | `ExtractFigureContentRequest` | generated figure summary | Run a vision model over a figure and persist summary metadata. | +| `POST /api/documents/{document_id}/figures/{figure_id}/extract-content` | legacy alias | generated figure summary | Backward-compatible alias for figure content extraction. | +| `GET /api/documents/{document_id}/tables/{table_filename}` | file hash + table filename | HTML response | Serve table HTML artifact. | + +Important helper functions: + +- `camel_to_snake_case()` converts camelCase keys. +- `transform_keys_to_snake_case()` recursively converts nested structures. +- `_generate_figure_summary_with_retry()` handles vision-summary retries. +- `_insert_figure_summaries_inline()` injects figure summaries into markdown. + +Service dependencies: + +- `DocumentService` +- `OrganizedFileService` +- `LLMService` +- `normalize_bbox_format` +- `cost_tracker` +- SQLAlchemy DB service for document row updates + +Key exceptions/status behavior: + +- Missing processed artifacts produce 404-style HTTP exceptions. +- Processor failures surface as failed processing JSON or HTTP exceptions depending on path. +- Figure/table filenames are validated before artifact lookup to reduce path traversal risk. + +## 5. Extraction endpoint + +### `backend/api/extractions/router.py` + +Router prefix: `/api`. + +Request model: + +- `ExtractRequest` +- nested `Entity` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/extract` | document conversion id, entities, model config, optional session id | extraction results per entity | Run entity extraction over markdown and persist optional session results. | + +Important behavior: + +- Loads markdown through `DocumentService`. +- Builds optional figure context from figure metadata and summaries. +- Runs one extraction task per entity concurrently. +- Uses `LLMService.extract_entities_from_markdown()` for provider routing. +- Attempts reference/bounding-box matching where raw analysis and references exist. +- Persists results with `SessionService.add_extraction_result_fast()` when `session_id` is provided. + +Service dependencies: + +- `DocumentService` +- `LLMService` +- `SessionService` +- Azure/Docling bbox matchers +- `cost_tracker` + +Provider map: + +- `azure` -> Azure OpenAI style extraction +- `gemini` -> Gemini/Vertex +- `anthropic` -> Anthropic Vertex +- `llama` / `azure-llama` +- `macbook` +- `vllm` + +## 6. Paragraph generation and paragraph evaluation + +### `backend/api/paragraphgenerator.py` + +Router prefix: `/api`. + +Request model: + +- `ParagraphGenerationRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/generate_paragraph` | extracted entities, model config, optional session id | generated paragraph text and metadata | Generate a scientific summary paragraph from extracted entity values. | + +Important behavior: + +- Routes to `LLMService.generate_paragraph()`. +- Persists paragraph output into session extraction results if `session_id` is provided. +- Uses provider map similar to extraction. + +### `backend/api/paragraph_evaluation.py` + +Router prefix: `/api/paragraph-evaluation`. + +Request model: + +- `ParagraphEvalGenerateRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/paragraph-evaluation/generate` | session/model/entity information | paragraph ground-truth/evaluation record | Build a deterministic paragraph ground truth from entity values. | + +Helper: + +- `build_paragraph_ground_truth(entities)` converts entity values into a paragraph-style expected answer. + +## 7. Evaluation endpoints + +### `backend/api/evaluations/router.py` + +Router prefix: `/api/evaluations`. + +Request models: + +- `EvaluationRequest` +- `BatchEvaluationRequest` +- `CustomMetricRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/evaluations/cancel` | `X-Session-Id` header | cancellation status | Mark a session evaluation as cancelled. | +| `POST /api/evaluations/evaluate` | `EvaluationRequest` | `EvaluationResponse`-like dict | Evaluate one extraction. | +| `POST /api/evaluations/evaluate/batch` | `BatchEvaluationRequest` | batch evaluation result | Evaluate multiple extraction outputs. | +| `POST /api/evaluations/evaluate/custom` | `CustomMetricRequest` | custom metric result | Evaluate using caller-provided metric steps. | +| `GET /api/evaluations/results/{evaluation_id}` | evaluation id | stored result JSON | Fetch a result from file-backed evaluation storage. | +| `GET /api/evaluations/results` | none | list of stored results | List stored evaluation outputs. | +| `GET /api/evaluations/metrics/info` | none | metric/provider metadata | Describe built-in metrics and configured providers. | + +Service dependencies: + +- `EvaluationService` +- cancellation helpers in `services.evaluation.evaluation_service` +- provider configuration from environment variables + +### `backend/api/evaluations/jobs.py` + +Router prefix: `/api/evaluations/jobs`. + +Request models: + +- `EvalTaskRequest` +- `ProviderConfigRequest` +- `SubmitJobRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/evaluations/jobs` | tasks + providers + session id | job status with job id | Submit background evaluation job. | +| `GET /api/evaluations/jobs/{job_id}` | job id | current job status | Poll in-memory or DB-backed job status. | +| `POST /api/evaluations/jobs/{job_id}/cancel` | job id | cancellation status | Cancel local or cross-worker job. | + +Service dependencies: + +- `services.evaluation.job_queue.create_job` +- `get_job` +- `cancel_job` +- `EvalJobRecord` persistence through DB service + +## 8. Session endpoints + +### `backend/api/sessions/router.py` + +Router prefix: `/api/sessions`. + +Request/response schemas: + +- `CreateSessionRequest` +- `UpdateSessionRequest` +- `Session` +- `SessionListResponse` +- local `ShareSessionRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/sessions` | `CreateSessionRequest` | `Session` | Create workflow session. | +| `GET /api/sessions` | current user | `SessionListResponse` | List user session summaries. | +| `GET /api/sessions/{session_id}` | session id | `Session` | Fetch full session owned by user, with shared fallback. | +| `GET /api/sessions/{session_id}/restore-view` | session id | restore-view dict | Build frontend restore state. | +| `PATCH /api/sessions/{session_id}` | `UpdateSessionRequest` | session dict | Update config, docs, extractions, evaluations. | +| `DELETE /api/sessions/{session_id}` | session id | deletion status | Delete owned session. | +| `POST /api/sessions/{session_id}/extractions` | `ExtractionResult` | updated session/status | Add extraction result. | +| `POST /api/sessions/{session_id}/evaluations` | `EvaluationResult` | updated session/status | Add evaluation result. | +| `GET /api/sessions/shared/list` | current user | `SessionListResponse` | List sessions shared with user groups. | +| `GET /api/sessions/shared/{session_id}` | session id | `Session` | Fetch shared session. | +| `GET /api/sessions/shared/{session_id}/restore-view` | session id | restore-view dict | Restore shared session view. | +| `POST /api/sessions/{session_id}/share` | group id | status/session | Share owned session with group. | +| `DELETE /api/sessions/{session_id}/share` | session id | status | Remove sharing from owned session. | + +Service dependencies: + +- `SessionService` +- `SQLAlchemyDBService` +- `OrganizedFileService` indirectly through restore view + +## 9. Group endpoints + +### `backend/api/groups/router.py` + +Router prefix: `/api/groups`. + +Local request/response models: + +- `CreateGroupRequest` +- `UpdateGroupRequest` +- `AddMemberRequest` +- `UpdateMemberRoleRequest` +- `GroupResponse` +- `GroupDetailResponse` +- `MemberResponse` +- `UserSearchResult` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `GET /api/groups` | current user | list groups | List groups for current user. | +| `POST /api/groups` | group name/description | group | Create group and owner membership. | +| `GET /api/groups/{group_id}` | group id | group detail | Fetch group with members. | +| `PUT /api/groups/{group_id}` | name/description | group | Update group metadata. | +| `DELETE /api/groups/{group_id}` | group id | 204 | Delete group. | +| `GET /api/groups/{group_id}/members` | group id | members | List group members. | +| `POST /api/groups/{group_id}/members` | user id/email + role | member | Add group member. | +| `PUT /api/groups/{group_id}/members/{user_id}` | role | member | Update member role. | +| `DELETE /api/groups/{group_id}/members/{user_id}` | member user id | 204 | Remove member. | +| `GET /api/groups/users/search` | query | users | Search users for group membership. | + +Service dependency: + +- `GroupService` + +Authorization is handled in `GroupService`, including owner/admin/member role rules and system-admin bypass where implemented. + +## 10. Template endpoints + +### `backend/api/templates/router.py` + +Router prefix: `/api/templates`. + +Local request/response models: + +- `EntityModel` +- `VariableModel` +- `CreateTemplateRequest` +- `UpdateTemplateRequest` +- `SetImmutableRequest` +- `SetPermissionRequest` +- `ForkTemplateRequest` +- `ChangeScopeRequest` +- `CreateFolderRequest` +- `RenameFolderRequest` +- `FolderResponse` +- `TemplateResponse` +- `VersionResponse` +- `PermissionResponse` + +Folder endpoints: + +| Method/path | Purpose | +| --- | --- | +| `GET /api/templates/folders` | List folders by scope/owner/parent. | +| `POST /api/templates/folders` | Create folder. | +| `PATCH /api/templates/folders/{folder_id}` | Rename folder. | +| `DELETE /api/templates/folders/{folder_id}` | Delete empty folder. | + +Template endpoints: + +| Method/path | Purpose | +| --- | --- | +| `GET /api/templates` | List accessible templates with filters. | +| `POST /api/templates` | Create template. | +| `GET /api/templates/{template_id}` | Fetch one template. | +| `PUT /api/templates/{template_id}` | Update template and create version snapshot. | +| `DELETE /api/templates/{template_id}` | Delete template. | +| `POST /api/templates/{template_id}/fork` | Copy accessible template into user scope. | +| `PUT /api/templates/{template_id}/scope` | Change template scope. | +| `PUT /api/templates/{template_id}/immutable` | Set immutability flag. | +| `GET /api/templates/{template_id}/versions` | List version history. | +| `POST /api/templates/{template_id}/revert/{version}` | Revert to a previous version. | +| `GET /api/templates/{template_id}/permissions` | List explicit permissions. | +| `POST /api/templates/{template_id}/permissions` | Upsert user permission. | +| `DELETE /api/templates/{template_id}/permissions/{user_id}` | Remove user permission. | + +Service dependencies: + +- `TemplateService` +- `FolderService` + +See [10-template-system.md](10-template-system.md). + +## 11. Server, metrics, and model endpoints + +### `backend/api/server/router.py` + +Router prefix: `/api`. + +Local request model: + +- `BatchMetricsRequest` + +| Method/path | Auth | Purpose | +| --- | --- | --- | +| `GET /api/server/health` | no schema auth | DB and service health check. | +| `POST /api/telemetry/traces` | unauthenticated internal/proxy style | Proxy browser OTLP traces to Tempo endpoint. | +| `POST /api/server/client-error` | unauthenticated report endpoint | Receive frontend client error reports. | +| `GET /api/server-config` | public | Return provider/config flags as `ServerConfig`. | +| `GET /api/models` | authenticated | Return available model catalog across providers. | +| `GET /api/server/session-metrics` | authenticated | Return session cost/latency/call totals. | +| `POST /api/server/session-metrics/load` | authenticated | Load metrics from DB into cost tracker. | +| `DELETE /api/server/session-metrics` | authenticated | Clear in-memory and DB metrics for a session. | +| `POST /api/server/batch-metrics` | authenticated | Record batch metric summary. | +| `GET /api/server/document-metrics` | authenticated | Return document-level metrics. | +| `POST /api/server/benchmark/clear` | authenticated | Clear benchmark/session cache state. | +| `GET /api/server/logs` | authenticated | Return backend logs. | + +Service dependencies: + +- `LLMService` provider clients indirectly +- `MacbookLLMClient` model discovery/health +- `cost_tracker` +- DB service +- log files under `backend/output/logs` + +## 12. Chat endpoint + +### `backend/api/chat/router.py` + +Router prefix: `/api/chat`. + +Request model: + +- `ChatQueryRequest` + +| Method/path | Request | Response | Purpose | +| --- | --- | --- | --- | +| `POST /api/chat/query` | query, optional `document_markdown`, model config | model answer JSON | General chat endpoint over optional uploaded document markdown context. | + +Important behavior: + +- Reuses LLM provider logic for a general answer rather than structured entity extraction. +- Frontend can concatenate multiple documents into the single `document_markdown` field. + +## 13. Interface conventions + +### Authentication + +Most protected routes use: + +```python +Depends(get_current_user) +``` + +The dependency returns a dict: + +```python +{ + "id": str, + "email": str, + "name": Optional[str], + "image": Optional[str], + "is_admin": bool +} +``` + +### Errors + +Routers generally use `HTTPException` for expected API errors. `main.py` also installs a global exception handler returning HTTP 500 with `{"detail": str(exc)}` for unhandled errors. + +### Response shape style + +The backend uses a mixture of: + +- Pydantic response models for sessions/groups/templates; +- plain dictionaries for processing, extraction, evaluation, server metrics, and model catalog endpoints; +- binary `Response` objects for files, figures, tables, and telemetry proxying. + +This mixed style should be considered part of the current API contract when changing clients. diff --git a/docs/backend/03-data-models.md b/docs/backend/03-data-models.md new file mode 100644 index 0000000..49d996f --- /dev/null +++ b/docs/backend/03-data-models.md @@ -0,0 +1,449 @@ +# Data Models Technical Design + +This document describes the backend physical data model implemented with SQLAlchemy models in `backend/models/` and Alembic migrations in `backend/alembic/`. + +## 1. Database infrastructure + +### `backend/models/base.py` + +| Symbol | Purpose | +| --- | --- | +| `Base` | SQLAlchemy declarative base for all ORM models. | +| `_build_database_url()` | Uses `DATABASE_URL` or constructs a PostgreSQL URL from `POSTGRES_*` variables. Converts asyncpg URLs to sync SQLAlchemy URLs. | +| `get_engine()` | Lazy sync SQLAlchemy engine singleton with pool settings. | +| `get_session_factory()` | Lazy `sessionmaker` singleton. | +| `get_db_session()` | Returns one new SQLAlchemy session. | +| `db_session_scope()` | Context manager for transaction-scoped DB writes with commit/rollback. | + +The ORM is synchronous. Async FastAPI handlers call sync DB code directly or through service methods; cost-tracker DB updates are pushed through an executor where needed. + +## 2. Auth tables + +### `User` + +File: `backend/models/user.py` + +Table: `user` + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `String(36)` | Primary key from Better Auth. | +| `name` | `Text` | Required display name. | +| `email` | `Text` | Required, unique. | +| `email_verified` | `Boolean` | Column name `emailVerified`, Python default `False`. | +| `image` | `Text` | Optional avatar/image URL. | +| `created_at` | `DateTime` | Column name `createdAt`, Python default now. | +| `updated_at` | `DateTime` | Column name `updatedAt`, Python default/onupdate now. | +| `role` | `Text` | Application role, Python default `user`. | +| `is_admin` | `Boolean` | Application admin flag, Python default `False`. | + +Associations: + +- Referenced by auth/session/account tables and nearly all app-owned records. + +### `AuthSession` + +Table: `session` + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `String(36)` | Primary key. | +| `expires_at` | `DateTime` | Column name `expiresAt`; used for session expiry. | +| `token` | `Text` | Required, unique; matched by `core.auth.get_current_user`. | +| `created_at` / `updated_at` | `DateTime` | Better Auth timestamp columns. | +| `ip_address` | `Text` | Column name `ipAddress`. | +| `user_agent` | `Text` | Column name `userAgent`. | +| `user_id` | `String(36)` | Column name `userId`; logical link to `user.id`. | + +### `Account` + +Table: `account` + +Stores Better Auth account/provider linkage, including OAuth access/refresh/id tokens, token expiry fields, scopes, and optional password field. + +### `Verification` + +Table: `verification` + +Stores Better Auth verification/reset tokens: `identifier`, `value`, expiry, created/updated timestamps. + +## 3. Session and document workflow tables + +### `AppSession` + +File: `backend/models/app_session.py` + +Table: `app_sessions` + +Represents one user workflow session, not a login session. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `user_id` | `String(36)` | FK to `user.id`, cascade delete. | +| `name` | `Text` | Default `Untitled Session`. | +| `status` | `Text` | Default `in_progress`. | +| `last_step` | `Text` | Default `upload`. | +| `configuration` | `JSONB` | Extraction/session config. | +| `evaluation_config` | `JSONB` | Evaluation settings. | +| `files_config` | `JSONB` | Per-file frontend/restore settings. | +| `total_cost` | `Float` | Session-level cost total. | +| `total_latency` | `Float` | Session-level latency total. | +| `total_calls` | `Integer` | Number of recorded calls. | +| `shared_with_group_id` | `UUID` | Optional FK to `groups.id`, set null on group deletion. | +| `shared_by` | `String(36)` | Optional FK to `user.id`. | +| `shared_at` | `DateTime` | Share timestamp. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Indexes: + +- `idx_app_sessions_user_id` +- `idx_app_sessions_updated_at` +- partial `idx_app_sessions_shared_group` where `shared_with_group_id IS NOT NULL` + +### `Document` + +File: `backend/models/document.py` + +Table: `documents` + +Tracks uploaded and processed document metadata. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `session_id` | `UUID` | Optional FK to `app_sessions.id`, cascade delete. | +| `user_id` | `String(36)` | FK to `user.id`, cascade delete. | +| `file_hash` | `Text` | SHA-256 content hash; indexed. | +| `filename` | `Text` | Original filename or resolved filename. | +| `file_path` | `Text` | Blob-style or local file path. | +| `study_type` | `Text` | Optional domain/study label. | +| `processor_used` | `Text` | Parser used, e.g. `azure_doc_intelligence` or `docling`. | +| `processing_status` | `Text` | Free-text status, default `pending`. | +| `processing_error` | `Text` | Error message if processing failed. | +| `extracted_text_path` | `Text` | Path to markdown or extracted content artifact. | +| `processed_at` | `DateTime` | Completed timestamp. | +| `parse_cost` | `Float` | Estimated parsing cost. | +| `page_count` | `Integer` | Parsed page count. | +| `parse_duration_seconds` | `Float` | Processing duration. | +| `figure_count` | `Integer` | Parsed figure count. | +| `table_count` | `Integer` | Parsed table count. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Indexes: + +- `idx_documents_session_id` +- `idx_documents_user_id` +- `idx_documents_file_hash` + +### `ExtractionResult` + +File: `backend/models/extraction.py` + +Table: `extraction_results` + +Stores one extracted entity for one document/model combination. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `session_id` | `UUID` | FK to `app_sessions.id`, cascade delete. | +| `document_id` | `UUID` | FK to `documents.id`, cascade delete. | +| `entity_name` | `Text` | Requested entity name. | +| `model_id` | `Text` | Provider/model identifier. | +| `extracted_text` | `Text` | Extracted answer text. | +| `bbox_references` | `JSONB` | Matched references/bounding boxes. | +| `status` | `Text` | Free-text status, default `pending`. | +| `error_message` | `Text` | Error text for failed extraction. | +| `extracted_at` | `DateTime` | Completion timestamp. | +| `prompt_tokens` | `Integer` | Provider prompt token count. | +| `completion_tokens` | `Integer` | Provider completion token count. | +| `duration_ms` | `Integer` | Extraction duration. | +| `cost` | `Float` | Estimated extraction cost. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Constraints/indexes: + +- Unique `(document_id, entity_name, model_id)` as `uq_extraction_doc_entity_model`. +- Indexed by session, document, and entity/model. + +Upsert behavior depends on the unique constraint. + +### `EvaluationResult` + +File: `backend/models/evaluation.py` + +Table: `evaluation_results` + +Stores one evaluation score for one extraction result, metric, and judge model. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `extraction_result_id` | `UUID` | FK to `extraction_results.id`, cascade delete. | +| `metric` | `Text` | Metric name, e.g. correctness, completeness, relevance, safety. | +| `score` | `Float` | Numeric score. | +| `reasoning` | `Text` | Judge explanation. | +| `judge_model` | `Text` | Model that judged the output. | +| `human_score` | `Float` | Optional human override. | +| `ground_truth` | `Text` | Expected answer. | +| `evaluation_cost` | `Float` | Estimated judge-call cost. | +| `evaluation_time` | `Float` | Evaluation duration. | +| `evaluated_at` | `DateTime` | Evaluation timestamp. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Constraints/indexes: + +- Unique `(extraction_result_id, metric, judge_model)` as `uq_eval_extraction_metric_judge`. +- Indexed by extraction id and judge model. + +## 4. Groups and memberships + +### `Group` + +File: `backend/models/group.py` + +Table: `groups` + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `name` | `Text` | Group name. | +| `description` | `Text` | Optional description. | +| `created_by` | `String(36)` | FK to `user.id`. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Indexes: + +- `idx_groups_created_by` +- `idx_groups_name` + +### `UserGroup` + +Table: `user_groups` + +Composite primary key: + +- `user_id` +- `group_id` + +Fields: + +| Field | Type | Notes | +| --- | --- | --- | +| `user_id` | `String(36)` | FK to `user.id`, cascade delete. | +| `group_id` | `UUID` | FK to `groups.id`, cascade delete. | +| `role` | `Text` | One of `viewer`, `member`, `admin`, `owner`. | +| `joined_at` | `DateTime` | Join timestamp. | + +Constraint: + +- `ck_user_groups_role` enforces allowed roles. + +## 5. Template system tables + +### `TemplateFolder` + +File: `backend/models/template.py` + +Table: `template_folders` + +Supports hierarchical folders scoped to user, group, or global template workspaces. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `name` | `Text` | Folder name. | +| `scope` | `Text` | `user`, `group`, or `global`. | +| `owner_user_id` | `String(36)` | User owner for user scope. | +| `owner_group_id` | `UUID` | Group owner for group scope. | +| `parent_id` | `UUID` | Self-FK for hierarchy. | +| `created_by` | `String(36)` | Creator user id. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Constraint: + +- `ck_template_folders_scope` enforces allowed scope. + +### `PromptTemplate` + +Table: `prompt_templates` + +Stores reusable prompt templates. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `name` | `Text` | Template name. | +| `description` | `Text` | Optional description. | +| `study_type` | `Text` | Optional domain/study type. | +| `scope` | `Text` | `user`, `group`, or `global`. | +| `owner_user_id` | `String(36)` | User owner for user scope. | +| `owner_group_id` | `UUID` | Group owner for group scope. | +| `system_prompt` | `Text` | Optional shared system prompt. | +| `entities` | `JSONB` | Entity definitions. | +| `summary_prompt` | `Text` | Optional paragraph/summary prompt. | +| `variables` | `JSONB` | Template variable definitions. | +| `is_immutable` | `Boolean` | Blocks edits when true. | +| `tags` | `ARRAY(Text)` | Search/filter tags. | +| `is_default` | `Boolean` | Default template flag. | +| `version` | `Integer` | Current version number. | +| `folder_id` | `UUID` | Folder association; model does not declare FK. | +| `created_by` | `String(36)` | Creator. | +| `created_at` / `updated_at` | `DateTime` | ORM timestamps. | + +Constraint: + +- `ck_templates_scope` enforces allowed scope. + +### `TemplateVersion` + +Table: `template_versions` + +Stores snapshots before template updates. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `template_id` | `UUID` | FK to `prompt_templates.id`, cascade delete. | +| `version` | `Integer` | Snapshot version. | +| `system_prompt` | `Text` | Snapshot field. | +| `entities` | `JSONB` | Snapshot field. | +| `summary_prompt` | `Text` | Snapshot field. | +| `variables` | `JSONB` | Snapshot field. | +| `changed_by` | `String(36)` | User who caused snapshot. | +| `change_summary` | `Text` | Optional summary. | +| `created_at` | `DateTime` | Snapshot timestamp. | + +Constraint: + +- Unique `(template_id, version)` as `uq_template_version`. + +### `TemplatePermission` + +Table: `template_permissions` + +Per-user template permission override. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `UUID` | Primary key. | +| `template_id` | `UUID` | FK to `prompt_templates.id`, cascade delete. | +| `user_id` | `String(36)` | FK to `user.id`, cascade delete. | +| `can_read` | `Boolean` | Read override. | +| `can_write` | `Boolean` | Write override. | +| `granted_by` | `String(36)` | User who granted permission. | +| `created_at` | `DateTime` | Grant timestamp. | + +Constraint: + +- Unique `(template_id, user_id)` as `uq_template_permission`. + +## 6. Preferences, login history, and legacy prompt templates + +### `UserPreferences` + +Table: `user_preferences` + +One row per user. Stores default models, default temperature, and arbitrary settings JSON. + +### `LoginHistory` + +Table: `login_history` + +Audit trail with `user_id`, `ip_address`, `user_agent`, and `login_at`. + +### `UserPromptTemplate` + +Table: `user_prompt_templates` + +Legacy user-scoped prompt templates. Unique `(user_id, name, entity_name)`. + +## 7. Evaluation jobs + +### `EvalJobRecord` + +File: `backend/models/eval_job.py` + +Table: `eval_jobs` + +Persists background evaluation job state so polling can work across workers/replicas. + +| Field | Type | Notes | +| --- | --- | --- | +| `job_id` | `Text` | Primary key. | +| `session_id` | `Text` | Associated session id. | +| `user_id` | `Text` | Owner/requester user id. | +| `status` | `Text` | pending/running/completed/cancelled/failed style status. | +| `progress` | `Integer` | Completed task count. | +| `total` | `Integer` | Total task count. | +| `results` | `JSONB` | Serialized task results. | +| `errors` | `JSONB` | Serialized errors. | +| `error` | `Text` | Top-level failure message. | +| `created_at` | `DateTime(timezone=True)` | Creation timestamp. | +| `completed_at` | `DateTime(timezone=True)` | Completion timestamp. | + +Indexes: + +- `idx_eval_jobs_status` +- `idx_eval_jobs_user_id` + +## 8. Alembic migrations + +### Initial schema + +`backend/alembic/versions/03069a8f5e8c_initial_schema.py` creates: + +- Better Auth tables: `account`, `session`, `user`, `verification`. +- Collaboration and preferences: `groups`, `user_groups`, `user_preferences`, `login_history`, `user_prompt_templates`. +- Workflow tables: `app_sessions`, `documents`, `extraction_results`, `evaluation_results`. +- Template tables: `template_folders`, `prompt_templates`, `template_versions`, `template_permissions`. + +### Eval jobs migration + +`backend/alembic/versions/b5f8e2a1c9d3_add_eval_jobs_table.py` creates `eval_jobs` with server defaults for `status`, `progress`, and `total`. + +## 9. Important model-vs-migration notes + +Some defaults are Python-side ORM defaults, not database server defaults. Direct SQL inserts may behave differently from ORM inserts unless callers provide values explicitly. + +Examples: + +- `User.email_verified` has a Python default but no server default in the initial migration. +- `AppSession.status`, `last_step`, config JSON fields, metrics, and timestamps rely on ORM/application defaults. +- `Document.processing_status` and `ExtractionResult.status` are free text and have no DB check constraints. +- `PromptTemplate.entities` is non-null in the model but nullable in the initial migration. +- `PromptTemplate.folder_id` is used as a logical association but no FK is declared in the model. +- `EvalJobRecord.results` and `errors` have Python defaults but no JSON server defaults in the migration. + +These are not necessarily bugs, but they are important implementation constraints for tests and direct DB scripts. + +## 10. Relationship summary + +```text +user + -> session / account / verification Better Auth + -> app_sessions workflow ownership + -> documents uploaded/processed docs + -> groups.created_by group creator + -> user_groups group membership + -> prompt_templates / template_folders template ownership/creation + -> template_permissions explicit permissions + -> user_preferences / login_history settings/audit + +app_sessions + -> documents + -> extraction_results + +extraction_results + -> evaluation_results + +groups + -> user_groups + -> app_sessions.shared_with_group_id + -> group-scoped templates/folders + +prompt_templates + -> template_versions + -> template_permissions +``` diff --git a/docs/backend/04-schemas.md b/docs/backend/04-schemas.md new file mode 100644 index 0000000..a9fa9b4 --- /dev/null +++ b/docs/backend/04-schemas.md @@ -0,0 +1,409 @@ +# Pydantic Schemas Technical Design + +This document describes request and response schemas in `backend/schemas/` and local router-level schemas in `backend/api/*`. These schemas define the API-facing data structures separate from SQLAlchemy ORM models. + +## 1. Shared enum schemas + +### `ProcessorType` + +File: `backend/schemas/enums.py` + +String enum values: + +| Name | Value | Meaning | +| --- | --- | --- | +| `AUTO` | `auto` | Let backend choose parser. | +| `DOCLING` | `docling` | Use Docling parser. | +| `AZURE_DOC_INTELLIGENCE` | `azure_doc_intelligence` | Use Azure Document Intelligence parser. | + +Used by document-processing requests. + +## 2. Document schemas + +File: `backend/schemas/documents.py` + +### `ProcessFileRequest` + +Request body for document processing. + +| Field | Type | Default | Notes | +| --- | --- | --- | --- | +| `processor` | `Optional[ProcessorType]` | `auto` | Parser selection. | +| `extract_figures` | `bool` | `True` | Whether figure extraction should run. | +| `batch_number` | `Optional[int]` | `None` | Frontend grouping/benchmark metadata. Description says 1-99, but no numeric bounds are enforced. | + +### `ExtractFigureContentRequest` + +Request body for figure content extraction or summary generation. + +| Field | Type | Default | Notes | +| --- | --- | --- | --- | +| `model_type` | `str` | `gemini` | Free string, not enum-constrained. | +| `model_id` | `Optional[str]` | `None` | Specific model identifier. | +| `extraction_prompt` | `str` | long OCR/scientific prompt | Prompt sent to vision model. | +| `max_tokens` | `int` | `2048` | No explicit bounds. | +| `temperature` | `float` | `0.0` | No explicit bounds. | +| `system_message` | `Optional[str]` | `None` | Optional system prompt. | + +### `FigureExtractionResult` + +Nested result attached to figure metadata. + +| Field | Type | +| --- | --- | +| `content` | `str` | +| `model_used` | `str` | +| `timestamp` | `str` | +| `duration` | `float` | + +### `FigureMetadata` + +Figure metadata shape returned to API callers. + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `str` | Figure identifier. | +| `page` | `Optional[int]` | Page number. | +| `caption` | `Optional[str]` | Figure caption. | +| `image_path` | `Optional[str]` | Relative artifact path. | +| `bounding_regions` | `Optional[list]` | Unconstrained list shape. | +| `extracted_content` | `Optional[FigureExtractionResult]` | Nested extracted summary/content. | + +## 3. Extraction schemas + +File: `backend/schemas/extractions.py` + +### `Entity` + +One entity extraction instruction. + +| Field | Type | Notes | +| --- | --- | --- | +| `name` | `str` | Entity label. | +| `prompt` | `str` | Entity-specific extraction prompt. | +| `extracted` | `Optional[str]` | Optional existing value. | +| `system_prompt` | `Optional[str]` | Optional per-entity system prompt. | + +### `ExtractRequest` + +Request body for `POST /api/extract`. + +| Field | Type | Notes | +| --- | --- | --- | +| `conversion_id` | `str` | File hash/conversion id. | +| `session_id` | `Optional[str]` | Session for persistence. | +| `deployment` | `Optional[str]` | Azure-style deployment. | +| `entities` | `List[Entity]` | Entity extraction instructions. | +| `api_version` | `Optional[str]` | Provider API version. | +| `azure_endpoint` | `Optional[str]` | Optional direct Azure endpoint. | +| `azure_api_key` | `Optional[str]` | Optional direct Azure API key. | +| `gemini_api_key` | `Optional[str]` | Optional Gemini API key. | +| `gemini_project_id` | `Optional[str]` | Optional GCP project. | +| `gemini_location` | `Optional[str]` | Optional Vertex location. | +| `max_tokens` | `int` | Default `8024`. | +| `temperature` | `float` | Default `0.0`. | +| `model_type` | `Optional[str]` | Default `azure`; free string. | +| `model_id` | `Optional[str]` | Provider model id. | +| `processor_used` | `Optional[str]` | Preferred parser output subtree. | + +Validation is intentionally light. Provider and model compatibility is handled in service/router code. + +## 4. Evaluation schemas + +File: `backend/schemas/evaluations.py` + +### `EvaluationRequest` + +Request for one extraction evaluation. + +Core fields: + +| Field | Type | Default / constraint | +| --- | --- | --- | +| `entity_name` | `str` | required | +| `extraction_prompt` | `str` | required | +| `actual_output` | `str` | required | +| `expected_output` | `Optional[str]` | `None` | +| `metrics` | `Optional[List[str]]` | `['all']`; values not enum-constrained | +| `provider` | `str` | `azure_openai` | +| `threshold` | `float` | default `0.5`, constrained `0.0 <= x <= 1.0` | +| `strict_mode` | `bool` | `False` | +| `custom_evaluation_steps` | `Optional[Dict[str, List[str]]]` | `None` | + +Provider-specific fields: + +- Azure: `azure_deployment`, `azure_endpoint`, `azure_api_key`, `azure_model_name`. +- Vertex: `vertex_model_name`, `vertex_project`, `vertex_location`. +- Anthropic/other: `model_name`. + +### `SingleExtractionEval` + +Nested item for batch evaluation: + +- `entity_name` +- `extraction_prompt` +- `actual_output` +- `expected_output` + +### `BatchEvaluationRequest` + +Batch request with: + +- `extractions: List[SingleExtractionEval]` +- metrics/custom steps/provider/threshold/strict mode +- same Azure/Vertex/model provider fields as single evaluation + +### `CustomMetricRequest` + +Request for one custom metric evaluation: + +| Field | Type | Notes | +| --- | --- | --- | +| `metric_name` | `str` | Custom metric label. | +| `evaluation_steps` | `List[str]` | Required but no min length. | +| `entity_name` | `str` | Entity label. | +| `extraction_prompt` | `str` | Original extraction prompt. | +| `actual_output` | `str` | Output being evaluated. | +| `expected_output` | `Optional[str]` | Ground truth. | +| provider fields | mixed | Same pattern as other evaluation schemas. | + +### Evaluation response models + +`MetricResult`: + +- `metric_name` +- `score` +- `threshold` +- `success` +- `reason` + +`EvaluationResponse`: + +- evaluation metadata, test case, metric results, aggregate score, pass/fail, status, optional error. + +`BatchEvaluationResponse`: + +- batch id, timing, counts, average score, pass/fail, provider, and raw result dictionaries. + +## 5. Server config schema + +File: `backend/schemas/server.py` + +### `ServerConfig` + +Returned by `/api/server-config`. + +| Field | Type | Meaning | +| --- | --- | --- | +| `is_azure_openai_configured` | `bool` | Azure OpenAI credentials/models available. | +| `is_gemini_configured` | `bool` | Gemini/Vertex configured. | +| `is_azure_document_intelligence_configured` | `bool` | Azure Document Intelligence available. | +| `is_llama_configured` | `bool` | Llama MaaS configured. | +| `is_macbook_configured` | `bool` | Macbook base URL configured. | +| `is_macbook_healthy` | `bool` | Macbook endpoint reachable. | + +## 6. Session schemas + +File: `backend/schemas/sessions.py` + +These schemas are the main aggregate response contract for workflow restore and session history. + +### `SessionEntity` + +| Field | Type | +| --- | --- | +| `name` | `str` | +| `prompt` | `str` | +| `system_prompt` | `Optional[str]` | + +### `SessionConfiguration` + +| Field | Type | Default | +| --- | --- | --- | +| `study_type` | `Optional[str]` | `None` | +| `selected_models` | `List[str]` | empty list | +| `entities` | `List[SessionEntity]` | empty list | +| `summary_prompt` | `Optional[str]` | `None` | +| `paragraph_system_prompt` | `Optional[str]` | `None` | +| `temperature` | `float` | `0.0` | +| `model_temperatures` | `Optional[Dict[str, float]]` | empty dict | +| `files_config` | `Optional[Dict[str, Any]]` | empty dict | +| `evaluation_config` | `Optional[Dict[str, Any]]` | empty dict | + +### `SessionDocument` + +Document summary in a session response: + +- `id` +- `file_hash` +- `filename` +- `processor_used` +- `parse_cost` +- `page_count` +- `parse_duration_seconds` +- `figure_count` +- `table_count` + +### `ExtractionResult` + +API/session extraction result, distinct from ORM model with the same class name. + +| Field | Type | Notes | +| --- | --- | --- | +| `entity_name` | `str` | Entity label. | +| `model_id` | `str` | Provider/model id. | +| `document_id` | `Optional[str]` | Associated document id. | +| `extracted_text` | `Optional[str]` | Answer text. | +| `references` | `Optional[List[Dict[str, Any]]]` | Bounding-box/reference data. | +| `status` | `Literal['pending','completed','error']` | Strictly validated. | +| `error_message` | `Optional[str]` | Error text. | +| `extracted_at` | `Optional[datetime]` | Completion timestamp. | +| `file_hash` | `Optional[str]` | File hash for matching. | +| token/cost fields | optional ints/floats | Usage metrics. | + +### `SessionMetrics` + +- `total_cost: float = 0.0` +- `total_latency: float = 0.0` +- `total_calls: int = 0` + +### `EvaluationScore` + +One metric score: + +- `metric` +- `score` +- `reasoning` +- `judge_model` +- `human_score` +- `evaluation_cost` +- `evaluation_time` + +### `EvaluationResult` + +Session-level grouped evaluation result: + +- document/file/entity/model identity fields; +- `ground_truth`; +- `scores: List[EvaluationScore]`; +- optional aggregate human/cost/time fields. + +### `Session` + +Full session aggregate: + +| Field | Type | Notes | +| --- | --- | --- | +| `session_id` | `str` | Default UUID string. | +| `user_id` | `str` | Owner. | +| `name` | `str` | Default `Untitled Session`. | +| `status` | `Literal['in_progress','completed']` | Strictly validated. | +| `last_step` | `Optional[str]` | UI workflow step. | +| `evaluation_config` | `Optional[Dict[str, Any]]` | Session-level eval config. | +| `files_config` | `Optional[Dict[str, Any]]` | Per-file config. | +| `created_at` / `updated_at` | `datetime` | Timestamps. | +| `configuration` | `SessionConfiguration` | Main workflow configuration. | +| `documents` | `List[SessionDocument]` | Documents in session. | +| `extraction_results` | `List[ExtractionResult]` | Extraction outputs. | +| `evaluation_results` | `List[EvaluationResult]` | Grouped evaluation outputs. | +| `session_metrics` | `Optional[SessionMetrics]` | Optional aggregate metrics. | + +### `CreateSessionRequest` + +Fields: + +- `user_id` +- optional name/last_step/config/evaluation_config/files_config/documents + +### `UpdateSessionRequest` + +All fields optional. Supports updating: + +- user/name/status/last_step; +- configuration/evaluation_config/files_config; +- documents/extraction_results/evaluation_results. + +### `SessionSummary` and `SessionListResponse` + +`SessionSummary` is list-card style metadata: + +- session id, name, status, timestamps, last step; +- study type, document count/names, extraction/evaluation counts; +- shared session display fields. + +`SessionListResponse` wraps: + +- `sessions: List[SessionSummary]` +- `total: int` + +## 7. Router-local schemas + +Some routers define local Pydantic models rather than central schemas. + +### Files router + +- `FileUploadResponse` +- `UserFileInfo` + +These cover upload metadata and user-file list entries. + +### Groups router + +- `CreateGroupRequest` +- `UpdateGroupRequest` +- `AddMemberRequest` +- `UpdateMemberRoleRequest` +- `GroupResponse` +- `MemberResponse` +- `GroupDetailResponse` +- `UserSearchResult` + +These mirror group service dictionaries and profile-enriched membership data. + +### Templates router + +- `EntityModel` +- `VariableModel` +- `CreateTemplateRequest` +- `UpdateTemplateRequest` +- `SetImmutableRequest` +- `SetPermissionRequest` +- `ForkTemplateRequest` +- `ChangeScopeRequest` +- `CreateFolderRequest` +- `RenameFolderRequest` +- `FolderResponse` +- `TemplateResponse` +- `VersionResponse` +- `PermissionResponse` + +These define the template workspace API contract. + +### Evaluation jobs router + +- `EvalTaskRequest` +- `ProviderConfigRequest` +- `SubmitJobRequest` + +These are converted into `EvalTask`, `ProviderConfig`, and `EvalJob` dataclasses in the job queue. + +### Chat router + +- `ChatQueryRequest` + +Fields include query text, optional document markdown, and model configuration. + +### Paragraph routers + +- `ParagraphGenerationRequest` +- `ParagraphEvalGenerateRequest` + +These support generated scientific summary paragraphs and paragraph-specific evaluation records. + +## 8. Schema design notes + +- Several fields are free strings instead of enums (`model_type`, `provider`, `metrics`, status fields in ORM models). Validation happens later in services. +- Session API schemas use `Literal` for `Session.status` and API extraction status, making the API stricter than some database fields. +- Many nested JSON structures intentionally use `Dict[str, Any]` or `List[Dict[str, Any]]` because provider outputs and bbox references vary by parser/provider. +- Mutable defaults use `default_factory` in session schemas where needed. +- There are naming collisions between ORM classes and Pydantic classes, especially `ExtractionResult` and `EvaluationResult`. Always qualify by package in technical discussions. diff --git a/docs/backend/05-document-processing.md b/docs/backend/05-document-processing.md new file mode 100644 index 0000000..005cac9 --- /dev/null +++ b/docs/backend/05-document-processing.md @@ -0,0 +1,395 @@ +# Document Processing Technical Design + +This document describes upload, storage, parsing, artifact access, bounding-box normalization, figure/table handling, and document-view construction. + +## 1. Scope + +In scope: + +- file upload and SHA-256 deduplication; +- Azure Blob Storage paths and local cache paths; +- processor selection between Azure Document Intelligence and Docling; +- processor output artifacts; +- document viewer/restore state; +- raw analysis normalization; +- figure/table serving and figure-summary generation. + +Out of scope: + +- frontend rendering of the document viewer; +- cloud provisioning for blob storage; +- internals of the remote Docling service outside this repository. + +## 2. Main classes and files + +| Class/function | File | Responsibility | +| --- | --- | --- | +| `DocumentService` | `backend/services/document/document_service.py` | High-level parser façade and artifact readers. | +| `OrganizedFileService` | `backend/services/document/organized_file_service.py` | Hash-based upload, blob paths, processed artifact access, document-view builder. | +| `OrganizedDocumentProcessor` | `backend/services/document/organized_processor.py` | Coordinates processing into organized `/tmp` and blob paths. | +| `BlobStorageClient` | `backend/services/storage/blob_storage.py` | Async Azure Blob Storage wrapper. | +| `AzureDocIntelligenceService` | `backend/services/document/processors/azure_doc_intelligence/azure_doc_intelligence_service.py` | Azure parser implementation. | +| `DoclingService` | `backend/services/document/processors/docling/docling_service.py` | Local Docling parser implementation with process pool and VRAM guard. | +| `DoclingRemoteClient` | `backend/services/document/processors/docling/docling_remote_client.py` | Remote Docling service client. | +| `VRAMGuard` | `backend/services/document/processors/docling/vram_guard.py` | GPU memory-aware concurrency guard for local Docling. | +| `normalize_bbox_format()` | `backend/services/document/bbox_normalizer.py` | Normalizes Azure/Docling raw analysis to a common shape. | +| bbox matchers | `backend/services/document/processors/*/bounding_box_matcher.py` | Match extraction references to document bounding boxes. | + +## 3. Upload and storage model + +### 3.1 Upload entry point + +`POST /api/upload` calls `OrganizedFileService.save_uploaded_file()`. + +### 3.2 Hashing and deduplication + +`OrganizedFileService.compute_file_hash(content)` computes a SHA-256 hex digest. That hash is the stable identifier used across upload, processing, retrieval, and cache lookup. + +Upload blob layout: + +```text +global/{sha256}/original.{ext} +global/{sha256}/metadata.json +``` + +Upload metadata includes: + +- `file_hash` +- `original_filename` +- `file_size` +- `mime_type` +- `created_at` +- `extension` + +If the original blob already exists, upload returns `is_new=False` and `deduplicated=True` instead of rewriting the file. + +### 3.3 Optional DB registration + +When a user id is available, `save_uploaded_file()` attempts to register a `Document` row through the DB service. DB registration failures are warning-logged and do not fail blob upload. + +## 4. Processed artifact model + +### 4.1 Local scratch path + +Processors write to local scratch space first: + +```text +/tmp/summarization/{sha256}/processed/{processor}/ +``` + +This path is returned by `OrganizedFileService.get_processing_output_path(file_hash, processor)`. + +### 4.2 Blob processed path + +After processing completes, the local tree is synced to blob: + +```text +global/{sha256}/processed/{processor}/document.md +global/{sha256}/processed/{processor}/metadata.json +global/{sha256}/processed/{processor}/raw_analysis.json +global/{sha256}/processed/{processor}/figures/{filename} +global/{sha256}/processed/{processor}/tables/{filename} +``` + +`OrganizedFileService.sync_processing_output_to_blob()` uploads the directory recursively and preserves relative paths. + +### 4.3 Read/cache behavior + +`OrganizedFileService.get_processing_file_bytes(file_hash, processor, relative_path)` reads from local `/tmp` first. If missing, it downloads from blob, writes the bytes into the matching local cache path, and returns them. + +This supports cross-replica blob persistence with per-container local cache warming. + +## 5. Processor selection + +`DocumentService.convert_document_to_markdown()` accepts a `ProcessorType`: + +- `auto` +- `azure_doc_intelligence` +- `docling` + +Current auto-selection algorithm: + +```text +if Azure Document Intelligence is available: + choose azure_doc_intelligence +else: + choose docling +``` + +If Azure is explicitly requested but unavailable, the service falls back to Docling and annotates the result with: + +- `processor_used = "docling"` +- `processor_fallback = True` +- `fallback_reason = "Azure Document Intelligence not available"` + +## 6. Azure Document Intelligence processing + +Class: `AzureDocIntelligenceService` + +### 6.1 Availability + +Azure is available only when: + +- the Azure Document Intelligence SDK imports successfully; +- `AZURE_DOC_INTELLIGENCE_ENDPOINT` is configured; +- `AZURE_DOC_INTELLIGENCE_KEY` is configured. + +### 6.2 Conversion algorithm + +For each conversion: + +1. Create a conversion id and output directory. +2. Call Azure `begin_analyze_document()` with markdown output and optional figures. +3. Wait for `poller.result()`. +4. Save the complete result dictionary as `raw_analysis.json`. +5. Save markdown content as `document.md`. +6. Extract HTML table blocks from markdown into `tables/table-{n}.html`. +7. Download figure images when result id and figure ids are available. +8. Save summary `metadata.json`. + +### 6.3 Azure output files + +```text +document.md +raw_analysis.json +metadata.json +conversion.log +figures/{figure_id}.png +tables/table-{idx}.html +``` + +Metadata includes: + +- conversion id; +- source/source type; +- processor/model id; +- status; +- conversion/log/raw/markdown paths; +- start/end time; +- conversion time and parse duration; +- content length; +- page count; +- table count; +- key-value pair count; +- figure count and figure metadata. + +Figure metadata includes id, page, caption, spans, bounding regions, and optional image path. + +## 7. Docling processing + +There are two Docling-related implementation paths: + +- `DoclingRemoteClient` for the remote Docling service used by `DocumentService` and `OrganizedDocumentProcessor`. +- `DoclingService` for local processing with multiprocessing and VRAM-aware concurrency. + +### 7.1 Local Docling worker algorithm + +`_docling_worker_process()` performs the conversion in a subprocess: + +1. Initialize or reuse a process-local `DocumentConverter`. +2. Convert the source PDF. +3. Extract picture items and save PNGs into `figures/`. +4. Export markdown to `document.md`. +5. Export table HTML to `tables/table-{idx}.html`. +6. Build a Docling-style `raw_analysis.json` containing pages, paragraphs, tables, figures, and document structure. +7. Optionally write a debug `docling_document.json`. +8. Return success metadata including parse duration, markdown, page count, image info, and peak VRAM. + +### 7.2 Docling output files + +```text +document.md +raw_analysis.json +metadata.json +conversion.log +figures/picture-{n}.png +tables/table-{idx}.html +docling_document.json # debug output when available +``` + +### 7.3 VRAM guard + +`VRAMGuard` protects local Docling conversion concurrency. + +Key behavior: + +- Computes max workers from total GPU VRAM, safety margin, and observed per-worker memory. +- Uses cold-start limits before enough jobs have completed. +- Provides async `acquire_slot()` context manager. +- Tracks active and queued workers. +- Updates per-worker estimate from recent peak VRAM observations. +- Persists state in `.vram_guard_state.json` when possible. +- On OOM, increases estimated per-worker memory and shrinks allowed concurrency. + +## 8. Organized processing flow + +`OrganizedDocumentProcessor.process_document()` provides a hash-oriented processing path: + +```text +input file path + -> read bytes and compute hash + -> OrganizedFileService.save_uploaded_file() + -> if already processed and not force_reprocess: + return cached document.md + metadata + else: + output_path = /tmp/summarization/{hash}/processed/{processor} + original_path = get_original_file_path(hash) + process with Azure or Docling + sync output_path to blob + return result +``` + +Cache hit requires `OrganizedFileService.is_file_processed()`, which strictly checks for `document.md`. + +## 9. Artifact resolution + +`OrganizedFileService.resolve_processed_processor(file_hash, preferred_processor)` chooses the processor subtree to read. + +Candidate order: + +1. preferred processor if given; +2. `azure_doc_intelligence`; +3. `docling`. + +A processor counts as resolved if any of the following exists: + +- `metadata.json` +- `document.md` +- `raw_analysis.json` +- any blob under the processed subtree prefix + +This is intentionally more permissive than `is_file_processed()` so partially available artifact trees can still be inspected. + +## 10. Document view contract + +`OrganizedFileService.build_document_view()` assembles canonical frontend state. + +Top-level fields include: + +- `fileName` +- `fileId` +- `status` +- `selectedParser` +- `processorUsed` + +Nested `processingResult` fields include both camelCase and snake_case names for compatibility: + +- `conversionId` +- `fileHash` +- `processorUsed` +- `markdownPath` +- `parseCost` / `parse_cost` +- `parseDuration` / `parse_duration_seconds` +- `pageCount` / `page_count` +- `figures` +- `figuresCount` +- `tablesCount` +- `artifactAvailability` + +Artifact availability flags: + +- `original` +- `markdown` +- `analysis` +- `figures` +- `tables` + +If figure/table counts are missing from metadata, the service enumerates blob prefixes under `figures/` and `tables/`. + +## 11. Raw analysis normalization + +`normalize_bbox_format(analysis_result)` detects processor type and returns a normalized shape. + +### 11.1 Processor detection + +Azure indicators: + +- `apiVersion` +- `modelId` +- camelCase `boundingRegions` +- page `unit` + +Docling indicators: + +- `api_version` +- `document_structure` +- snake_case `bounding_regions` + +### 11.2 Normalized output shape + +Common top-level fields: + +- `processor` +- `api_version` +- `model_id` +- `pages` +- `paragraphs` +- `tables` +- `figures` + +Azure page coordinates in inches are converted to points. Docling output is already closer to the normalized snake_case shape and is mostly copied with field normalization. + +## 12. Bounding-box and reference matching + +Azure matcher: + +- normalizes text; +- searches paragraphs first; +- falls back to page lines; +- extracts figure references with regexes such as Figure/Fig/FIG; +- maps figure references to figure metadata and bounding regions; +- returns best matches plus paragraph/line match candidates. + +Docling matcher: + +- searches paragraphs; +- falls back to page-level match; +- can extract polygon coordinates from 8-value arrays; +- returns simpler match structures than Azure. + +These matchers are used by extraction routes to attach visual grounding to entity answers. + +## 13. Figure and table handling + +### 13.1 Figure image serving + +`GET /api/documents/{document_id}/figures/{figure_filename}` reads figure bytes from processed artifacts and returns an image response. + +The router validates filenames before reading artifacts to avoid arbitrary path access. + +### 13.2 Figure summary generation + +`POST /api/documents/{document_id}/figures/{figure_id}/generate-summary`: + +1. resolves figure metadata and image path; +2. writes image bytes to a temporary file if required by provider client; +3. calls `LLMService.extract_content_from_image()`; +4. stores generated content back in metadata; +5. returns the updated figure summary. + +### 13.3 Enhanced markdown + +`GET /api/documents/{document_id}/enhanced-content` inserts available figure summaries into markdown near their figure references. + +### 13.4 Table serving + +`GET /api/documents/{document_id}/tables/{table_filename}` returns saved table HTML from the processed artifact tree. + +## 14. Failure modes + +| Failure | Current behavior | +| --- | --- | +| Missing blob connection string | `OrganizedFileService` construction fails. | +| Duplicate upload | Returns existing hash and dedupe flags. | +| Missing processed document | Content endpoints return not-found style errors. | +| Azure unavailable | Auto mode uses Docling; explicit Azure falls back with flags. | +| Figure image missing | Figure endpoint returns not found; summary generation fails for that figure. | +| Malformed raw analysis | Some readers assume valid JSON; callers may receive errors. | +| Docling OOM | Worker returns structured error, VRAM guard reports OOM and shrinks concurrency estimate. | + +## 15. Related docs + +- [02-api-surface.md](02-api-surface.md) +- [03-data-models.md](03-data-models.md) +- [07-extraction-flow.md](07-extraction-flow.md) +- [appendices/data-flow-diagrams.md](appendices/data-flow-diagrams.md) diff --git a/docs/backend/06-llm-layer.md b/docs/backend/06-llm-layer.md new file mode 100644 index 0000000..40781a8 --- /dev/null +++ b/docs/backend/06-llm-layer.md @@ -0,0 +1,349 @@ +# LLM Provider Layer Technical Design + +This document describes the provider-routing layer implemented under `backend/services/llm/`. It covers classes, request/response contracts, timeout/retry behavior, concurrency controls, and cost-tracking integration. + +## 1. Scope + +In scope: + +- `LLMService` provider dispatch; +- provider client responsibilities; +- extraction, image extraction, and paragraph generation methods; +- return dictionary conventions; +- timeout/retry behavior; +- Macbook queue serialization; +- cost/session metric recording. + +Out of scope: + +- frontend model picker UI; +- provider account provisioning; +- exact model availability at runtime. + +## 2. Main classes + +| Class | File | Responsibility | +| --- | --- | --- | +| `LLMService` | `backend/services/llm/llm_service.py` | High-level router across provider clients. | +| `AzureLLMClient` | `backend/services/llm/azure.py` | Azure OpenAI / Azure AI Foundry REST+SDK calls. | +| `GeminiLLMClient` | `backend/services/llm/gemini.py` | Vertex AI Gemini text and vision calls. | +| `AnthropicLLMClient` | `backend/services/llm/anthropic.py` | Anthropic-on-Vertex calls. | +| `LlamaLLMClient` | `backend/services/llm/llama.py` | Vertex AI MaaS Llama calls. | +| `MacbookLLMClient` | `backend/services/llm/macbook.py` | Ollama-compatible Macbook-hosted inference. | +| `MacbookRequestQueue` | `backend/services/llm/macbook_queue.py` | FIFO single-worker queue for Macbook inference. | +| `VLLMClient` | `backend/services/llm/vllm.py` | OpenAI-compatible vLLM endpoint client. | + +## 3. `LLMService` + +`LLMService` owns provider client instances and exposes three main operations: + +- `extract_entities_from_markdown(...)` +- `extract_content_from_image(...)` +- `generate_paragraph(...)` + +It also records usage/cost metrics through `_record_session_metrics()` after successful provider calls. + +### 3.1 Provider dispatch for entity extraction + +`extract_entities_from_markdown()` dispatches by `model_type`: + +| `model_type` | Client method | +| --- | --- | +| `azure` | `AzureLLMClient.extract_entities_with_azure()` | +| `gemini` | `GeminiLLMClient.extract_entities_with_gemini()` | +| `anthropic` | `AnthropicLLMClient.extract_entities_with_anthropic()` | +| `llama` | `LlamaLLMClient.extract_entities_with_llama()` | +| `azure-llama` | currently routed through Azure client path | +| `macbook` | `MacbookLLMClient.extract_entities_with_macbook()` | +| `vllm` | `VLLMClient.extract_entities_with_vllm()` | + +Provider-disabled clients return structured `success=False` responses rather than crashing the whole app. + +### 3.2 Timeout budgets + +Default wrapper timeout is 240 seconds, with provider-specific overrides: + +| Provider path | Timeout | +| --- | ---: | +| Azure/Gemini/Anthropic | 240s default wrapper | +| Llama | 300s | +| Macbook | 1900s | +| vLLM | 600s | + +Timeouts are logged to `backend/output/timeout_logs/timeout_log.txt`. + +### 3.3 Session metrics + +On successful responses, `_record_session_metrics(session_id, provider, result)` extracts: + +- provider; +- model/deployment; +- prompt tokens; +- completion tokens; +- duration. + +Then it calls `cost_tracker.record_call(...)`. + +## 4. Common response conventions + +Provider clients return dictionaries rather than a shared class. Common keys: + +| Key | Meaning | +| --- | --- | +| `success` | Boolean success flag. | +| `content` | Main text result. | +| `answer` | Structured answer text, when available. | +| `references` | Structured references, when available. | +| `raw` | Raw provider response or parsed JSON. | +| `meta` | Provider/model/timing/token metadata. | +| `error` | Error text when `success=False`. | + +Provider-specific clients may also return compatibility fields such as `extracted_text`, `generated_text`, `usage`, `model`, or strategy metadata. + +Downstream code should check `success` and then normalize provider-specific fields. + +## 5. Azure provider + +Class: `AzureLLMClient` + +### 5.1 Configuration + +Supports: + +- global Azure OpenAI env vars; +- `AZURE_OPENAI_MODELS` JSON list for per-deployment endpoints/keys/API versions; +- Azure AI Foundry serverless endpoints detected by `.services.ai.azure.com`. + +### 5.2 Entity extraction + +Primary path uses OpenAI SDK structured outputs: + +- Pydantic `MarkdownReference` +- Pydantic `ExtractionResult` +- `client.beta.chat.completions.parse(...)` +- JSON response format with answer and references + +Fallback path uses raw REST `requests.post()` if structured parsing fails. + +Retry behavior: + +- up to 3 attempts; +- retry on 429, 500, 503, 504 or matching exception text; +- exponential backoff with jitter; +- temperature-related 400 errors can retry without `temperature`. + +### 5.3 Paragraph generation + +Uses REST chat completions. Payload includes messages, max completion tokens, `n=1`, optional temperature, and Foundry-specific model field when needed. + +### 5.4 Vision extraction + +Reads image as base64 and sends a multimodal chat payload with text and image data URL through REST using `aiohttp`. + +## 6. Gemini provider + +Class: `GeminiLLMClient` + +### 6.1 Configuration + +Requires: + +- GCP project id; +- Vertex location; +- service account credentials. + +Supports env aliases such as `GEMINI_PROJECT_ID`, `GEMINI_PROJECT`, `VERTEX_AI_PROJECT`, `GEMINI_LOCATION`, and `VERTEX_AI_LOCATION`. + +### 6.2 Core call behavior + +Builds Vertex AI publisher-model endpoint: + +- global endpoint for global-only models; +- regional endpoint otherwise. + +Payload supports: + +- `contents`; +- `generationConfig.temperature`; +- `generationConfig.maxOutputTokens`; +- optional `responseMimeType: application/json` and `responseJsonSchema`; +- optional `systemInstruction`. + +Retry behavior: + +- three attempts; +- retries 429/500/503/504; +- retries empty responses, JSON parse failures, and content extraction errors; +- returns partial content if finish reason is `MAX_TOKENS`. + +### 6.3 Supported extraction/generation models + +Allowed short names include: + +- `gemini-2.5-pro` +- `gemini-2.5-flash-lite` +- `gemini-2.5-flash` +- `gemini-3-pro-preview` + +Structured extraction uses the Gemini version of `ExtractionResult.model_json_schema()`. + +## 7. Anthropic provider + +Class: `AnthropicLLMClient` + +### 7.1 Configuration + +Uses `anthropic.AnthropicVertex`, requiring Google service account credentials. It finds credentials from `GOOGLE_APPLICATION_CREDENTIALS` or JSON files under `backend/core/`. + +### 7.2 Core call behavior + +Calls `client.messages.create(...)` with: + +- `model` +- `max_tokens` +- `messages` +- optional `system` +- optional `temperature` + +For structured extraction, the code uses prompt-enforced JSON because Vertex-hosted Anthropic structured output beta is not used here. + +### 7.3 Entity extraction + +Default model: + +- `claude-sonnet-4-5@20250929` + +Structured-output prompt requests: + +- `answer: string` +- `references: [{ text: string }]` + +## 8. Llama provider + +Class: `LlamaLLMClient` + +### 8.1 Configuration + +Uses Vertex AI MaaS OpenAI-compatible endpoint. Requires project, location/region, and service account credentials. + +### 8.2 Region routing + +- Llama 4 models route to `us-east5`. +- Llama 3.x models route to `us-central1`. + +### 8.3 Extraction algorithm + +Primary strategy: + +1. Optimize/truncate long prompts and markdown. +2. Request JSON object output. +3. Validate response with Pydantic `ExtractionResult`. +4. Return answer/references and `strategy='primary_optimized'`. + +Fallback strategy: + +1. Minimal system prompt. +2. Shortened context. +3. Low token budget. +4. Return `strategy='fallback_minimal'`. + +Parsing error handling writes diagnostic JSON logs under `backend/logs/llama_errors/` and attempts to salvage embedded JSON fragments. + +## 9. Macbook provider + +Class: `MacbookLLMClient` + +### 9.1 Configuration + +Uses: + +- `MACBOOK_LLM_BASE_URL` +- optional retry/backoff/timeout env vars; +- `backend/config/macbook_model_policy.json` for allow/deny model filtering. + +The endpoint is Ollama-style: + +- `GET /api/tags` +- `POST /api/generate` + +### 9.2 Queueing + +Macbook calls are serialized through `MacbookRequestQueue`. + +Queue behavior: + +- one background worker; +- FIFO `asyncio.Queue`; +- caller awaits a future; +- failed worker is restarted on next enqueue; +- stats expose total enqueued, processed, pending, and worker state. + +This prevents concurrent requests from overwhelming a local Macbook-hosted model runtime. + +### 9.3 Response cleanup + +`_sanitize_content()` strips reasoning/thinking tags and preserves user-facing content. + +## 10. vLLM provider + +Class: `VLLMClient` + +### 10.1 Configuration + +Uses: + +- `VLLM_BASE_URL` +- optional `VLLM_API_KEY` +- optional `VLLM_MODELS` + +If static models are configured, `fetch_available_models()` uses them. Otherwise it queries `GET /models`. + +### 10.2 Core call + +Calls OpenAI-compatible: + +```text +POST {base_url}/chat/completions +``` + +Payload: + +- `model` +- `messages` +- `max_tokens` +- `temperature` + +Default timeout is 600 seconds. + +If model id starts with `vllm-`, the prefix is stripped before sending to the backend. + +## 11. Cost tracking integration + +`LLMService` records successful calls only. + +Cost tracker uses: + +- provider; +- normalized model key; +- prompt/completion token counts; +- duration; +- optional document/page metadata for document parser costs. + +See [11-auth-security-observability.md](11-auth-security-observability.md) for telemetry details. + +## 12. Error behavior + +| Provider | Error style | +| --- | --- | +| Azure | Returns `success=False` with error/raw details; retries transient failures. | +| Gemini | Returns structured error or fallback partial content on token truncation. | +| Anthropic | Returns error dict for API/JSON parsing failures. | +| Llama | Uses primary/fallback strategies and parsing diagnostics. | +| Macbook | Retries 5xx/HTML/bad-gateway/request exceptions until attempt/total cap. | +| vLLM | Returns error for timeout, non-200, or missing choices. | + +## 13. Related docs + +- [07-extraction-flow.md](07-extraction-flow.md) +- [08-evaluation-flow.md](08-evaluation-flow.md) +- [11-auth-security-observability.md](11-auth-security-observability.md) diff --git a/docs/backend/07-extraction-flow.md b/docs/backend/07-extraction-flow.md new file mode 100644 index 0000000..4e6dfa1 --- /dev/null +++ b/docs/backend/07-extraction-flow.md @@ -0,0 +1,288 @@ +# Entity Extraction Flow Technical Design + +This document describes how the backend extracts structured entity answers from processed documents, attaches reference/bounding-box data, records cost, and persists results into sessions. + +## 1. Scope + +In scope: + +- `POST /api/extract` interface behavior; +- entity request types; +- markdown and figure-context construction; +- LLM provider routing; +- provider response normalization; +- reference and bounding-box matching; +- persistence into `extraction_results`; +- timeout logging and cost/session metrics. + +Out of scope: + +- frontend prompt-template editing; +- model-provider provisioning; +- evaluation of extracted answers, which is covered in [08-evaluation-flow.md](08-evaluation-flow.md). + +## 2. Main files and classes + +| Component | File | Responsibility | +| --- | --- | --- | +| extraction router | `backend/api/extractions/router.py` | HTTP endpoint, per-entity task orchestration, persistence. | +| `ExtractRequest` / `Entity` | `backend/schemas/extractions.py` | API request schemas. | +| `DocumentService` | `backend/services/document/document_service.py` | Markdown/raw-analysis/figure retrieval. | +| `LLMService` | `backend/services/llm/llm_service.py` | Provider dispatch. | +| bbox matchers | `backend/services/document/processors/*/bounding_box_matcher.py` | Reference-to-bbox matching. | +| `SessionService` | `backend/services/session/session_service.py` | Persist extraction results. | +| `ExtractionResult` ORM | `backend/models/extraction.py` | Physical extraction row. | +| `ExtractionResult` schema | `backend/schemas/sessions.py` | Session API extraction result. | + +## 3. API contract + +Endpoint: + +```text +POST /api/extract +``` + +Request body: `ExtractRequest` + +Important fields: + +- `conversion_id`: file hash / document conversion id; +- `session_id`: optional session id for persistence; +- `entities`: list of `Entity` objects; +- `model_type`: provider route, e.g. `azure`, `gemini`, `anthropic`, `llama`, `macbook`, `vllm`; +- `model_id`, `deployment`, `api_version`, provider-specific credentials/config; +- `max_tokens`, `temperature`; +- `processor_used`: optional preferred processor subtree. + +Each `Entity` contains: + +- `name` +- `prompt` +- optional `extracted` +- optional `system_prompt` + +## 4. High-level sequence + +```text +POST /api/extract + -> validate auth dependency + -> load markdown for conversion_id + -> load figure metadata/context when available + -> for each entity: + call LLMService.extract_entities_from_markdown() + normalize provider result + load raw analysis when needed + match references to bounding boxes + build entity result dict + -> persist results to session when session_id provided + -> return extraction result payload +``` + +## 5. Markdown loading + +The router uses `DocumentService.get_markdown_content(conversion_id, processor_used)`. + +Resolution behavior: + +1. Resolve processor via `OrganizedFileService.resolve_processed_processor()`. +2. Try `document.md` in the processed artifact tree. +3. If missing, optionally fall back to `raw_analysis.json` content field. + +If markdown cannot be loaded, extraction fails with a not-found/error response. + +## 6. Figure context construction + +The extraction router can build extra figure context with `_build_figures_context(figures, conversion_id)`. + +Inputs: + +- figure metadata from `DocumentService.get_figures_for_conversion()`; +- figure summaries stored in metadata, when previously generated. + +Purpose: + +- Give text-only extraction models more context about figures and visual content. +- Preserve figure ids/captions/summaries in the prompt context. + +The figure context is appended to or included with document markdown before provider calls, depending on route logic. + +## 7. Per-entity extraction task + +The router creates one async task per requested entity. + +For each entity: + +1. Combine document markdown, figure context, and the entity prompt. +2. Use the entity-level `system_prompt` if present. +3. Call `LLMService.extract_entities_from_markdown()` with provider configuration. +4. Interpret result success/failure. +5. Extract answer text from provider-specific fields. +6. Extract token usage, duration, and raw references from `meta` / provider response. +7. Match references to bounding boxes if possible. +8. Return an entity result object. + +## 8. Provider response handling + +Provider result dictionaries vary. Common extraction answer candidates include: + +- `answer` +- `content` +- `extracted_text` +- provider-specific generated fields + +Common reference candidates include: + +- `references` +- structured JSON references inside `raw` + +Common metadata fields include: + +- `meta.model` +- `meta.deployment` +- `meta.prompt_tokens` +- `meta.completion_tokens` +- `meta.duration` + +The extraction flow treats provider dictionaries as semi-structured and maps them into the stable session extraction schema. + +## 9. Bounding-box matching + +### 9.1 Raw analysis loading + +When references exist or bbox matching is requested, the router reads raw analysis through: + +```python +DocumentService.get_raw_analysis_result(conversion_id, processor_used) +``` + +Then the raw analysis is normalized with `normalize_bbox_format()` or routed to processor-specific matchers. + +### 9.2 Azure matching + +Azure matcher behavior: + +1. Normalize text. +2. Search paragraphs for exact/substring/similarity matches. +3. Fall back to page lines when paragraph match is weak or absent. +4. Extract figure references such as `Figure 1`, `Fig. 2`, etc. +5. Match figure ids to Azure figure metadata. +6. Return paragraph matches, line matches, best match, and figure-enriched matches. + +Returned data can include: + +- matched text; +- similarity score; +- page number; +- polygon/bounding regions; +- paragraph content; +- line content; +- figure id/caption/reference. + +### 9.3 Docling matching + +Docling matcher behavior: + +1. Normalize text. +2. Search paragraphs. +3. Fall back to page-level matching. +4. Extract and validate 8-number polygons where available. + +Docling matching is simpler than Azure matching because Docling raw analysis does not always include line-level structures equivalent to Azure. + +## 10. Persistence + +If `session_id` is provided, each successful or failed entity result can be persisted through: + +```python +SessionService.add_extraction_result_fast(session_id, user_id, result) +``` + +`SessionService` resolves the target document by: + +1. explicit `document_id`, if present; +2. `file_hash`, if present; +3. cached session documents; +4. DB lookup. + +Important multi-document guard: + +- If a session has multiple documents and an extraction result lacks `file_hash`/document identity, the service refuses to guess and returns `False`. + +DB upsert target: + +```text +(document_id, entity_name, model_id) +``` + +Constraint name: + +```text +uq_extraction_doc_entity_model +``` + +## 11. Extraction DB shape + +Persisted `ExtractionResult` ORM fields: + +- `session_id` +- `document_id` +- `entity_name` +- `model_id` +- `extracted_text` +- `bbox_references` +- `status` +- `error_message` +- `extracted_at` +- `prompt_tokens` +- `completion_tokens` +- `duration_ms` +- `cost` + +The session API later converts this into `schemas.sessions.ExtractionResult` with `references` instead of `bbox_references`. + +## 12. Cost and timing + +Provider clients include token and duration metadata when available. `LLMService` records session metrics on successful provider responses. The extraction persistence layer also stores per-extraction token, duration, and cost fields when provided. + +If stored cost is missing or zero, `SessionService._db_to_session()` can recompute estimated cost from token counts and model id using `cost_tracker` and backfill the DB. + +## 13. Timeout and error logging + +`LLMService` wraps provider calls with timeout logging. The extraction router also has a `log_timeout_event()` helper to write extraction timeout details. + +Common error outputs: + +- provider disabled; +- provider API failure; +- timeout; +- markdown/document not found; +- bbox analysis unavailable; +- DB persistence failure. + +Provider/API failures are generally returned as failed entity results rather than aborting the entire batch when possible. + +## 14. Algorithms to preserve + +### 14.1 Per-entity concurrency + +Entities are extracted concurrently so one request can produce multiple entity results faster. Persistence also uses async task-style behavior for entity result writes. + +### 14.2 Figure-context augmentation + +Figure metadata and summaries are converted into text context. This gives text LLMs access to visual analysis without requiring every extraction call to invoke a vision model. + +### 14.3 Reference grounding + +Structured provider references are mapped to parser raw analysis. The design decouples extraction answer generation from visual grounding: providers produce textual references, then backend matchers map those references to bounding boxes. + +### 14.4 Multi-document safety + +`SessionService` avoids cross-document contamination by requiring file/document identity when a session contains multiple documents. + +## 15. Related docs + +- [02-api-surface.md](02-api-surface.md) +- [04-schemas.md](04-schemas.md) +- [05-document-processing.md](05-document-processing.md) +- [06-llm-layer.md](06-llm-layer.md) +- [09-session-sharing-groups.md](09-session-sharing-groups.md) diff --git a/docs/backend/08-evaluation-flow.md b/docs/backend/08-evaluation-flow.md new file mode 100644 index 0000000..f73ec89 --- /dev/null +++ b/docs/backend/08-evaluation-flow.md @@ -0,0 +1,417 @@ +# Evaluation Flow Technical Design + +This document describes LLM-as-a-judge evaluation, DeepEval metric construction, batch evaluation, background job execution, cancellation, persistence, and cost tracking. + +## 1. Scope + +In scope: + +- single extraction evaluation; +- batch evaluation; +- custom metric evaluation; +- built-in metric factories; +- provider adapters for Azure, Vertex/Gemini, and Anthropic Vertex; +- background evaluation jobs; +- cancellation; +- result storage; +- session evaluation persistence; +- judge-call cost tracking. + +Out of scope: + +- frontend evaluation table UI; +- external DeepEval library implementation; +- provider account setup. + +## 2. Main classes and files + +| Component | File | Responsibility | +| --- | --- | --- | +| `EvaluationService` | `backend/services/evaluation/evaluation_service.py` | Evaluation orchestration, metric creation, combined scoring, batch handling. | +| `EvaluationResultStorage` | `backend/services/evaluation/storage/result_storage.py` | JSON file storage for evaluation outputs. | +| metric factories | `backend/services/evaluation/metrics/*.py` | Create built-in/custom GEval metrics. | +| adapters | `backend/services/evaluation/adapters/*.py` | Wrap provider models for DeepEval. | +| job queue | `backend/services/evaluation/job_queue.py` | Async background evaluation job management. | +| API router | `backend/api/evaluations/router.py` | Synchronous evaluation endpoints. | +| jobs router | `backend/api/evaluations/jobs.py` | Background job submit/poll/cancel endpoints. | +| `EvalJobRecord` | `backend/models/eval_job.py` | Cross-worker job status persistence. | + +## 3. API endpoints + +Synchronous evaluation endpoints: + +```text +POST /api/evaluations/evaluate +POST /api/evaluations/evaluate/batch +POST /api/evaluations/evaluate/custom +POST /api/evaluations/cancel +GET /api/evaluations/results/{evaluation_id} +GET /api/evaluations/results +GET /api/evaluations/metrics/info +``` + +Background job endpoints: + +```text +POST /api/evaluations/jobs +GET /api/evaluations/jobs/{job_id} +POST /api/evaluations/jobs/{job_id}/cancel +``` + +## 4. Evaluation model creation + +`EvaluationService.create_evaluation_model()` supports: + +| Provider id | Adapter | +| --- | --- | +| `azure_openai` | `AzureOpenAIDeepEvalModel` | +| `vertex_ai` | `VertexAIDeepEvalModel` | +| `anthropic` | `AnthropicVertexDeepEvalModel` | + +Default models: + +- Vertex/Gemini: `EVAL_DEFAULT_GEMINI_MODEL` or `gemini-2.5-flash`. +- Anthropic: `EVAL_DEFAULT_ANTHROPIC_MODEL` or `claude-sonnet-4-5@20250929`. + +Unsupported providers raise `ValueError`. + +## 5. Built-in metric factories + +Metric factory map: + +| Metric key | Factory | Evaluation focus | +| --- | --- | --- | +| `correctness` | `CorrectnessMetricFactory` | Factual accuracy against ground truth. | +| `completeness` | `CompletenessMetricFactory` | Coverage of expected key information. | +| `relevance` | `RelevanceMetricFactory` | Focus on the requested entity/task. | +| `safety` | `SafetyMetricFactory` | PII, toxicity, bias, unsupported/harmful claims. | + +All built-in metrics are GEval metrics with async mode enabled. + +Correctness and completeness require expected output. If no expected output is provided, the service skips those metrics. + +## 6. Custom metrics + +`CustomMetricFactory.create()` accepts: + +- metric name; +- evaluation steps; +- evaluation model; +- evaluation params; +- threshold; +- strict mode. + +Default evaluation params are input, actual output, and expected output. + +## 7. Combined scoring algorithm + +`EvaluationService._evaluate_combined()` is the preferred scoring path for multiple metrics. + +Purpose: + +- score all metrics in one judge-model call; +- reduce repeated prompt context; +- lower latency/cost compared with one call per metric. + +Algorithm: + +1. Build a prompt containing extraction task, actual output, optional expected output, and one criteria block per metric. +2. Ask the judge model for a strict JSON object with metric entries. +3. Parse output using multiple recovery strategies: + - direct `json.loads()`; + - strip Markdown code fences; + - extract outermost JSON block with regex; + - salvage per-metric entries with regex. +4. Clamp scores to `[0, 1]`. +5. Set metric score/reason/success fields. +6. Return list of metric result dictionaries. + +If combined scoring fails, `evaluate_extraction()` falls back to per-metric evaluation with `asyncio.gather()`. + +## 8. Single extraction evaluation + +`EvaluationService.evaluate_extraction()` inputs: + +- `entity_name` +- `extraction_prompt` +- `actual_output` +- optional `expected_output` +- optional `metrics` +- `provider` +- `threshold` +- `strict_mode` +- optional `custom_evaluation_steps` +- optional `session_id` +- provider-specific model kwargs + +Flow: + +```text +create evaluation id + -> create evaluation model + -> decide metric set + -> skip metrics requiring missing expected output + -> build DeepEval LLMTestCase + -> try combined scoring + -> fallback to per-metric concurrent scoring if needed + -> collect adapter call history + -> estimate and record cost + -> compute aggregate score and all_passed + -> return result dict +``` + +Success result includes: + +- `evaluation_id` +- `entity_name` +- `provider` +- `model` +- `timestamp` +- `evaluation_time` +- `evaluation_cost` +- `metrics` +- `aggregate_score` +- `all_passed` +- `threshold` +- `strict_mode` +- `status='success'` + +Error result includes: + +- evaluation id; +- entity/provider/timestamp; +- `status='error'`; +- error text. + +## 9. Batch evaluation + +`EvaluationService.evaluate_multiple_extractions()` accepts a list of extraction dictionaries and runs them in chunks. + +Behavior: + +1. Clear stale cancellation flag for the session. +2. Default metrics to all four built-ins if omitted. +3. Process extraction mini-batches with `asyncio.gather()`. +4. Check session cancellation between batches. +5. Fill remaining results with `status='cancelled'` if cancelled. +6. Compute batch summary statistics. +7. Save batch result to `EvaluationResultStorage`. + +Batch result includes: + +- `batch_id` +- `timestamp` +- `batch_time` +- `total_evaluations` +- `successful_evaluations` +- `failed_evaluations` +- `avg_aggregate_score` +- `all_passed` +- `threshold` +- `provider` +- `results` + +## 10. Cancellation + +`EvaluationService` uses a process-local `CANCELLED_SESSIONS` set. + +Helpers: + +- `cancel_session(session_id)` +- `clear_cancelled_session(session_id)` +- `is_session_cancelled(session_id)` + +`POST /api/evaluations/cancel` reads `X-Session-Id` and marks the session as cancelled. + +Limitations: + +- This cancellation set is process-local. +- Background job cancellation also uses `EvalJobRecord` DB state for cross-worker cancellation. + +## 11. Provider adapters + +### 11.1 Azure adapter + +`AzureOpenAIDeepEvalModel` wraps `AzureChatOpenAI`. + +Configuration order: + +1. `backend/core/secrets.toml` +2. constructor args +3. environment variables + +Concurrency: + +- module-level semaphore of 35. + +Important behavior: + +- Extracts JSON from prose/code fences. +- Records call history with duration and token usage. +- Supports both sync and async `generate` methods required by DeepEval. + +### 11.2 Vertex adapter + +`VertexAIDeepEvalModel` wraps `ChatVertexAI`. + +Behavior: + +- Requires GCP project. +- Sets safety settings to `BLOCK_NONE` for major harm categories. +- Uses module-level semaphore of 25. +- Retries rate-limit/server errors with exponential backoff and jitter. +- Records call history. + +### 11.3 Anthropic Vertex adapter + +`AnthropicVertexDeepEvalModel` wraps Anthropic Vertex clients. + +Behavior: + +- Defaults to Claude Sonnet model. +- Uses module-level semaphore of 8. +- Sends user prompt through Anthropic messages API. +- Extracts usage objects with attribute access. +- Records call history. + +## 12. Background evaluation jobs + +File: `backend/services/evaluation/job_queue.py` + +### 12.1 Dataclasses + +| Dataclass/class | Purpose | +| --- | --- | +| `EvalTask` | One entity output to evaluate. | +| `ProviderConfig` | Judge provider/model configuration. | +| `TaskResult` | One task/provider evaluation result. | +| `EvalJob` | Job state and serialization. | +| `_JobStatusProxy` | DB-backed read-only job status for non-local jobs. | + +### 12.2 Job lifecycle + +```text +create_job() + -> EvalJob(job_id, tasks, providers, session/user id) + -> submit_job() + -> store in _JOBS + -> create EvalJobRecord asynchronously + -> start _process_job(job) background task + +_process_job() + -> mark running and sync DB + -> flatten tasks x providers + -> compute per-job concurrency + -> run work items with global and per-job semaphores + -> mark completed/cancelled/failed + -> final DB sync +``` + +### 12.3 Concurrency + +- Global LLM concurrency cap: `GLOBAL_LLM_CONCURRENCY = 30`. +- `_LLM_SEMAPHORE` protects total concurrent judge calls. +- Per-job concurrency is computed as: + +```text +max(1, GLOBAL_LLM_CONCURRENCY // active_running_jobs) +``` + +Each individual evaluation is wrapped in `asyncio.wait_for(..., timeout=60.0)`. + +### 12.4 Job persistence + +`EvalJobRecord` stores: + +- job id; +- session id; +- user id; +- status; +- progress/total; +- results/errors; +- top-level error; +- created/completed timestamps. + +`get_job(job_id)` checks in-memory `_JOBS` first, then falls back to DB status through `_JobStatusProxy`. + +### 12.5 Job cancellation + +`cancel_job(job_id)`: + +- if job is local, sets `cancelled=True` and cancels live asyncio tasks; +- if job is not local, marks the DB record cancelled. + +## 13. Session persistence of evaluation results + +During background jobs, successful task results are converted into `schemas.sessions.EvaluationScore` and `schemas.sessions.EvaluationResult`, then persisted through: + +```python +SessionService.add_evaluation_result_fast(...) +``` + +`SessionService` matches the evaluation to an extraction by: + +- entity name; +- model id; +- document id or file hash when available; +- paragraph-summary fallback for `__paragraph_summary__`. + +Evaluation DB upsert target: + +```text +(extraction_result_id, metric, judge_model) +``` + +Constraint name: + +```text +uq_eval_extraction_metric_judge +``` + +## 14. Result storage + +`EvaluationResultStorage` saves JSON files under: + +```text +backend/output/evaluations/{evaluation_id}.json +``` + +Methods: + +- `save(evaluation_id, result)` +- `get(evaluation_id)` +- `list_all()` sorted by timestamp descending +- `delete(evaluation_id)` +- `get_storage_path()` + +This storage is separate from normalized DB persistence of per-extraction evaluation scores. + +## 15. Cost tracking + +Evaluation service records judge-call costs from adapter call history. + +For each adapter call: + +1. estimate cost with `cost_tracker.estimate_call_cost()`; +2. record call with `cost_tracker.record_call()` when session id exists; +3. sum costs into `evaluation_cost`. + +The returned evaluation result includes total evaluation cost and evaluation time. + +## 16. Error classification + +The job queue includes helpers for classifying errors: + +- `_is_timeout_error()` +- `_is_rate_limit_error()` +- `_is_non_retryable_error()` + +Retry delay constants are defined for normal and rate-limit failures, but current `MAX_ATTEMPTS` is 1 for single job tasks. + +## 17. Related docs + +- [02-api-surface.md](02-api-surface.md) +- [04-schemas.md](04-schemas.md) +- [06-llm-layer.md](06-llm-layer.md) +- [09-session-sharing-groups.md](09-session-sharing-groups.md) +- [appendices/risks-assumptions-testing.md](appendices/risks-assumptions-testing.md) diff --git a/docs/backend/09-session-sharing-groups.md b/docs/backend/09-session-sharing-groups.md new file mode 100644 index 0000000..5e3bdb6 --- /dev/null +++ b/docs/backend/09-session-sharing-groups.md @@ -0,0 +1,413 @@ +# Sessions, Sharing, and Groups Technical Design + +This document describes workflow sessions, DB-to-API conversion, restore-view generation, shared sessions, group membership, and authorization rules. + +## 1. Scope + +In scope: + +- `SessionService` session lifecycle; +- `SQLAlchemyDBService` session/document/extraction/evaluation persistence operations; +- session schema conversion; +- restore-view construction; +- group CRUD and membership; +- session sharing with groups. + +Out of scope: + +- frontend session-history UI; +- template-specific group sharing, covered in [10-template-system.md](10-template-system.md). + +## 2. Main classes and files + +| Component | File | Responsibility | +| --- | --- | --- | +| `SessionService` | `backend/services/session/session_service.py` | High-level workflow/session orchestration. | +| `SQLAlchemyDBService` | `backend/services/database/sqlalchemy_db_service.py` | Persistence boundary for sessions, docs, extractions, evaluations, metrics, sharing. | +| `GroupService` | `backend/services/groups/group_service.py` | Group and membership business logic. | +| session schemas | `backend/schemas/sessions.py` | API-facing session aggregate models. | +| session router | `backend/api/sessions/router.py` | HTTP session endpoints. | +| group router | `backend/api/groups/router.py` | HTTP group endpoints. | +| `AppSession` ORM | `backend/models/app_session.py` | Workflow session table. | +| `Group`, `UserGroup` ORM | `backend/models/group.py` | Collaboration tables. | + +## 3. Session lifecycle + +### 3.1 Create session + +Endpoint: + +```text +POST /api/sessions +``` + +`SessionService.create_session()` flow: + +1. Convert optional `SessionConfiguration` into a dict. +2. Create `AppSession` through `SQLAlchemyDBService.create_session()`. +3. For each optional `SessionDocument`, create a `Document` DB row. +4. If all requested document inserts fail, delete the orphaned session and raise an error. +5. Return a Pydantic `Session` aggregate. + +Partial document insert success is allowed. Failed document rows are logged and skipped. + +### 3.2 Get session + +Endpoint: + +```text +GET /api/sessions/{session_id} +``` + +`SQLAlchemyDBService.get_session()` loads: + +- the `AppSession` row for the requesting user; +- associated `Document` rows; +- associated `ExtractionResult` rows; +- associated `EvaluationResult` rows. + +Then `SessionService._db_to_session()` converts raw DB dictionaries into Pydantic models. + +If the session is not owned by the user, the service returns `None`. The router can also attempt shared-session fallback depending on endpoint path. + +### 3.3 List sessions + +Endpoint: + +```text +GET /api/sessions +``` + +`SQLAlchemyDBService.list_sessions()` returns session summary rows sorted by `updated_at` descending and enriches each with: + +- `document_count` +- `document_names` +- `extraction_count` +- `study_type` from JSON configuration + +`SessionService.list_sessions()` converts these into `SessionSummary` objects. + +### 3.4 Update session + +Endpoint: + +```text +PATCH /api/sessions/{session_id} +``` + +`SessionService.update_session()` supports three categories: + +1. Basic fields: `name`, `status`, `last_step`, `configuration`. +2. Config merges: `evaluation_config`, `files_config`. +3. Heavy updates: documents, extraction results, evaluation results. + +Important merge behavior: + +- `evaluation_config` is merged into the existing config dict. +- `files_config` is deep-merged per file id so existing per-file config is preserved. + +Heavy update behavior: + +- New documents are inserted only if their `file_hash` is not already present. +- Extraction results are matched to documents by `file_hash` or document id. +- Evaluation results are matched to extraction results by entity/model/document identity. + +If the update is config-only, the service returns a lightweight session object without reloading all child rows. If heavy updates occurred, it returns the full session. + +### 3.5 Delete session + +Endpoint: + +```text +DELETE /api/sessions/{session_id} +``` + +Deletes only when both session id and user id match. + +## 4. DB-to-session conversion + +`SessionService._db_to_session()` is the central conversion function. + +### 4.1 Document conversion + +For each DB document: + +- if parse cost is missing/zero, estimate it from processor/page count/duration; +- backfill the DB when a cost can be computed; +- build `SessionDocument` with id, file hash, filename, processor, cost, page/figure/table counts. + +### 4.2 Extraction conversion + +For each DB extraction: + +- if extraction cost is missing/zero, estimate it from token counts and model id; +- infer provider from model id; +- backfill cost where possible; +- map `bbox_references` to API field `references`; +- build `schemas.sessions.ExtractionResult`. + +### 4.3 Evaluation conversion + +DB evaluation rows are grouped by: + +```text +(document_id, entity_name, model_id) +``` + +Each group becomes one `schemas.sessions.EvaluationResult` with a list of `EvaluationScore` entries. + +This preserves per-document evaluation granularity in multi-document sessions. + +## 5. Extraction persistence rules + +`SessionService.add_extraction_result_fast()` resolves the target document before writing. + +Resolution order: + +1. Existing cached session documents. +2. DB document lookup. +3. Match by `file_hash` when provided. +4. If only one document exists, use it. +5. If multiple documents exist and no file identity is provided, return `False` instead of guessing. + +This guard prevents cross-document contamination. + +Persistence uses `SQLAlchemyDBService.upsert_extraction_result()` and the database unique constraint: + +```text +(document_id, entity_name, model_id) +``` + +## 6. Evaluation persistence rules + +`SessionService.add_evaluation_result_fast()`: + +1. loads extraction results for the session; +2. resolves target document from `document_id` or `file_hash`; +3. matches extraction by entity name, model id, and optional document id; +4. supports a special `__paragraph_summary__` fallback; +5. upserts each metric score through DB service. + +Special human-score behavior: + +- A human-score update can apply to all existing metrics for a judge model. +- If no score list exists but a human score is provided, the service updates existing evaluations or creates a `human_evaluation` placeholder. + +## 7. Restore-view construction + +Endpoint: + +```text +GET /api/sessions/{session_id}/restore-view +GET /api/sessions/shared/{session_id}/restore-view +``` + +`SessionService.build_restore_view()`: + +1. merges `session.configuration.files_config` with top-level `session.files_config`; +2. resolves each document's processor; +3. calls `OrganizedFileService.build_document_view()` for each document; +4. returns a canonical frontend payload. + +Returned top-level shape: + +```json +{ + "fileId": "primary-file-hash", + "conversionId": "primary-file-hash", + "processorUsed": "azure_doc_intelligence", + "uploadedFiles": [] +} +``` + +The first uploaded file is treated as the primary file. + +## 8. Session sharing + +### 8.1 Share session + +Endpoint: + +```text +POST /api/sessions/{session_id}/share +``` + +`SessionService.share_session()` first verifies the requester belongs to the target group using `SQLAlchemyDBService.get_user_group_ids()`. + +Then `SQLAlchemyDBService.share_session()` updates the session fields: + +- `shared_with_group_id` +- `shared_by` +- `shared_at` + +Only the owning user can share the session. + +### 8.2 Unshare session + +Endpoint: + +```text +DELETE /api/sessions/{session_id}/share +``` + +Clears share fields on an owned session. + +### 8.3 List shared sessions + +Endpoint: + +```text +GET /api/sessions/shared/list +``` + +Flow: + +1. Find groups the user belongs to. +2. Query sessions shared with those group ids. +3. Exclude sessions owned by the same user. +4. Enrich with group display name and sharer display name. +5. Return `SessionSummary` list. + +### 8.4 Shared session read + +A user can read a shared session when: + +- `AppSession.shared_with_group_id` is set; +- the requesting user has a `UserGroup` row for that group. + +Role does not matter for session shared-view access; membership is enough. + +## 9. Group lifecycle + +### 9.1 Create group + +Endpoint: + +```text +POST /api/groups +``` + +`GroupService.create_group()`: + +1. inserts a `Group` row with `created_by=user_id`; +2. inserts a `UserGroup` row for the creator with `role='owner'`; +3. returns the group dict. + +The owner membership is created in the same transaction. + +### 9.2 Get group + +Endpoint: + +```text +GET /api/groups/{group_id} +``` + +Rules: + +- System admin can read any group. +- Non-admin users must be group members. + +Returned group detail includes: + +- group fields; +- `user_role`; +- enriched `members` list. + +### 9.3 List user groups + +Endpoint: + +```text +GET /api/groups +``` + +Returns groups where the user has a membership row. Each row includes: + +- `user_role` +- `member_count` + +### 9.4 Update/delete group + +Update requires: + +- system admin; or +- group admin/owner role. + +Delete requires: + +- system admin; or +- owner role. + +## 10. Membership rules + +### 10.1 Add member + +Endpoint: + +```text +POST /api/groups/{group_id}/members +``` + +Rules: + +- Requester must be admin/owner or system admin. +- New member role `owner` is normalized to `admin`. +- Adding an existing member delegates to role update. + +### 10.2 Update role + +Endpoint: + +```text +PUT /api/groups/{group_id}/members/{user_id} +``` + +Rules: + +- Cannot change to or from owner through this endpoint. +- Requester must be admin/owner unless system admin. +- Only owner can promote someone to admin. + +### 10.3 Remove member + +Endpoint: + +```text +DELETE /api/groups/{group_id}/members/{user_id} +``` + +Rules: + +- Self-removal is allowed unless the user is the only owner. +- System admin can remove members but not owners. +- Non-admin users cannot remove others. +- Owners cannot be removed by this method. + +## 11. Member enrichment + +`GroupService._enrich_members_with_profiles()` bulk-loads `User` rows and adds: + +- `display_name = user.name or user.email` +- `email` +- `avatar_url = user.image` + +Missing users receive `None` profile fields. + +## 12. Session metrics + +`SQLAlchemyDBService.increment_session_metrics()` atomically increments: + +- `total_cost` +- `total_latency` +- `total_calls` + +`CostTracker` calls this from its record path. `SessionService._db_to_session()` can also use stored totals when building session responses. + +## 13. Related docs + +- [03-data-models.md](03-data-models.md) +- [04-schemas.md](04-schemas.md) +- [05-document-processing.md](05-document-processing.md) +- [07-extraction-flow.md](07-extraction-flow.md) +- [08-evaluation-flow.md](08-evaluation-flow.md) diff --git a/docs/backend/10-template-system.md b/docs/backend/10-template-system.md new file mode 100644 index 0000000..e304b60 --- /dev/null +++ b/docs/backend/10-template-system.md @@ -0,0 +1,407 @@ +# Template System Technical Design + +This document describes the prompt template workspace: templates, folders, scopes, versions, immutability, forks, and permissions. + +## 1. Scope + +In scope: + +- template CRUD; +- folder CRUD; +- user/group/global scopes; +- access-control algorithms; +- version snapshots and revert; +- fork and scope-change behavior; +- explicit per-user template permissions. + +Out of scope: + +- frontend template editor UI; +- prompt quality/content strategy. + +## 2. Main classes and files + +| Component | File | Responsibility | +| --- | --- | --- | +| `TemplateService` | `backend/services/templates/template_service.py` | Template CRUD, permissions, versions, forks, scope changes. | +| `FolderService` | `backend/services/templates/folder_service.py` | Folder hierarchy and folder permission checks. | +| template router | `backend/api/templates/router.py` | HTTP API for templates/folders. | +| `PromptTemplate` | `backend/models/template.py` | Main template ORM model. | +| `TemplateVersion` | `backend/models/template.py` | Version snapshot ORM model. | +| `TemplatePermission` | `backend/models/template.py` | Per-user permission ORM model. | +| `TemplateFolder` | `backend/models/template.py` | Folder ORM model. | +| `GroupService` | `backend/services/groups/group_service.py` | Group role checks for group-scoped resources. | + +## 3. Template data model + +`PromptTemplate` stores: + +- `name` +- `description` +- `study_type` +- `scope`: `user`, `group`, or `global` +- `owner_user_id` +- `owner_group_id` +- `system_prompt` +- `entities` JSONB +- `summary_prompt` +- `variables` JSONB +- `is_immutable` +- `tags` +- `is_default` +- `version` +- `folder_id` +- `created_by` +- timestamps + +The `scope` determines default read/edit behavior. + +## 4. Folder data model + +`TemplateFolder` stores: + +- `name` +- `scope` +- `owner_user_id` +- `owner_group_id` +- `parent_id` +- `created_by` +- timestamps + +Folders can be hierarchical through `parent_id`. + +Folder deletion is intentionally non-cascading: the service refuses to delete a folder that contains templates or subfolders. + +## 5. Template creation + +Endpoint: + +```text +POST /api/templates +``` + +`TemplateService.create_template()` rules: + +- `scope` must be `user`, `group`, or `global`. +- `group` scope requires `owner_group_id`. +- For group scope, requester must have role `member`, `admin`, or `owner` in that group. +- User-scope templates set `owner_user_id=user_id`. +- Group-scope templates set `owner_group_id`. +- New templates start at `version=1`. +- `created_by` is the requesting user. + +Global scope is allowed by this service without a special admin guard in current code. + +## 6. Template read/list + +### 6.1 Get one template + +Endpoint: + +```text +GET /api/templates/{template_id} +``` + +`TemplateService.get_template()`: + +1. loads template by id; +2. checks `_can_read()`; +3. returns `None` if unreadable; +4. adds `can_edit` and `is_owner` flags to response. + +### 6.2 List templates + +Endpoint: + +```text +GET /api/templates +``` + +Supports filters: + +- `scope` +- `study_type` +- search across name/description +- tags + +List behavior: + +1. query candidate templates; +2. load current user's groups; +3. apply `_can_read()` in memory; +4. annotate `can_edit`, `is_owner`, and group name where relevant; +5. apply tag filter using any-match semantics. + +Because access checks are applied after the DB query, query result count can be larger than final response count. + +## 7. Template update and versioning + +Endpoint: + +```text +PUT /api/templates/{template_id} +``` + +`TemplateService.update_template()`: + +1. requires readable template; +2. requires `_can_edit()`; +3. snapshots current content into `TemplateVersion` before mutation; +4. updates allowed fields; +5. increments `version`; +6. updates timestamp; +7. returns updated template. + +Allowed update fields: + +- `name` +- `description` +- `study_type` +- `system_prompt` +- `entities` +- `summary_prompt` +- `variables` +- `tags` +- `is_immutable` +- `folder_id` + +If no valid update fields are provided, the current template is returned unchanged. + +## 8. Version history and revert + +Endpoints: + +```text +GET /api/templates/{template_id}/versions +POST /api/templates/{template_id}/revert/{version} +``` + +Version history: + +- requires read permission; +- returns snapshots ordered by version descending. + +Revert behavior: + +1. requires read and edit permission; +2. loads requested `TemplateVersion`; +3. calls `update_template()` with snapshot fields; +4. creates a new version snapshot as part of the update path. + +Reverting does not reuse the old version number; it creates a new current version. + +## 9. Forking + +Endpoint: + +```text +POST /api/templates/{template_id}/fork +``` + +`TemplateService.fork_template()`: + +1. loads source through `get_template()` so read permission applies; +2. creates a new user-scope copy; +3. defaults name to `Copy of {source_name}` unless provided; +4. sets `is_immutable=False`. + +Forked templates are always personal/user scoped. + +## 10. Scope changes + +Endpoint: + +```text +PUT /api/templates/{template_id}/scope +``` + +`TemplateService.change_scope()` validates the target scope and enforces rules based on old scope. + +### 10.1 Old user scope + +- Requester must own the template. +- Moving to group scope requires membership/admin/owner role in target group. + +### 10.2 Old group scope + +- Requester must be admin/owner of current group. +- Moving to another group requires membership/admin/owner in target group. + +### 10.3 Old global scope + +- Requester must be `created_by`. + +### 10.4 Ownership fields + +The service updates ownership fields according to target scope: + +- user scope: `owner_user_id=user_id`, `owner_group_id=None`; +- group scope: `owner_user_id=None`, `owner_group_id=target group`; +- global scope: both owner fields cleared. + +## 11. Immutability + +Endpoint: + +```text +PUT /api/templates/{template_id}/immutable +``` + +Rules: + +- User-scope template owner can set immutability. +- Group-scope admin/owner can set immutability. +- Global scope currently returns `None` for this operation. + +`_can_edit()` always denies edits when `is_immutable=True`, regardless of other permissions. + +## 12. Explicit permissions + +Endpoints: + +```text +GET /api/templates/{template_id}/permissions +POST /api/templates/{template_id}/permissions +DELETE /api/templates/{template_id}/permissions/{user_id} +``` + +`TemplatePermission` fields: + +- `template_id` +- `user_id` +- `can_read` +- `can_write` +- `granted_by` + +Permissions can be managed by: + +- user-scope template owner; +- group-scope admin/owner. + +Global scope is disallowed for explicit permission operations. + +Permission upsert uses unique constraint: + +```text +(template_id, user_id) +``` + +## 13. Access-control algorithms + +### 13.1 `_can_read(template, user_id)` + +Algorithm: + +1. Global scope is readable by anyone. +2. User scope is readable by owner. +3. Group scope is readable by group members. +4. Otherwise, explicit `TemplatePermission.can_read` can grant read access. + +### 13.2 `_can_edit(template, user_id)` + +Algorithm: + +1. If immutable, deny. +2. If explicit permission exists, use `can_write`. +3. User scope: owner can edit. +4. Group scope: member/admin/owner can edit by default. +5. Global scope: creator can edit. + +Important implication: + +- Explicit user permission is checked before default scope edit rules, but immutability always wins. + +### 13.3 `_is_owner(template, user_id)` + +Algorithm: + +- User scope: owner user id matches. +- Group scope: group admin/owner counts as owner. +- Global scope: creator counts as owner. + +## 14. Folder operations + +### 14.1 List folders + +Endpoint: + +```text +GET /api/templates/folders +``` + +Filters: + +- scope; +- owner user/group; +- parent id. + +Top-level folders are selected with `parent_id is None`. + +### 14.2 Create folder + +Endpoint: + +```text +POST /api/templates/folders +``` + +Rules: + +- Name must be non-empty. +- User scope is manageable by the user. +- Group scope requires admin/owner role in owning group. +- Global scope is currently allowed by `_can_manage_folder()`. +- Parent folder must have the same scope. +- Group parent folder must have the same owning group. + +### 14.3 Rename folder + +Endpoint: + +```text +PATCH /api/templates/folders/{folder_id} +``` + +Allowed if: + +- user can manage the folder scope; or +- user originally created the folder. + +### 14.4 Delete folder + +Endpoint: + +```text +DELETE /api/templates/folders/{folder_id} +``` + +Allowed if: + +- user can manage folder scope; or +- user originally created the folder. + +Deletion is refused when: + +- any template has `folder_id` equal to the folder; +- any subfolder has `parent_id` equal to the folder. + +Return shape on success: + +```json +{"deleted": "folder-id"} +``` + +## 15. Risks and implementation notes + +- Global-scope creation and global folder management are permissive in current service code. +- `PromptTemplate.folder_id` is a logical association but no ORM FK is declared. +- Group-scope edit permission allows any member/admin/owner by default, unless immutable or explicit permission logic changes. +- Tag filtering is in-memory and uses any-match semantics. +- Version snapshots are created before update, so snapshot version represents the previous state. + +## 16. Related docs + +- [03-data-models.md](03-data-models.md) +- [04-schemas.md](04-schemas.md) +- [09-session-sharing-groups.md](09-session-sharing-groups.md) +- [11-auth-security-observability.md](11-auth-security-observability.md) diff --git a/docs/backend/11-auth-security-observability.md b/docs/backend/11-auth-security-observability.md new file mode 100644 index 0000000..973bc91 --- /dev/null +++ b/docs/backend/11-auth-security-observability.md @@ -0,0 +1,382 @@ +# Auth, Security, and Observability Technical Design + +This document describes authentication, authorization boundaries, security-sensitive behaviors, logging, metrics, tracing, and cost/session telemetry. + +## 1. Scope + +In scope: + +- Better Auth session validation in FastAPI; +- auth proxy behavior; +- CORS configuration; +- service-level authorization boundaries; +- file/path safety considerations; +- logging and request IDs; +- Prometheus/OpenTelemetry/Loki hooks; +- cost/session telemetry. + +Out of scope: + +- Better Auth sidecar internal TypeScript implementation; +- OAuth provider setup details; +- cloud IAM policy design. + +## 2. Authentication model + +The backend does not validate JWTs. It validates Better Auth sessions by looking up session tokens in PostgreSQL. + +Main file: + +```text +backend/core/auth.py +``` + +### 2.1 `get_current_user(request)` + +Flow: + +```text +extract token + -> query AuthSession joined to User + -> reject missing session + -> reject expired session + -> optional ALLOWED_EMAILS allowlist check + -> return user dict +``` + +Accepted token locations: + +1. `Authorization: Bearer ` header. +2. `better-auth.session_token` cookie fallback. + +The code prefers the header because Better Auth v1.2+ hashes tokens before storing them in the DB, and the frontend may send the DB/hash token from the get-session API. + +Returned user shape: + +```json +{ + "id": "user-id", + "email": "user@example.com", + "name": "Display Name", + "image": "avatar-url", + "is_admin": false +} +``` + +Failure behavior: + +- no token: HTTP 401; +- invalid token: HTTP 401; +- expired token: HTTP 401; +- email not allowed: HTTP 403; +- unexpected auth error: HTTP 401. + +### 2.2 `get_optional_user(request)` + +Returns `None` instead of raising for auth failure. Used by endpoints that can work with optional authentication. + +## 3. Auth proxy + +File: + +```text +backend/api/auth/proxy.py +``` + +Purpose: + +```text +/api/auth/{path:path} -> Better Auth sidecar /api/auth/{path} +``` + +Important behavior: + +- Auth proxy router is registered first in `main.py`. +- Forwards methods such as GET, POST, PUT, PATCH, DELETE, OPTIONS. +- Passes through `Set-Cookie` and redirect responses. +- Adds forwarded host/proto headers. +- Strips hop-by-hop headers. +- Can enforce email allowlist behavior for session lookup. + +This lets production route all auth traffic through the same public frontend/backend origin without nginx. + +## 4. Authorization boundaries + +Authentication only identifies the user. Authorization is mostly enforced in service classes. + +| Area | Authorization owner | +| --- | --- | +| Sessions | `SessionService`, `SQLAlchemyDBService` filter by `user_id`; shared sessions check group membership. | +| Groups | `GroupService` role checks and system-admin bypass. | +| Templates | `TemplateService` scope, owner, group role, explicit permission, immutability checks. | +| Folders | `FolderService` scope/group/creator checks. | +| Files/documents | File hash access is less strongly permission-scoped; user document listing uses DB user association. | +| Evaluation jobs | Job records include user/session ids, but in-memory polling primarily uses job id. | + +## 5. CORS + +File: + +```text +backend/core/middleware.py +``` + +`setup_cors(app)` reads: + +```text +CORS_ALLOWED_ORIGINS +``` + +Behavior: + +- If unset, defaults to `*` for local development. +- If set, splits comma-separated origins. +- Allows credentials, all methods, and all headers. + +Production should set explicit allowed origins. + +## 6. Configuration and secrets loading + +### 6.1 `main.load_secrets_to_env()` + +Loads TOML secrets from candidate paths and maps sections/keys to uppercase environment variables. + +Special case: + +- `Macbook.macbook_llm_base_url` -> `MACBOOK_LLM_BASE_URL` + +### 6.2 `core.config.load_config()` + +Loads provider-specific config from `backend/core/secrets.toml`. + +Supported sections include: + +- `azure_openai` +- `azure_doc_intelligence` +- `vertex_ai` +- `anthropic` + +It also supports `azure_openai.models` as a multi-model JSON list stored in `AZURE_OPENAI_MODELS`. + +Google credentials are detected from a service-account JSON file under `backend/core/` and mapped to `GOOGLE_APPLICATION_CREDENTIALS`. + +## 7. File and path safety + +Important safety mechanisms: + +- Uploaded files are addressed by SHA-256 content hash, not arbitrary user paths. +- Blob paths are generated by service code under `global/{hash}/...`. +- Processed artifacts are addressed by known relative paths such as `document.md`, `metadata.json`, `raw_analysis.json`, `figures/*`, and `tables/*`. +- Figure/table-serving endpoints validate filenames before reading artifact bytes. +- Text persisted to PostgreSQL is sanitized by `sanitize_text()` in DB service paths to remove null bytes and unsupported control characters. + +Important caveat: + +- Several document endpoints operate by file hash/document id and should be reviewed carefully before making files publicly guessable or exposing hashes outside authenticated contexts. + +## 8. Structured logging + +File: + +```text +backend/core/logging_config.py +``` + +`setup_logging()` configures: + +- structlog JSON rendering; +- stdout handler; +- file handler at `backend/output/logs/app.log`; +- optional Loki handler when `LOKI_URL` is configured. + +Noisy libraries are reduced to warning level: + +- `httpx` +- `httpcore` +- `urllib3` +- `uvicorn.access` + +## 9. Request observability middleware + +`main.create_app()` installs an HTTP middleware that: + +1. creates a 12-character request id; +2. clears and binds structlog context variables; +3. measures duration; +4. logs at: + - error for HTTP 500+; + - warning for HTTP 400+; + - info for success; +5. attaches `X-Request-Id` response header. + +This is the primary per-request logging path. + +## 10. Global exception handling + +`main.create_app()` registers a global exception handler for `Exception`. + +Behavior: + +- logs type, method, path, and traceback; +- returns HTTP 500 with: + +```json +{"detail": ""} +``` + +Expected API errors should still use `HTTPException` in routers/services. + +## 11. Prometheus metrics + +`main.create_app()` attempts to install `prometheus_fastapi_instrumentator`. + +If available: + +```text +GET /metrics +``` + +is exposed outside the OpenAPI schema. + +`CostTracker` also optionally emits Prometheus counters/histograms when `prometheus_client` is installed: + +- token counter by provider/model/token type; +- cost counter in cents; +- duration histogram. + +## 12. OpenTelemetry tracing + +`main._setup_otel(app)` enables tracing when: + +```text +OTLP_ENDPOINT +``` + +is set. + +Behavior: + +- creates `TracerProvider` with service name `summarization-backend`; +- sends spans through OTLP HTTP exporter to `{OTLP_ENDPOINT}/v1/traces`; +- instruments FastAPI app. + +Failure to configure tracing is warning-logged and non-fatal. + +## 13. Browser trace proxy + +Endpoint: + +```text +POST /api/telemetry/traces +``` + +The server router can proxy browser OTLP trace payloads to the configured tracing backend. This keeps browser instrumentation from needing direct access to the telemetry backend. + +## 14. Cost tracking + +File: + +```text +backend/services/telemetry/cost_tracker.py +``` + +Main classes: + +- `CallMetric` +- `BatchMetric` +- `SessionMetrics` +- `CostTracker` + +### 14.1 Pricing config + +Pricing is loaded from: + +```text +backend/config/pricing.json +``` + +Optional override: + +```text +PRICING_JSON_OVERRIDE +``` + +Overrides are JSON and merged into the pricing map. + +### 14.2 Cost algorithm + +`CostTracker._compute_cost()` supports: + +- token cost per million tokens; +- token cost per thousand tokens; +- page cost for document parsing; +- compute cost per minute for local/self-hosted runtimes. + +Model/provider normalization handles Azure, Vertex/Gemini, Claude, Llama, Docling, Azure Document Intelligence, Macbook, and vLLM-style ids. + +### 14.3 Recording calls + +`record_call()` updates in-memory session aggregate: + +- `total_cost` +- `total_latency` +- `total_calls` +- list of calls + +It also: + +- emits Prometheus metrics when available; +- updates DB session metrics through `SQLAlchemyDBService.increment_session_metrics()`. + +The DB write is scheduled through an executor when an event loop is running so telemetry does not block the async request path. + +### 14.4 Batch metrics + +`record_batch()` stores `BatchMetric` under the session state. + +### 14.5 Restore and clear + +- `load_session_metrics_from_db(session_id)` reconstructs aggregate metrics from DB totals. +- `clear_session(session_id)` clears in-memory metrics and resets DB totals. + +## 15. Server metrics/config endpoints + +Router file: + +```text +backend/api/server/router.py +``` + +Important endpoints: + +- `/api/server/health` +- `/api/server-config` +- `/api/models` +- `/api/server/session-metrics` +- `/api/server/session-metrics/load` +- `/api/server/batch-metrics` +- `/api/server/document-metrics` +- `/api/server/logs` + +These endpoints expose operational health, provider availability, model catalog, session metrics, document metrics, and recent logs. + +## 16. Security risks and mitigations + +| Risk | Current mitigation | Remaining concern | +| --- | --- | --- | +| Unauthenticated API access | Most routers use `Depends(get_current_user)`. | Some utility endpoints are intentionally unauthenticated; review before public deployment. | +| Token misuse | Sessions are looked up in DB and expiry checked. | Header/cookie token behavior depends on Better Auth token hashing mode. | +| Unauthorized session reads | Session queries filter by `user_id`; shared reads require group membership. | File hash endpoints should be reviewed if hashes leak. | +| Group privilege escalation | `GroupService` protects owner/admin transitions and only-owner removal. | System-admin behavior should be audited when admin assignment changes. | +| Template unauthorized edits | `TemplateService` checks scope, owner, group role, explicit permissions, and immutability. | Global-scope operations are permissive in current code. | +| Path traversal for artifacts | Service-generated blob paths and filename validation for figure/table endpoints. | Keep all future artifact reads on service-generated relative paths. | +| PostgreSQL null-byte errors | `sanitize_text()` strips null/control chars before DB writes in key paths. | Ensure new text persistence paths use sanitizer. | +| Provider rate limits | Provider clients and evaluation adapters use semaphores/retries/timeouts. | Retry policies vary by provider; background job `MAX_ATTEMPTS` is currently 1. | + +## 17. Related docs + +- [01-architecture.md](01-architecture.md) +- [02-api-surface.md](02-api-surface.md) +- [06-llm-layer.md](06-llm-layer.md) +- [08-evaluation-flow.md](08-evaluation-flow.md) +- [appendices/risks-assumptions-testing.md](appendices/risks-assumptions-testing.md) diff --git a/docs/backend/README.md b/docs/backend/README.md new file mode 100644 index 0000000..fcc30eb --- /dev/null +++ b/docs/backend/README.md @@ -0,0 +1,125 @@ +# Backend Technical Design Document + +This documentation set is a layered Technical Design Document (TDD) for the FastAPI backend in `backend/`. It is written from the implementation and is intended to help reviewers understand how the system is implemented in code: API boundaries, classes, data models, algorithms, infrastructure assumptions, risks, and testing strategy. + +The backend is not documented as one long README because the codebase contains several distinct subsystems: authentication, document processing, LLM routing, entity extraction, evaluation, sessions, groups, templates, storage, telemetry, and deployment hooks. Start here, then follow the module links for details. + +## 1. Introduction + +### 1.1 Problem + +The application needs a backend that can ingest user-uploaded scientific documents, parse them into machine-readable artifacts, run prompt-based entity extraction with multiple model providers, evaluate extraction quality, and persist collaborative workflow state for later restoration and sharing. + +The implementation problem is larger than a single endpoint or service because the backend must coordinate: + +- authenticated access through Better Auth sessions stored in PostgreSQL; +- file upload, deduplication, blob-backed artifact storage, and local cache hydration; +- document parsing through Azure Document Intelligence and Docling; +- model-provider routing across Azure OpenAI, Gemini/Vertex, Anthropic-on-Vertex, Llama MaaS, Macbook-hosted models, and vLLM; +- extraction and figure-reference grounding against document analysis output; +- DeepEval-based evaluation with background job execution and cancellation; +- persistent sessions, groups, template workspaces, and sharing workflows; +- cost, latency, logging, metrics, and deployment-oriented observability. + +### 1.2 Background + +The backend is a FastAPI application launched from `backend/main.py`. It exposes API routers under `backend/api/`, uses SQLAlchemy models under `backend/models/`, Pydantic request/response schemas under `backend/schemas/`, and domain services under `backend/services/`. + +The current architecture replaced an earlier Supabase-based implementation with PostgreSQL, SQLAlchemy, Alembic, and a Better Auth sidecar. The migration context is documented in `../superpowers/migration-guide.md` and the deployment architecture in `../superpowers/plans/dockerize-and-deploy.md`. + +### 1.3 Requirements + +#### 1.3.1 Functional requirements + +- Accept authenticated PDF uploads and deduplicate files by SHA-256 hash. +- Store original files and processed outputs in Azure Blob Storage-backed paths. +- Process documents with Azure Document Intelligence or Docling. +- Generate markdown, raw analysis JSON, metadata, figure images, and table HTML artifacts. +- Return canonical document views for frontend restore and viewer workflows. +- Extract custom entities from document markdown using configured LLM providers. +- Preserve extraction references and bounding boxes where provider output allows it. +- Generate paragraph summaries from extracted entities. +- Evaluate extraction outputs using built-in and custom metrics. +- Support background evaluation jobs with polling and cancellation. +- Persist sessions, documents, extractions, evaluations, metrics, templates, folders, groups, and sharing metadata. +- Allow group-based sharing of sessions and templates. +- Expose model/provider availability and server metrics endpoints. + +#### 1.3.2 Non-functional requirements + +- Authentication: protected endpoints validate Better Auth session tokens against the PostgreSQL `session` table. +- Authorization: services enforce ownership, group membership, roles, and template permissions. +- Scalability: document and evaluation jobs use concurrency controls; production scales by replicas instead of multiple Gunicorn workers per container. +- Cost visibility: LLM and document-processing costs are estimated and attached to session metrics. +- Reliability: provider clients include retry, timeout, and fallback paths where necessary. +- Observability: structured JSON logs, request IDs, Prometheus metrics, optional OpenTelemetry traces, and optional Loki shipping are supported. +- Portability: the backend uses environment variables and `secrets.toml` loading to support local and containerized environments. + +## 2. Technical design map + +| Area | Primary document | Main implementation files | +| --- | --- | --- | +| Backend architecture | [01-architecture.md](01-architecture.md) | `backend/main.py`, `backend/core/*` | +| API contracts | [02-api-surface.md](02-api-surface.md) | `backend/api/*` | +| Physical data model | [03-data-models.md](03-data-models.md) | `backend/models/*`, `backend/alembic/*` | +| Pydantic schemas | [04-schemas.md](04-schemas.md) | `backend/schemas/*` | +| Document processing | [05-document-processing.md](05-document-processing.md) | `backend/services/document/*`, `backend/services/storage/*` | +| LLM provider layer | [06-llm-layer.md](06-llm-layer.md) | `backend/services/llm/*` | +| Entity extraction | [07-extraction-flow.md](07-extraction-flow.md) | `backend/api/extractions/router.py`, provider clients, bbox matchers | +| Evaluation | [08-evaluation-flow.md](08-evaluation-flow.md) | `backend/services/evaluation/*`, `backend/api/evaluations/*` | +| Sessions, groups, sharing | [09-session-sharing-groups.md](09-session-sharing-groups.md) | `backend/services/session/*`, `backend/services/groups/*` | +| Templates and folders | [10-template-system.md](10-template-system.md) | `backend/services/templates/*`, `backend/api/templates/router.py` | +| Auth, security, observability | [11-auth-security-observability.md](11-auth-security-observability.md) | `backend/core/*`, `backend/api/auth/*`, telemetry/logging files | + +## 3. Appendices + +- [API endpoint index](appendices/api-endpoint-index.md) — compact endpoint list by router. +- [Class index](appendices/class-index.md) — backend classes, dataclasses, and schema classes by package. +- [Data-flow diagrams](appendices/data-flow-diagrams.md) — text diagrams for upload, processing, extraction, evaluation, and restore flows. +- [Risks, assumptions, and testing](appendices/risks-assumptions-testing.md) — risks, assumptions, and recommended test coverage. + +## 4. High-level backend stack + +| Layer | Technology / implementation | +| --- | --- | +| API framework | FastAPI | +| Auth | Better Auth sidecar, PostgreSQL-backed session validation | +| ORM | SQLAlchemy | +| Migrations | Alembic | +| Database | PostgreSQL | +| File storage | Azure Blob Storage via `BlobStorageClient`; local `/tmp/summarization` cache for processed artifacts | +| Document parsers | Azure Document Intelligence, Docling remote/local service paths | +| LLM providers | Azure OpenAI, Vertex/Gemini, Anthropic Vertex, Llama MaaS, Macbook-hosted Ollama-compatible runtime, vLLM OpenAI-compatible endpoint | +| Evaluation | DeepEval GEval metrics plus custom metric factory | +| Metrics/cost | `CostTracker`, Prometheus metrics, session metric DB fields | +| Logging/tracing | structlog JSON logs, optional Loki, optional OpenTelemetry OTLP export | + +## 5. Design boundaries + +### In scope + +- Backend API behavior and endpoint contracts. +- Backend classes, methods, data structures, and persistence models. +- Document parsing artifact formats and storage paths. +- LLM provider routing, timeout, retry, and response normalization behavior. +- Evaluation algorithms, job queue behavior, and cost recording. +- Session restore, sharing, groups, and template authorization logic. +- Security and observability mechanisms implemented in the backend. + +### Out of scope + +- Frontend component design and UI state management, except where backend restore/API contracts require context. +- Auth sidecar internal TypeScript design, except the backend proxy and session-validation boundary. +- Cloud provisioning details beyond backend design dependencies; see `../superpowers/plans/dockerize-and-deploy.md` for deployment records. +- Business Requirements Document (BRD) details not visible in the current repository. + +## 6. How to maintain these docs + +When backend code changes, update the smallest relevant module document first, then update the index or appendix only if links, class names, endpoint lists, or cross-module flows changed. + +Good update examples: + +- Adding a new SQLAlchemy table: update [03-data-models.md](03-data-models.md) and [appendices/class-index.md](appendices/class-index.md). +- Adding a new route: update [02-api-surface.md](02-api-surface.md) and [appendices/api-endpoint-index.md](appendices/api-endpoint-index.md). +- Adding a new model provider: update [06-llm-layer.md](06-llm-layer.md), [11-auth-security-observability.md](11-auth-security-observability.md) if new secrets are needed, and the risk/testing appendix. +- Changing extraction result shape: update [04-schemas.md](04-schemas.md), [07-extraction-flow.md](07-extraction-flow.md), and data-flow diagrams. diff --git a/docs/backend/appendices/api-endpoint-index.md b/docs/backend/appendices/api-endpoint-index.md new file mode 100644 index 0000000..c5fe927 --- /dev/null +++ b/docs/backend/appendices/api-endpoint-index.md @@ -0,0 +1,152 @@ +# API Endpoint Index + +Compact index of backend API endpoints by router. For design details, see [../02-api-surface.md](../02-api-surface.md). + +## Auth proxy + +| Method | Path | Purpose | +| --- | --- | --- | +| all common methods | `/api/auth/{path:path}` | Proxy Better Auth sidecar endpoints. | + +## Auth + +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/auth/health` | Auth-protected health check. | +| `POST` | `/auth/history` | Record login history. | + +## Files + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/upload` | Upload and deduplicate file. | +| `GET` | `/api/files/list` | List current user's files. | +| `GET` | `/api/files/{file_id}` | Download uploaded file. | +| `GET` | `/api/files/{file_id}/info` | Get file metadata and processing flags. | +| `DELETE` | `/api/files/{file_id}` | Delete file placeholder/stub behavior. | + +## Documents + +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/api/documents/{document_id}/view` | Return canonical document view. | +| `POST` | `/api/documents/process/file/{file_id}` | Process uploaded file. | +| `GET` | `/api/documents/{document_id}/content` | Return markdown content. | +| `GET` | `/api/documents/{document_id}/enhanced-content` | Return markdown with figure summaries. | +| `GET` | `/api/documents/{document_id}/figures` | Return figure metadata. | +| `GET` | `/api/documents/{document_id}/analysis` | Return normalized raw analysis. | +| `GET` | `/api/documents/{document_id}/figures/{figure_filename}` | Serve figure image artifact. | +| `POST` | `/api/documents/{document_id}/figures/{figure_id}/generate-summary` | Generate/persist figure summary. | +| `POST` | `/api/documents/{document_id}/figures/{figure_id}/extract-content` | Legacy alias for figure extraction. | +| `GET` | `/api/documents/{document_id}/tables/{table_filename}` | Serve table HTML artifact. | + +## Extractions + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/extract` | Extract requested entities from a processed document. | + +## Paragraph generation and evaluation + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/generate_paragraph` | Generate paragraph from extracted entities. | +| `POST` | `/api/paragraph-evaluation/generate` | Generate paragraph evaluation/ground-truth record. | + +## Evaluations + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/evaluations/cancel` | Cancel session evaluation. | +| `POST` | `/api/evaluations/evaluate` | Evaluate one extraction. | +| `POST` | `/api/evaluations/evaluate/batch` | Evaluate multiple extractions. | +| `POST` | `/api/evaluations/evaluate/custom` | Evaluate with custom metric. | +| `GET` | `/api/evaluations/results/{evaluation_id}` | Fetch stored evaluation result. | +| `GET` | `/api/evaluations/results` | List stored evaluation results. | +| `GET` | `/api/evaluations/metrics/info` | Return metric/provider info. | + +## Evaluation jobs + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/evaluations/jobs` | Submit background evaluation job. | +| `GET` | `/api/evaluations/jobs/{job_id}` | Poll job status. | +| `POST` | `/api/evaluations/jobs/{job_id}/cancel` | Cancel job. | + +## Sessions + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/sessions` | Create session. | +| `GET` | `/api/sessions` | List user sessions. | +| `GET` | `/api/sessions/{session_id}` | Get full owned session. | +| `GET` | `/api/sessions/{session_id}/restore-view` | Build restore-view payload. | +| `PATCH` | `/api/sessions/{session_id}` | Update session. | +| `DELETE` | `/api/sessions/{session_id}` | Delete session. | +| `POST` | `/api/sessions/{session_id}/extractions` | Add extraction result. | +| `POST` | `/api/sessions/{session_id}/evaluations` | Add evaluation result. | +| `GET` | `/api/sessions/shared/list` | List shared sessions. | +| `GET` | `/api/sessions/shared/{session_id}` | Get shared session. | +| `GET` | `/api/sessions/shared/{session_id}/restore-view` | Build shared restore-view payload. | +| `POST` | `/api/sessions/{session_id}/share` | Share session with group. | +| `DELETE` | `/api/sessions/{session_id}/share` | Unshare session. | + +## Groups + +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/api/groups` | List groups. | +| `POST` | `/api/groups` | Create group. | +| `GET` | `/api/groups/{group_id}` | Get group detail. | +| `PUT` | `/api/groups/{group_id}` | Update group. | +| `DELETE` | `/api/groups/{group_id}` | Delete group. | +| `GET` | `/api/groups/{group_id}/members` | List members. | +| `POST` | `/api/groups/{group_id}/members` | Add member. | +| `PUT` | `/api/groups/{group_id}/members/{user_id}` | Update member role. | +| `DELETE` | `/api/groups/{group_id}/members/{user_id}` | Remove member. | +| `GET` | `/api/groups/users/search` | Search users. | + +## Templates and folders + +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/api/templates/folders` | List folders. | +| `POST` | `/api/templates/folders` | Create folder. | +| `PATCH` | `/api/templates/folders/{folder_id}` | Rename folder. | +| `DELETE` | `/api/templates/folders/{folder_id}` | Delete folder. | +| `GET` | `/api/templates` | List templates. | +| `POST` | `/api/templates` | Create template. | +| `GET` | `/api/templates/{template_id}` | Get template. | +| `PUT` | `/api/templates/{template_id}` | Update template. | +| `DELETE` | `/api/templates/{template_id}` | Delete template. | +| `POST` | `/api/templates/{template_id}/fork` | Fork template. | +| `PUT` | `/api/templates/{template_id}/scope` | Change template scope. | +| `PUT` | `/api/templates/{template_id}/immutable` | Set immutability. | +| `GET` | `/api/templates/{template_id}/versions` | List versions. | +| `POST` | `/api/templates/{template_id}/revert/{version}` | Revert version. | +| `GET` | `/api/templates/{template_id}/permissions` | List permissions. | +| `POST` | `/api/templates/{template_id}/permissions` | Set permission. | +| `DELETE` | `/api/templates/{template_id}/permissions/{user_id}` | Remove permission. | + +## Server and telemetry + +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/api/server/health` | Health check. | +| `POST` | `/api/telemetry/traces` | Proxy browser traces. | +| `POST` | `/api/server/client-error` | Record frontend error. | +| `GET` | `/api/server-config` | Provider/config flags. | +| `GET` | `/api/models` | Available model catalog. | +| `GET` | `/api/server/session-metrics` | Session metrics. | +| `POST` | `/api/server/session-metrics/load` | Load metrics from DB. | +| `DELETE` | `/api/server/session-metrics` | Clear session metrics. | +| `POST` | `/api/server/batch-metrics` | Record batch metrics. | +| `GET` | `/api/server/document-metrics` | Document metrics. | +| `POST` | `/api/server/benchmark/clear` | Clear benchmark cache. | +| `GET` | `/api/server/logs` | Fetch server logs. | + +## Chat + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/chat/query` | General chat over optional document markdown. | diff --git a/docs/backend/appendices/class-index.md b/docs/backend/appendices/class-index.md new file mode 100644 index 0000000..50236ae --- /dev/null +++ b/docs/backend/appendices/class-index.md @@ -0,0 +1,172 @@ +# Backend Class Index + +Compact index of backend classes, dataclasses, and schema classes. For detailed behavior, follow the linked module docs from [../README.md](../README.md). + +## Core app/config/auth + +| Symbol | File | Type | +| --- | --- | --- | +| `get_current_user` | `backend/core/auth.py` | FastAPI dependency function | +| `get_optional_user` | `backend/core/auth.py` | FastAPI dependency function | +| `load_config` | `backend/core/config.py` | config loader | +| `setup_cors` | `backend/core/middleware.py` | middleware installer | +| `setup_logging` | `backend/core/logging_config.py` | logging setup | + +## SQLAlchemy models + +| Class | File | Table | +| --- | --- | --- | +| `Base` | `backend/models/base.py` | declarative base | +| `User` | `backend/models/user.py` | `user` | +| `AuthSession` | `backend/models/user.py` | `session` | +| `Account` | `backend/models/user.py` | `account` | +| `Verification` | `backend/models/user.py` | `verification` | +| `AppSession` | `backend/models/app_session.py` | `app_sessions` | +| `Document` | `backend/models/document.py` | `documents` | +| `ExtractionResult` | `backend/models/extraction.py` | `extraction_results` | +| `EvaluationResult` | `backend/models/evaluation.py` | `evaluation_results` | +| `Group` | `backend/models/group.py` | `groups` | +| `UserGroup` | `backend/models/group.py` | `user_groups` | +| `UserPreferences` | `backend/models/preferences.py` | `user_preferences` | +| `LoginHistory` | `backend/models/preferences.py` | `login_history` | +| `UserPromptTemplate` | `backend/models/preferences.py` | `user_prompt_templates` | +| `TemplateFolder` | `backend/models/template.py` | `template_folders` | +| `PromptTemplate` | `backend/models/template.py` | `prompt_templates` | +| `TemplateVersion` | `backend/models/template.py` | `template_versions` | +| `TemplatePermission` | `backend/models/template.py` | `template_permissions` | +| `EvalJobRecord` | `backend/models/eval_job.py` | `eval_jobs` | + +## Central Pydantic schemas + +| Class | File | Purpose | +| --- | --- | --- | +| `ProcessorType` | `backend/schemas/enums.py` | Parser enum. | +| `ProcessFileRequest` | `backend/schemas/documents.py` | Process file request. | +| `ExtractFigureContentRequest` | `backend/schemas/documents.py` | Figure summary/extraction request. | +| `FigureExtractionResult` | `backend/schemas/documents.py` | Figure extracted content. | +| `FigureMetadata` | `backend/schemas/documents.py` | Figure API metadata. | +| `Entity` | `backend/schemas/extractions.py` | Entity extraction instruction. | +| `ExtractRequest` | `backend/schemas/extractions.py` | Entity extraction request. | +| `EvaluationRequest` | `backend/schemas/evaluations.py` | Single evaluation request. | +| `SingleExtractionEval` | `backend/schemas/evaluations.py` | Batch evaluation item. | +| `BatchEvaluationRequest` | `backend/schemas/evaluations.py` | Batch evaluation request. | +| `CustomMetricRequest` | `backend/schemas/evaluations.py` | Custom metric request. | +| `MetricResult` | `backend/schemas/evaluations.py` | Metric response item. | +| `EvaluationResponse` | `backend/schemas/evaluations.py` | Single evaluation response. | +| `BatchEvaluationResponse` | `backend/schemas/evaluations.py` | Batch evaluation response. | +| `ServerConfig` | `backend/schemas/server.py` | Server/provider config flags. | +| `SessionEntity` | `backend/schemas/sessions.py` | Session entity config. | +| `SessionConfiguration` | `backend/schemas/sessions.py` | Session workflow config. | +| `SessionDocument` | `backend/schemas/sessions.py` | Session document summary. | +| `ExtractionResult` | `backend/schemas/sessions.py` | Session extraction result schema. | +| `SessionMetrics` | `backend/schemas/sessions.py` | Session metric totals. | +| `EvaluationScore` | `backend/schemas/sessions.py` | One evaluation metric score. | +| `EvaluationResult` | `backend/schemas/sessions.py` | Grouped session evaluation result. | +| `Session` | `backend/schemas/sessions.py` | Full session aggregate. | +| `CreateSessionRequest` | `backend/schemas/sessions.py` | Create session request. | +| `UpdateSessionRequest` | `backend/schemas/sessions.py` | Update session request. | +| `SessionSummary` | `backend/schemas/sessions.py` | Session list item. | +| `SessionListResponse` | `backend/schemas/sessions.py` | Session list response. | + +## Router-local schemas + +| Class | File | +| --- | --- | +| `FileUploadResponse` | `backend/api/files/router.py` | +| `UserFileInfo` | `backend/api/files/router.py` | +| `EvalTaskRequest` | `backend/api/evaluations/jobs.py` | +| `ProviderConfigRequest` | `backend/api/evaluations/jobs.py` | +| `SubmitJobRequest` | `backend/api/evaluations/jobs.py` | +| `ShareSessionRequest` | `backend/api/sessions/router.py` | +| `CreateGroupRequest` | `backend/api/groups/router.py` | +| `UpdateGroupRequest` | `backend/api/groups/router.py` | +| `AddMemberRequest` | `backend/api/groups/router.py` | +| `UpdateMemberRoleRequest` | `backend/api/groups/router.py` | +| `GroupResponse` | `backend/api/groups/router.py` | +| `MemberResponse` | `backend/api/groups/router.py` | +| `GroupDetailResponse` | `backend/api/groups/router.py` | +| `UserSearchResult` | `backend/api/groups/router.py` | +| `EntityModel` | `backend/api/templates/router.py` | +| `VariableModel` | `backend/api/templates/router.py` | +| `CreateTemplateRequest` | `backend/api/templates/router.py` | +| `UpdateTemplateRequest` | `backend/api/templates/router.py` | +| `SetImmutableRequest` | `backend/api/templates/router.py` | +| `SetPermissionRequest` | `backend/api/templates/router.py` | +| `ForkTemplateRequest` | `backend/api/templates/router.py` | +| `ChangeScopeRequest` | `backend/api/templates/router.py` | +| `CreateFolderRequest` | `backend/api/templates/router.py` | +| `RenameFolderRequest` | `backend/api/templates/router.py` | +| `FolderResponse` | `backend/api/templates/router.py` | +| `TemplateResponse` | `backend/api/templates/router.py` | +| `VersionResponse` | `backend/api/templates/router.py` | +| `PermissionResponse` | `backend/api/templates/router.py` | +| `BatchMetricsRequest` | `backend/api/server/router.py` | +| `ChatQueryRequest` | `backend/api/chat/router.py` | +| `ParagraphGenerationRequest` | `backend/api/paragraphgenerator.py` | +| `ParagraphEvalGenerateRequest` | `backend/api/paragraph_evaluation.py` | + +## Service classes + +| Class | File | Area | +| --- | --- | --- | +| `SQLAlchemyDBService` | `backend/services/database/sqlalchemy_db_service.py` | persistence | +| `SessionService` | `backend/services/session/session_service.py` | session orchestration | +| `GroupService` | `backend/services/groups/group_service.py` | groups/memberships | +| `TemplateService` | `backend/services/templates/template_service.py` | templates | +| `FolderService` | `backend/services/templates/folder_service.py` | template folders | +| `DocumentService` | `backend/services/document/document_service.py` | document façade | +| `OrganizedFileService` | `backend/services/document/organized_file_service.py` | blob-backed file organization | +| `OrganizedDocumentProcessor` | `backend/services/document/organized_processor.py` | organized processing orchestration | +| `FileService` | `backend/services/document/file_service.py` | legacy local file service | +| `BlobStorageClient` | `backend/services/storage/blob_storage.py` | Azure Blob wrapper | +| `AzureDocIntelligenceService` | `backend/services/document/processors/azure_doc_intelligence/azure_doc_intelligence_service.py` | Azure parser | +| `DoclingRemoteClient` | `backend/services/document/processors/docling/docling_remote_client.py` | remote Docling client | +| `DoclingService` | `backend/services/document/processors/docling/docling_service.py` | local Docling parser | +| `_VRAMPeakTracker` | `backend/services/document/processors/docling/docling_service.py` | worker VRAM polling | +| `VRAMStatus` | `backend/services/document/processors/docling/vram_guard.py` | VRAM status data | +| `VRAMGuard` | `backend/services/document/processors/docling/vram_guard.py` | VRAM concurrency guard | +| `LLMService` | `backend/services/llm/llm_service.py` | provider router | +| `AzureLLMClient` | `backend/services/llm/azure.py` | Azure provider | +| `GeminiLLMClient` | `backend/services/llm/gemini.py` | Gemini provider | +| `AnthropicLLMClient` | `backend/services/llm/anthropic.py` | Anthropic Vertex provider | +| `LlamaLLMClient` | `backend/services/llm/llama.py` | Llama MaaS provider | +| `MacbookLLMClient` | `backend/services/llm/macbook.py` | Macbook provider | +| `MacbookRequestQueue` | `backend/services/llm/macbook_queue.py` | Macbook FIFO queue | +| `VLLMClient` | `backend/services/llm/vllm.py` | vLLM provider | +| `EvaluationService` | `backend/services/evaluation/evaluation_service.py` | evaluation orchestration | +| `EvaluationResultStorage` | `backend/services/evaluation/storage/result_storage.py` | JSON result storage | +| `CorrectnessMetricFactory` | `backend/services/evaluation/metrics/correctness.py` | metric factory | +| `CompletenessMetricFactory` | `backend/services/evaluation/metrics/completeness.py` | metric factory | +| `RelevanceMetricFactory` | `backend/services/evaluation/metrics/relevance.py` | metric factory | +| `SafetyMetricFactory` | `backend/services/evaluation/metrics/safety.py` | metric factory | +| `CustomMetricFactory` | `backend/services/evaluation/metrics/custom.py` | metric factory | +| `AzureOpenAIDeepEvalModel` | `backend/services/evaluation/adapters/azure_adapter.py` | evaluation adapter | +| `VertexAIDeepEvalModel` | `backend/services/evaluation/adapters/vertex_adapter.py` | evaluation adapter | +| `AnthropicVertexDeepEvalModel` | `backend/services/evaluation/adapters/anthropic_adapter.py` | evaluation adapter | +| `CallMetric` | `backend/services/telemetry/cost_tracker.py` | telemetry dataclass | +| `BatchMetric` | `backend/services/telemetry/cost_tracker.py` | telemetry dataclass | +| `SessionMetrics` | `backend/services/telemetry/cost_tracker.py` | telemetry dataclass | +| `CostTracker` | `backend/services/telemetry/cost_tracker.py` | cost/session metrics | + +## Evaluation job dataclasses/classes + +| Class | File | Purpose | +| --- | --- | --- | +| `EvalTask` | `backend/services/evaluation/job_queue.py` | One entity output to evaluate. | +| `ProviderConfig` | `backend/services/evaluation/job_queue.py` | Judge provider config. | +| `TaskResult` | `backend/services/evaluation/job_queue.py` | One task/provider result. | +| `EvalJob` | `backend/services/evaluation/job_queue.py` | Background job runtime state. | +| `_JobStatusProxy` | `backend/services/evaluation/job_queue.py` | DB-backed job status snapshot. | + +## Provider structured-output helper schemas + +| Class | File | Purpose | +| --- | --- | --- | +| `MarkdownReference` | `backend/services/llm/azure.py` | Azure structured reference. | +| `ExtractionResult` | `backend/services/llm/azure.py` | Azure structured extraction result. | +| `MarkdownReference` | `backend/services/llm/gemini.py` | Gemini structured reference. | +| `ExtractionResult` | `backend/services/llm/gemini.py` | Gemini structured extraction result. | +| `MarkdownReference` | `backend/services/llm/llama.py` | Llama structured reference. | +| `ExtractionResult` | `backend/services/llm/llama.py` | Llama structured extraction result. | +| `MarkdownReference` | `backend/services/llm/vllm.py` | vLLM structured reference. | +| `ExtractionResult` | `backend/services/llm/vllm.py` | vLLM structured extraction result. | diff --git a/docs/backend/appendices/data-flow-diagrams.md b/docs/backend/appendices/data-flow-diagrams.md new file mode 100644 index 0000000..f62b474 --- /dev/null +++ b/docs/backend/appendices/data-flow-diagrams.md @@ -0,0 +1,343 @@ +# Backend Data-Flow Diagrams + +Text diagrams for major backend flows. See the module docs for field-level details. + +## 1. Authenticated API request + +```text +Frontend + | + | Authorization: Bearer + v +FastAPI route + | + | Depends(get_current_user) + v +core.auth.get_current_user() + | + | SELECT AuthSession JOIN User WHERE token = ... + v +PostgreSQL + | + | session row + user row + v +expiry check + optional ALLOWED_EMAILS check + | + v +route receives current_user dict +``` + +## 2. Better Auth proxy + +```text +Browser + | + | /api/auth/sign-in/*, /api/auth/callback/*, etc. + v +FastAPI auth proxy router + | + | forwards request to localhost/auth sidecar + v +Better Auth sidecar + | + | reads/writes Better Auth DB tables + v +PostgreSQL + | + | Set-Cookie / redirect / JSON response + v +Browser +``` + +## 3. File upload + +```text +POST /api/upload multipart file + | + v +files router + | + v +OrganizedFileService.save_uploaded_file() + | + +-- compute SHA-256 hash + +-- infer extension and mime type + +-- check blob exists: global/{hash}/original.{ext} + | + +-- if missing: + | upload original bytes + | upload global/{hash}/metadata.json + | + +-- if user_id: + | create Document DB row best-effort + v +response: file_hash, blob path, dedupe flags, filename, size +``` + +## 4. Document processing + +```text +POST /api/documents/process/file/{file_hash} + | + v +documents router + | + +-- check existing processed document.md + | global/{hash}/processed/{processor}/document.md + | + +-- if cache hit: + | build document view from metadata/artifacts + | + +-- if cache miss: + get original file from blob to /tmp/summarization/{hash}/original.{ext} + choose processor + write local artifacts to /tmp/summarization/{hash}/processed/{processor}/ + sync local artifact tree to blob + update Document DB processing metadata + build document view +``` + +## 5. Azure Document Intelligence processing + +```text +local original file or URL + | + v +AzureDocIntelligenceService.convert_document_to_markdown() + | + +-- begin_analyze_document(..., output markdown + figures) + +-- wait for poller.result() + +-- save raw_analysis.json + +-- save document.md + +-- regex-extract HTML tables to tables/table-N.html + +-- download figure PNGs to figures/{figure_id}.png + +-- save metadata.json + v +organized processor syncs output tree to blob +``` + +## 6. Docling processing + +```text +local original file + | + v +DoclingService / DoclingRemoteClient + | + +-- acquire VRAM slot if local Docling path + +-- worker process converts PDF + +-- save document.md + +-- save raw_analysis.json + +-- save figures/picture-N.png + +-- save tables/table-N.html + +-- save metadata.json + +-- report peak VRAM / OOM to VRAMGuard + v +organized processor syncs output tree to blob +``` + +## 7. Document content/read path + +```text +GET /api/documents/{file_hash}/content + | + v +DocumentService.get_markdown_content() + | + +-- resolve processor: + | preferred -> azure_doc_intelligence -> docling + | + +-- get_processed_content(file_hash, processor) + | /tmp cache first + | blob fallback + | + +-- optional raw_analysis.content fallback + v +return markdown_content +``` + +## 8. Entity extraction + +```text +POST /api/extract + | + v +load markdown + optional figure context + | + v +for each Entity concurrently: + | + +-- LLMService.extract_entities_from_markdown() + | provider dispatch by model_type + | provider call + timeout logging + | record cost/session metrics on success + | + +-- normalize answer/references/meta + | + +-- if references and raw analysis available: + | match references to bounding boxes + | + +-- build extraction result + | + +-- if session_id: + SessionService.add_extraction_result_fast() + DB upsert by (document_id, entity_name, model_id) + v +return all entity results +``` + +## 9. Figure summary generation + +```text +POST /api/documents/{file_hash}/figures/{figure_id}/generate-summary + | + v +resolve processor + metadata + | + v +find figure metadata and image path + | + v +read figure image bytes from /tmp cache or blob + | + v +LLMService.extract_content_from_image() + | + v +update metadata.json with extracted_content / summary + | + v +return figure summary result +``` + +## 10. Single evaluation + +```text +POST /api/evaluations/evaluate + | + v +EvaluationService.evaluate_extraction() + | + +-- create evaluation model adapter + +-- select metrics + +-- skip correctness/completeness if no expected_output + +-- create LLMTestCase + +-- try combined JSON scoring prompt + | parse direct JSON / fenced JSON / extracted JSON / salvaged metric entries + +-- fallback to per-metric async GEval scoring if needed + +-- collect call history + +-- estimate and record cost + +-- compute aggregate score/all_passed + v +return evaluation result +``` + +## 11. Background evaluation job + +```text +POST /api/evaluations/jobs + | + v +create EvalJob(tasks, providers, session_id, user_id) + | + v +submit_job() + | + +-- store in in-memory _JOBS + +-- create EvalJobRecord asynchronously + +-- start _process_job background task + | + v +_process_job() + | + +-- status=running, sync DB + +-- flatten tasks x providers + +-- compute per-job concurrency + +-- run _run_single_eval under per-job and global semaphores + +-- persist session evaluation result + +-- status=completed/cancelled/failed, sync DB + +GET /api/evaluations/jobs/{job_id} + | + +-- check _JOBS + +-- else load EvalJobRecord and return _JobStatusProxy snapshot +``` + +## 12. Session restore view + +```text +GET /api/sessions/{session_id}/restore-view + | + v +SessionService.get_session() + | + +-- DB session + documents + extractions + evaluations + +-- convert to Pydantic Session + | + v +SessionService.build_restore_view() + | + +-- merge files_config sources + +-- for each document: + OrganizedFileService.build_document_view() + check artifact availability + enumerate figure/table artifacts if needed + v +return primary file ids + uploadedFiles restore payload +``` + +## 13. Shared session read + +```text +GET /api/sessions/shared/{session_id} + | + v +SQLAlchemyDBService.get_session_for_shared_view() + | + +-- load AppSession + +-- require shared_with_group_id is not null + +-- require UserGroup row for requesting user/group + +-- load docs/extractions/evaluations + v +SessionService._db_to_session() + | + v +return shared Session +``` + +## 14. Template update/versioning + +```text +PUT /api/templates/{template_id} + | + v +TemplateService.update_template() + | + +-- load template + +-- _can_read() + +-- _can_edit() + +-- insert TemplateVersion snapshot of current content + +-- update allowed fields + +-- increment template.version + +-- update timestamp + v +return updated template +``` + +## 15. Cost recording + +```text +provider call succeeds + | + v +LLMService._record_session_metrics() + | + v +cost_tracker.record_call(session_id, provider, model, tokens, duration) + | + +-- update in-memory SessionMetrics + +-- emit Prometheus metrics if available + +-- DB increment_session_metrics through executor when event loop exists + v +session totals visible through server/session-metrics endpoints +``` diff --git a/docs/backend/appendices/risks-assumptions-testing.md b/docs/backend/appendices/risks-assumptions-testing.md new file mode 100644 index 0000000..7f4151b --- /dev/null +++ b/docs/backend/appendices/risks-assumptions-testing.md @@ -0,0 +1,228 @@ +# Risks, Assumptions, and Testing Strategy + +This appendix captures implementation assumptions, technical risks, and recommended tests for the backend TDD. + +## 1. Assumptions + +### 1.1 Runtime assumptions + +- PostgreSQL is available through `DATABASE_URL` or `POSTGRES_*` environment variables. +- Alembic migrations have been applied before the backend serves production traffic. +- Better Auth sidecar is running and shares the same PostgreSQL database. +- In production, `/api/auth/*` requests reach FastAPI first and are proxied to the auth sidecar. +- Azure Blob Storage connection string is present when using the organized file service. +- External model providers may be partially configured; unavailable providers should not prevent app startup. +- Production backend uses one Gunicorn worker per container replica to avoid duplicating heavyweight parser/model state. + +### 1.2 Data assumptions + +- File hash is the stable identity for uploaded file content. +- Processed artifact trees are organized by file hash and processor name. +- `document.md` is the strict signal that a file is fully processed. +- Partial artifact trees may still be useful for analysis/debug endpoints. +- Session extraction/evaluation persistence depends on matching document id or file hash, especially for multi-document sessions. +- Prompt template scopes are one of `user`, `group`, or `global`. + +### 1.3 Provider assumptions + +- Provider result dictionaries include enough metadata for token/cost tracking when calls succeed. +- Structured extraction providers may return `answer` and `references`, but downstream code must tolerate provider-specific shapes. +- Evaluation adapters can expose call history with token usage for cost estimation. +- Provider timeouts/retries vary by provider and are part of current behavior. + +## 2. Technical risks + +| Risk | Likelihood | Impact | Mitigation / management | +| --- | --- | --- | --- | +| ORM defaults differ from DB server defaults | Medium | Medium | Prefer ORM writes; add tests for direct migration schema; document model/migration mismatches. | +| File hash leakage allows artifact probing | Low/Medium | High | Keep file endpoints authenticated where possible; validate access model before public sharing. | +| Global template operations are too permissive | Medium | Medium | Review global-scope creation/folder management before enabling broad users. | +| Provider response shape drift | High | Medium | Normalize provider outputs in one place; add tests with recorded sample responses. | +| Evaluation combined JSON parsing fails | Medium | Medium | Keep per-metric fallback path; test malformed/fenced/partial JSON outputs. | +| Docling OOM or GPU pressure | Medium | High | VRAMGuard, worker peak reporting, OOM estimate bumping, one worker per container process. | +| Background jobs across replicas lose in-memory state | Medium | Medium | Persist `EvalJobRecord`; polling falls back to DB snapshot. | +| Cancellation is process-local for synchronous batch evaluation | Medium | Medium | Use job-based evaluation for cross-worker cancellation; document process-local limitation. | +| Blob/local cache inconsistency | Medium | Medium | Read from `/tmp` first but blob fallback; sync output tree only after successful processing. | +| Text with null bytes breaks PostgreSQL writes | Medium | Low/Medium | Use `sanitize_text()` in DB write paths. | +| CORS default is permissive | Medium | High in production | Set explicit `CORS_ALLOWED_ORIGINS` in production. | +| Telemetry write blocks request loop | Low | Medium | CostTracker schedules DB metrics updates through executor. | + +## 3. Recommended test coverage + +## 3.1 Auth and security tests + +- `get_current_user` accepts valid Authorization bearer token. +- `get_current_user` rejects missing token, invalid token, expired token. +- `ALLOWED_EMAILS` denies non-allowlisted email. +- Auth proxy forwards headers and preserves `Set-Cookie` behavior. +- CORS uses `*` when unset and comma-separated origins when configured. +- File/figure/table endpoints reject unsafe filenames. +- `sanitize_text()` removes null bytes/control chars and preserves tab/newline/carriage return. + +## 3.2 Database/model tests + +- Alembic migrations create all expected tables, indexes, and constraints. +- ORM insert defaults populate expected fields for `AppSession`, `Document`, `ExtractionResult`, and `EvalJobRecord`. +- Unique upsert constraints work for: + - extraction `(document_id, entity_name, model_id)`; + - evaluation `(extraction_result_id, metric, judge_model)`; + - template permission `(template_id, user_id)`; + - template version `(template_id, version)`. +- `PromptTemplate.entities` model/migration nullability mismatch is either fixed or documented by tests. +- Direct SQL insert behavior is known for fields without server defaults. + +## 3.3 File and document processing tests + +- Upload same bytes twice returns same hash and dedupe flags. +- Upload metadata is written to expected blob path. +- DB document registration failure does not fail upload. +- `resolve_processed_processor()` respects preferred processor and fallback order. +- `is_file_processed()` requires `document.md`. +- `get_processing_file_bytes()` reads local cache before blob and caches blob downloads. +- `build_document_view()` returns stable top-level and `processingResult` fields. +- Artifact availability flags reflect blob state. +- Figure/table fallback enumeration works when metadata counts are missing. + +## 3.4 Azure parser tests + +- Azure unavailable returns false availability. +- File source and URL source build different Azure analyze requests. +- Successful conversion writes `document.md`, `raw_analysis.json`, `metadata.json`, figures, and tables. +- Table extraction from markdown HTML is correct. +- Missing Azure figure result id does not fail the whole conversion. +- Error path returns structured failure with conversion id. + +## 3.5 Docling parser and VRAM tests + +- Worker returns structured success with markdown, raw analysis, image info, page count, peak VRAM. +- Worker returns structured failure on exception. +- Markdown table replacement preserves intended table order. +- `VRAMGuard.acquire_slot()` handles admission, queue count, release, and timeout. +- `VRAMGuard.report_worker_result()` updates per-worker estimate and max workers. +- `VRAMGuard.report_oom()` bumps estimate and can shrink max workers. +- Persisted VRAM state loads only when version and age are valid. + +## 3.6 Bounding-box tests + +- Azure normalization converts inch page dimensions and polygons to points. +- Azure paragraph match works for exact and fuzzy references. +- Azure line fallback works when paragraph match is absent. +- Azure figure-reference extraction detects Figure/Fig variants. +- Docling normalization returns expected page/paragraph/table/figure shape. +- Docling polygon extraction handles valid and malformed polygons. +- Unknown processor raw analysis passes through unchanged. + +## 3.7 LLM provider tests + +Use mocked provider responses, not live API calls, for unit tests. + +- `LLMService` dispatches correctly by `model_type`. +- Disabled provider returns `success=False` without crashing. +- Timeout wrapper logs and re-raises timeout. +- Azure structured extraction success maps answer/references/meta. +- Azure fallback path handles structured-output failure. +- Gemini structured output parses JSON and records retry metadata. +- Anthropic JSON-prompt mode parses answer/references. +- Llama primary and fallback strategies return expected strategy metadata. +- Macbook queue serializes concurrent requests. +- vLLM strips `vllm-` prefix before sending model id. +- Successful provider calls record session metrics. + +## 3.8 Extraction flow tests + +- Missing markdown returns API error. +- One request with multiple entities runs all entity tasks and returns per-entity results. +- Entity-level system prompt is passed to provider call. +- Figure context includes generated figure summaries when present. +- Provider references are matched to bbox data when raw analysis exists. +- Extraction persistence upserts existing result instead of duplicating. +- Multi-document session without file hash refuses to guess target document. +- Failed entity extraction can coexist with successful entities in one response. + +## 3.9 Evaluation tests + +- Metric factory map creates correctness/completeness/relevance/safety metrics. +- Correctness/completeness are skipped without expected output. +- Combined scoring parses direct JSON, fenced JSON, extracted JSON block, and partial salvaged metric entries. +- Combined scoring clamps scores to `[0,1]`. +- Per-metric fallback runs when combined parse fails. +- Batch evaluation honors cancellation between chunks. +- Evaluation cost is computed from adapter call history. +- Result storage saves, reads, lists, and deletes JSON files. + +## 3.10 Evaluation job tests + +- `create_job()` builds expected total from tasks x providers. +- `submit_job()` stores in memory and creates DB record. +- `get_job()` returns memory job first, DB proxy second. +- `cancel_job()` cancels local task handles when local. +- `cancel_job()` marks DB cancelled when non-local. +- `_run_single_eval()` persists session evaluation result on success. +- Per-job concurrency changes when multiple jobs run. +- Completed jobs are cleaned after TTL. + +## 3.11 Session tests + +- Create session with no docs, one doc, partial doc failures, and all doc failures. +- Config-only update returns lightweight session. +- `evaluation_config` and `files_config` merge instead of replace nested values unexpectedly. +- Extraction result matching uses file hash/document id correctly. +- Evaluation result matching preserves document-specific results. +- Human-score update applies to intended judge/model metrics. +- Restore-view returns expected primary file and uploaded files. +- Shared session requires group membership. + +## 3.12 Group tests + +- Group creation creates owner membership. +- Non-member cannot read group detail. +- System admin can read/update/delete according to service logic. +- Admin/owner can add members. +- Adding owner role normalizes to admin for new members. +- Cannot change to/from owner through role update endpoint. +- Only owner can promote member to admin. +- Only-owner self-removal is blocked. +- Membership responses are enriched with user profile data. + +## 3.13 Template/folder tests + +- Create template validates scope. +- Group-scope create requires group id and membership. +- Get/list templates enforce `_can_read()`. +- `_can_edit()` denies immutable templates. +- Update creates `TemplateVersion` snapshot before mutation. +- Revert creates a new version through update path. +- Fork creates user-scope mutable copy. +- Scope transitions enforce old/new scope permission rules. +- Explicit permission upsert uses unique constraint. +- Folder create validates parent scope/group. +- Folder delete refuses non-empty folders. + +## 4. Manual smoke tests + +For integrated backend verification: + +1. Start Postgres, auth sidecar, backend, and frontend. +2. Log in through Better Auth. +3. Upload a PDF. +4. Process with Azure Document Intelligence. +5. Fetch markdown/content/analysis/figures. +6. Extract at least two entities with one model. +7. Save session and reload restore view. +8. Run evaluation for one extraction. +9. Submit background evaluation job and poll until completed. +10. Create group, share session, verify another group member can open shared restore view. +11. Create template, update it, verify version history, fork it, and change scope. +12. Check `/api/server/session-metrics` and `/api/server/logs`. + +## 5. Documentation maintenance checklist + +When backend changes: + +- New route: update `../02-api-surface.md` and `api-endpoint-index.md`. +- New ORM model/migration: update `../03-data-models.md` and `class-index.md`. +- New schema: update `../04-schemas.md` and `class-index.md`. +- New parser/artifact: update `../05-document-processing.md` and data-flow diagrams. +- New model provider: update `../06-llm-layer.md`, security/config docs, and tests. +- New evaluation metric/provider: update `../08-evaluation-flow.md`. +- New auth/permission behavior: update `../11-auth-security-observability.md` and risk table. diff --git a/docs/migration-guide.md b/docs/superpowers/migration-guide.md similarity index 100% rename from docs/migration-guide.md rename to docs/superpowers/migration-guide.md diff --git a/docs/setup-github-auth.md b/docs/superpowers/setup-github-auth.md similarity index 100% rename from docs/setup-github-auth.md rename to docs/superpowers/setup-github-auth.md From 170030a041a9b49141e475fc83ad3422d5aec4e0 Mon Sep 17 00:00:00 2001 From: zhong Date: Fri, 15 May 2026 00:45:12 -0400 Subject: [PATCH 02/12] Enhance documentation with visual workflows for backend architecture, data models, document processing, LLM routing, extraction, evaluation, session sharing, and authentication. Added diagrams to improve understanding of system interactions and data flows. --- README.md | 8 + docs/backend/01-architecture.md | 4 + docs/backend/03-data-models.md | 6 + docs/backend/05-document-processing.md | 6 + docs/backend/06-llm-layer.md | 6 + docs/backend/07-extraction-flow.md | 6 + docs/backend/08-evaluation-flow.md | 6 + docs/backend/09-session-sharing-groups.md | 6 + docs/backend/10-template-system.md | 6 + .../backend/11-auth-security-observability.md | 6 + docs/backend/README.md | 52 +++++ docs/backend/appendices/data-flow-diagrams.md | 2 + .../images/auth-observability-workflow.png | Bin 0 -> 74734 bytes .../images/auth-observability-workflow.svg | 110 ++++++++++ .../images/backend-runtime-architecture.png | Bin 0 -> 78188 bytes .../images/backend-runtime-architecture.svg | 145 ++++++++++++++ .../images/data-model-relationships.png | Bin 0 -> 99915 bytes .../images/data-model-relationships.svg | 132 ++++++++++++ .../images/document-processing-workflow.png | Bin 0 -> 102002 bytes .../images/document-processing-workflow.svg | 129 ++++++++++++ docs/backend/images/evaluation-workflow.png | Bin 0 -> 90755 bytes docs/backend/images/evaluation-workflow.svg | 113 +++++++++++ .../images/extraction-grounding-workflow.png | Bin 0 -> 85518 bytes .../images/extraction-grounding-workflow.svg | 111 +++++++++++ docs/backend/images/llm-provider-routing.png | Bin 0 -> 79047 bytes docs/backend/images/llm-provider-routing.svg | 114 +++++++++++ .../session-sharing-template-workflow.png | Bin 0 -> 72251 bytes .../session-sharing-template-workflow.svg | 108 ++++++++++ ...summarization-tool-global-architecture.png | Bin 0 -> 120372 bytes ...summarization-tool-global-architecture.svg | 188 ++++++++++++++++++ 30 files changed, 1264 insertions(+) create mode 100644 docs/backend/images/auth-observability-workflow.png create mode 100644 docs/backend/images/auth-observability-workflow.svg create mode 100644 docs/backend/images/backend-runtime-architecture.png create mode 100644 docs/backend/images/backend-runtime-architecture.svg create mode 100644 docs/backend/images/data-model-relationships.png create mode 100644 docs/backend/images/data-model-relationships.svg create mode 100644 docs/backend/images/document-processing-workflow.png create mode 100644 docs/backend/images/document-processing-workflow.svg create mode 100644 docs/backend/images/evaluation-workflow.png create mode 100644 docs/backend/images/evaluation-workflow.svg create mode 100644 docs/backend/images/extraction-grounding-workflow.png create mode 100644 docs/backend/images/extraction-grounding-workflow.svg create mode 100644 docs/backend/images/llm-provider-routing.png create mode 100644 docs/backend/images/llm-provider-routing.svg create mode 100644 docs/backend/images/session-sharing-template-workflow.png create mode 100644 docs/backend/images/session-sharing-template-workflow.svg create mode 100644 docs/images/summarization-tool-global-architecture.png create mode 100644 docs/images/summarization-tool-global-architecture.svg diff --git a/README.md b/README.md index 2086142..661bde9 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,14 @@ It supports: ## 🏗 Architecture overview +### Global system architecture + +![Global system architecture](docs/images/summarization-tool-global-architecture.png) + +*Architecture overview. The platform connects document ingestion, the React user interface, FastAPI orchestration, parser outputs, LLM workflows, evaluation, collaboration, persistence, authentication, and observability.* + +Detailed backend diagrams: [Backend visual workflow map](docs/backend/README.md). + The application is organized around a small set of core services: | Service | Port | Purpose | diff --git a/docs/backend/01-architecture.md b/docs/backend/01-architecture.md index 27d6e37..80236c4 100644 --- a/docs/backend/01-architecture.md +++ b/docs/backend/01-architecture.md @@ -20,6 +20,10 @@ Router registration happens in `create_app()` and is intentionally ordered. The ## 2. Runtime layers +![Backend runtime architecture](images/backend-runtime-architecture.png) + +Read the diagram from top to bottom. `backend/main.py` creates the FastAPI runtime and installs cross-cutting middleware before requests reach routers. Routers stay thin: they validate HTTP inputs, call service objects, and translate service errors into API responses. The service layer owns orchestration across SQLAlchemy persistence, Azure Blob Storage, document parsers, LLM clients, evaluation jobs, sessions/groups/templates, and telemetry. External systems sit at the bottom because the backend treats them as replaceable infrastructure boundaries, not as dependencies that routers call directly. + ```text Browser / frontend | diff --git a/docs/backend/03-data-models.md b/docs/backend/03-data-models.md index 49d996f..a7ebd34 100644 --- a/docs/backend/03-data-models.md +++ b/docs/backend/03-data-models.md @@ -2,6 +2,12 @@ This document describes the backend physical data model implemented with SQLAlchemy models in `backend/models/` and Alembic migrations in `backend/alembic/`. +## Visual overview + +![Backend data model relationships](images/data-model-relationships.png) + +The diagram separates the database into five operational areas. Better Auth owns login identity through `user`, `session`, `account`, and `verification`. Application workflow state starts at `app_sessions`, flows to `documents`, then to `extraction_results`, and finally to `evaluation_results`. `eval_jobs` is intentionally separate from normalized evaluation scores because it tracks background execution status and polling state. Collaboration is handled through `groups` and `user_groups`, while templates use their own scoped records, version snapshots, and optional per-user permission overrides. + ## 1. Database infrastructure ### `backend/models/base.py` diff --git a/docs/backend/05-document-processing.md b/docs/backend/05-document-processing.md index 005cac9..fd6e1d1 100644 --- a/docs/backend/05-document-processing.md +++ b/docs/backend/05-document-processing.md @@ -20,6 +20,12 @@ Out of scope: - cloud provisioning for blob storage; - internals of the remote Docling service outside this repository. +## Visual workflow + +![Document upload and processing workflow](images/document-processing-workflow.png) + +The workflow is content-addressed. `POST /api/upload` validates the PDF, computes a SHA-256 hash, and writes only one global original artifact per hash. Processing then uses that hash as the stable document id. A strict cache hit requires `processed/{processor}/document.md`; metadata-only or partial artifact trees are readable for inspection but do not satisfy the processing cache gate. On a cache miss, the service hydrates the original file into `/tmp/summarization/{hash}`, writes parser outputs locally, syncs the full processor tree to Azure Blob Storage, updates DB metadata when a session document exists, and returns a canonical `document_view` object for restore/viewer workflows. + ## 2. Main classes and files | Class/function | File | Responsibility | diff --git a/docs/backend/06-llm-layer.md b/docs/backend/06-llm-layer.md index 40781a8..9157c11 100644 --- a/docs/backend/06-llm-layer.md +++ b/docs/backend/06-llm-layer.md @@ -20,6 +20,12 @@ Out of scope: - provider account provisioning; - exact model availability at runtime. +## Visual workflow + +![LLM provider routing workflow](images/llm-provider-routing.png) + +All text and vision model use goes through `LLMService`. The routers provide the operation-specific context, while `LLMService` selects the provider client from `model_type`, applies timeout logging, normalizes the provider dictionary, and records session metrics on successful responses. Provider clients own SDK or REST details, including retries, model-name translation, structured output support, local Macbook serialization, and OpenAI-compatible vLLM calls. Downstream code should rely on the common result keys rather than provider-specific raw payloads whenever possible. + ## 2. Main classes | Class | File | Responsibility | diff --git a/docs/backend/07-extraction-flow.md b/docs/backend/07-extraction-flow.md index 4e6dfa1..7c93e96 100644 --- a/docs/backend/07-extraction-flow.md +++ b/docs/backend/07-extraction-flow.md @@ -21,6 +21,12 @@ Out of scope: - model-provider provisioning; - evaluation of extracted answers, which is covered in [08-evaluation-flow.md](08-evaluation-flow.md). +## Visual workflow + +![Entity extraction and grounding workflow](images/extraction-grounding-workflow.png) + +The extraction path has two distinct technical phases. First, the router builds an enhanced markdown context from processed document content and available figure summaries, then fans out one provider call per entity. Cloud models run concurrently behind a semaphore; Macbook-backed models are submitted sequentially because the local runtime is already serialized by a FIFO queue. Second, structured references from the provider are matched back to parser raw analysis. Azure matching can use paragraph, line, and figure metadata; Docling matching uses paragraph/page structures and polygons. Persistence upserts by `(document_id, entity_name, model_id)` so reruns replace the same entity/model result instead of duplicating it. + ## 2. Main files and classes | Component | File | Responsibility | diff --git a/docs/backend/08-evaluation-flow.md b/docs/backend/08-evaluation-flow.md index f73ec89..a025e1e 100644 --- a/docs/backend/08-evaluation-flow.md +++ b/docs/backend/08-evaluation-flow.md @@ -23,6 +23,12 @@ Out of scope: - external DeepEval library implementation; - provider account setup. +## Visual workflow + +![Evaluation workflow](images/evaluation-workflow.png) + +Evaluation has a synchronous path and a background-job path, but both converge on `EvaluationService.evaluate_extraction()`. The service creates a judge adapter, selects built-in or custom metrics, prefers a combined JSON scoring prompt to reduce judge-call cost, and falls back to per-metric GEval scoring if parsing fails. Background jobs flatten `tasks x providers`, then use both global and per-job semaphores so multiple users can make progress without one job taking every judge slot. Job status is kept in memory for active work and synced to `eval_jobs` so polling can recover across workers. + ## 2. Main classes and files | Component | File | Responsibility | diff --git a/docs/backend/09-session-sharing-groups.md b/docs/backend/09-session-sharing-groups.md index 5e3bdb6..3659c68 100644 --- a/docs/backend/09-session-sharing-groups.md +++ b/docs/backend/09-session-sharing-groups.md @@ -18,6 +18,12 @@ Out of scope: - frontend session-history UI; - template-specific group sharing, covered in [10-template-system.md](10-template-system.md). +## Visual workflow + +![Session sharing and template workflow](images/session-sharing-template-workflow.png) + +Read the top half of the diagram for sessions and groups. Session APIs call `SessionService`, which converts DB rows into the Pydantic session aggregate and builds restore payloads by asking `OrganizedFileService` for each document view. Sharing does not copy session data. It sets share metadata on the owned `app_sessions` row and allows reads only when the requesting user has a `user_groups` membership for the target group. Group role checks protect group administration, while shared-session viewing only requires membership. + ## 2. Main classes and files | Component | File | Responsibility | diff --git a/docs/backend/10-template-system.md b/docs/backend/10-template-system.md index e304b60..80166fe 100644 --- a/docs/backend/10-template-system.md +++ b/docs/backend/10-template-system.md @@ -19,6 +19,12 @@ Out of scope: - frontend template editor UI; - prompt quality/content strategy. +## Visual workflow + +![Session sharing and template workflow](images/session-sharing-template-workflow.png) + +Read the bottom half of the diagram for template behavior. Template APIs call `TemplateService` and `FolderService`, which enforce user, group, and global scopes before touching `prompt_templates` or `template_folders`. Updates snapshot the current prompt fields into `template_versions` before mutation, so revert creates a new current version rather than rolling the row back in place. Explicit `template_permissions` can grant user-level read/write access, but `is_immutable` is evaluated first and always blocks edits. + ## 2. Main classes and files | Component | File | Responsibility | diff --git a/docs/backend/11-auth-security-observability.md b/docs/backend/11-auth-security-observability.md index 973bc91..09c3c59 100644 --- a/docs/backend/11-auth-security-observability.md +++ b/docs/backend/11-auth-security-observability.md @@ -21,6 +21,12 @@ Out of scope: - OAuth provider setup details; - cloud IAM policy design. +## Visual workflow + +![Auth, security, and observability workflow](images/auth-observability-workflow.png) + +The diagram shows the cross-cutting path that every protected request follows. FastAPI dependencies extract the Better Auth session token, prefer the `Authorization` header, join `AuthSession` to `User`, check expiry and optional email allowlist, then pass a compact user dict into the route. Authorization is deliberately service-owned: session ownership, group roles, template scope, and artifact path safety are enforced after authentication. Observability is attached at two places: request middleware logs request id, status, and duration; provider/parser paths record model, token, duration, and cost telemetry into in-memory metrics, Prometheus metrics when available, and session totals in PostgreSQL. + ## 2. Authentication model The backend does not validate JWTs. It validates Better Auth sessions by looking up session tokens in PostgreSQL. diff --git a/docs/backend/README.md b/docs/backend/README.md index fcc30eb..03c3f9c 100644 --- a/docs/backend/README.md +++ b/docs/backend/README.md @@ -4,6 +4,58 @@ This documentation set is a layered Technical Design Document (TDD) for the Fast The backend is not documented as one long README because the codebase contains several distinct subsystems: authentication, document processing, LLM routing, entity extraction, evaluation, sessions, groups, templates, storage, telemetry, and deployment hooks. Start here, then follow the module links for details. +## Visual workflow map + +The backend diagrams are stored under [`images/`](images/) and are embedded below in the same order as the detailed module documents. They are intentionally implementation-oriented: boxes map to routers, services, tables, providers, or storage paths that exist in `backend/`. + +### Backend Runtime Architecture + +![Backend runtime architecture](images/backend-runtime-architecture.png) + +Runtime layers from browser/API edge to routers, services, persistence, external providers, and observability. Details: [01-architecture.md](01-architecture.md). + +### Backend Data Model Relationships + +![Backend data model relationships](images/data-model-relationships.png) + +How Better Auth tables, workflow tables, evaluation jobs, groups, and templates relate. Details: [03-data-models.md](03-data-models.md). + +### Document Upload and Processing Workflow + +![Document upload and processing workflow](images/document-processing-workflow.png) + +Upload validation, SHA-256 deduplication, parser selection, artifact generation, blob sync, and document-view output. Details: [05-document-processing.md](05-document-processing.md). + +### LLM Provider Routing Workflow + +![LLM provider routing workflow](images/llm-provider-routing.png) + +`LLMService` dispatch, timeout handling, provider clients, response normalization, and cost tracking. Details: [06-llm-layer.md](06-llm-layer.md). + +### Entity Extraction and Grounding Workflow + +![Entity extraction and grounding workflow](images/extraction-grounding-workflow.png) + +Per-entity LLM fan-out, figure context, reference extraction, bbox matching, and extraction persistence. Details: [07-extraction-flow.md](07-extraction-flow.md). + +### Evaluation Workflow + +![Evaluation workflow](images/evaluation-workflow.png) + +Synchronous and background LLM-as-a-judge evaluation, metric factories, job concurrency, cancellation, and result storage. Details: [08-evaluation-flow.md](08-evaluation-flow.md). + +### Session Sharing and Template Workflow + +![Session sharing and template workflow](images/session-sharing-template-workflow.png) + +Session restore, group sharing, template scopes, permissions, folders, and version snapshots. Details: [09-session-sharing-groups.md](09-session-sharing-groups.md) and [10-template-system.md](10-template-system.md). + +### Auth, Security, and Observability Workflow + +![Auth, security, and observability workflow](images/auth-observability-workflow.png) + +Better Auth session lookup, auth proxy, service authorization, request logs, metrics, traces, and session telemetry. Details: [11-auth-security-observability.md](11-auth-security-observability.md). + ## 1. Introduction ### 1.1 Problem diff --git a/docs/backend/appendices/data-flow-diagrams.md b/docs/backend/appendices/data-flow-diagrams.md index f62b474..ac1ac54 100644 --- a/docs/backend/appendices/data-flow-diagrams.md +++ b/docs/backend/appendices/data-flow-diagrams.md @@ -2,6 +2,8 @@ Text diagrams for major backend flows. See the module docs for field-level details. +For visual architecture diagrams, start with the [visual workflow map](../README.md#visual-workflow-map). The SVG diagrams under [`../images/`](../images/) are the primary visual companion to this appendix; the text flows below remain useful for exact request-by-request sequencing. + ## 1. Authenticated API request ```text diff --git a/docs/backend/images/auth-observability-workflow.png b/docs/backend/images/auth-observability-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..3223942dde5e84289befeb8a0d904fb1c44ed728 GIT binary patch literal 74734 zcmd?QS5#C@6D|ybB3UIVQ2_~(Bxgn?i3mu}L2}MRPKttn9$;+%EXxjARw>|Wi~UDegqyJ|nx9rj6Co|u4|01FF?SmC{l8Wz_5 zyCRO-!+UqH#zz;HSXhs+6lC6Ncx4cm z)1Oqemfe^05>9d^?LGM`r!^|9n;SIGJ;3v4L;9k>^NlUbzvD3m?|*rj*ZH~=bK_s- zot0`8vYPTa3SkE|x6E32j#hWu^Vv#qM5cJ4DCCn*vx`Z@pMJcH=oB$;_vECWFPobj zacR2Sb%V~KE6;t^UH8{UHH{s$rnBX^vUVuRXvBG?_Vh@&xL7U9NGRX$-ez z{fDI^f2XHMmAL(h*6u8Lw@1%Bqc`It+6h8#0!nnwYVUi}TfD{HWV)~2vbC~!&{>b3 zQSx4(+;)JD4lgpZ&tk#$Q{{V~X5(}Z)}HZ5Vno`1imwUHKVX<=`5WDeU7J6%jBi794wqDuwjxHUH! zhD2x7OEFcWI|%XIj}Ojr4a+J3h_RwRfRGpR8SXEw84j0z<9xjuSi8Q2aNxQ=ota;j{OU`n%eQG-)pCHhhPMCqxHo5t3=D7 z!<-mk-sT_gQ0@_RRLRCe>&eP{#RGSGlA8M^_A0M#ArUP8K8b&4 zgVpjBL=?nxxSYxg{y`mgqJf993Kc$f>(dy3XPloUNB2P_+cH1rEjGL8V6Gl#;s+TV z4I%9)5D@?=#L0?eAk9jll9}FjzjOi8eL?MhHl%Acnp3mq$=Nj`pT?e^8YOXzx!LF_nH!qqpAIv=0Pl~3KVEzcv zXKcF&+&whZMy3qhPFX7j+TMylCl9y=al|~$j%Az%HM2t|{Z_KU2WUjL#Xi#8$O&Z* zJtmC}3^QDTShuLo&B+e1q_mXFQGM9Sf;JyHwDD(0#vG^aa;R)je~)-I=FT;7fLv63 ze0$lnODX5MD-{{`x?aGO0Y%WGH_eh))VQ;@pn+6oQq8VNTlv**UDt;rg|gSHxy|gh zn>uF{~wG58erJsNwv`|L=?C&7`ZQmuu&EH65uIzBIP0;EgFMpuJ z{ckRlBIFhgfwFCln}u4hC={B?48tm?_i%(0rb8Rf_7@dHOMc8Z0|0roJ70rNwe1Z8 zEHnk&m`iPwUPY@?!S-L4e{%?Fx0)t3g6fgE%;4NDi9rV4gU;gFib=rZ0#ADx#U}#H zXcyGBuKwlpcD`97l%pRYWWKV6p?t_XU3ec*2o|nDaHQMvo4cfsbBE7`8XuvCwyg-c zQqK2^uGsXlqNC9z5QUc4nBoRm7w}BzEx3`RRdGJ(3qM5S)_C*HuVj_LT28RCC)cv} zX)Hg2pcy`Uw6#(*$TJuk-I3WOtS|bjlF65Q*I2ff=<>x(qA1|oQ7U^xM~?V;lfd+& zMgDY5);M^dBb_@n9dt396=oQ!tOvY~t0`P~*QQuZMAtM~Qhy3|JC>Dq&wdBFS4$at zW5D%cC0)L3yBM6l(Hzz6wR^Z4sq}Hd#kMQ{Z0?y&@kbfyg#)f?3Fr-+r_Qy9+7+WV zd)@N_CxqHDLI*XeY&?0)>#%xwn;lh!5mA3^0vo=4&1i4E1e@aH=D0kh;+F?p+MkiQ z391w_HvWu?j8k&4f!PG#H$O-wW#X9*y=Z1^$t{?j8me~UDZHOtH@Ss!*@>?O)rE-` zCVND_Mu*!V^XF2tCv1s2rfxHB9#DlG(JIFqGI%>)eUiW<{JT!+sQaC%zQX(Gaw>fn zPRbbkU4{ucKyP&V(u*=Rl0=W^mU<4}`8v*7c>UtzXcud)vg(_A>zFIg_>$ z&*k2Ba42gU?UlB#kYK*HQlsQ?*bZ|DCvGm)krML;F15J?m=@iV^TjjB+G0M;VwjWk*NW!)x z?xYF7oUhCt9fT-l|H5fY$!f8v#-`h-#@y7~%8b22{&sIvn#e-kC{9K1cuuA^8+~Z7 zUpn)Zj{`*-Ii20>It25`zhvM`#L}*O-+|LNurEEE>nBxm#YYIM(k8Lp!DL&d z6<21H_wT%+_Bd4K-0A3vUkd|6`zcmW5B+8chBaN+&Sl8UpH3_%G}f7%Pa1X^y!53U zmUkqKQig&;7i~h7?UBv__QELHa$jHcnLM-?+hs^yKh9TCyxG~wwan+lCXW$G6q&Rg zM0%VE5IHQC_PsFH$mg91zaC#p-9ctotWgi=GLRN46V4qs?3ZSz9x(geSk2CX(kW!Q>bZJT>tVkn=8Iw!qnUDTOBjCMQAITU!J_ zkhXJxoActsLGd*vW2smE?qcf@b9+TKX(dK&TBEtMXr9u|2W}ptVX#(CMvGx9v%*Sl z(FPGsn09-2y77@$BOSu-JXaB7;BND#u0}OT`t@*n5i?Nt+p~`Rq^nPw_UX=u~5W77%GiKbz1Fk~6#^efmlf6>n6l z9^4$j>G4D=Jt<&6x*LfyjPrgUn8!dX4%by(n2nLQNZ9Cn4x1 zWkcl4Mwdjy;1|WQx9AKA4|0&*xA4)yrHVi#ELQ)ZH5w3O9PH(HvNdw~jHHXh87zv6c0f!wM7m%EIQQ1k_8G)%b0%h@OR##syeZ4u&PaRaNvS3? zY_*oRnr?+%xy}kEI*DONBbI)<`}nt_%iyy)6YYiVh!KhJVRb;4{mp_JW}+! za;AJXVchtr;M9$wsG9nf&}o-sHA!{?i{ZU17o{}IwFEIJ=1D}9>f?B`#flrXTke1* z?~T!(iCa(Yv@8$3w2G09`Ko`+WCp7Jpv4?k1-{mfR-vr`q#r!t3dH(^9<8KO0PiGc zOd5x;Q%7Irx8J-dJ-nJ$%JLECLQQM)5Ua8z<2Fx`<(mSMePZdCITp2-i+Y>ll@{9V zc>Kk9*V&q@tBz3yfh0LI=+`p%R1wHWevSH*q1;EBw`*NqNRi zQP}6O1eo>p`Llq~hKDi6nJt;r47_HF*Du%QoPlFfs6^`Q`K#PBTg1oamC576?a>n0 zd#5r+S>>3+s!9LQ9+JH@@czZz{e>Qq*s~QJBmd|-DUX(0fvB}y|IpcX%;kls%~{7? zT$F0tuhM}+e}dkx>XXJ?hu?k}R)Nh;#TSb8sKgik;BN}|JWHlssDMZyuBNY|{#lMq zs3w-5)wUS4Mex^S)wA>cQJjz=#_)Z8L>4PNN4h#HoUQ5_hfA}UwE(kkH=5PqAp#6= zFPSI?jHTCp2Y^Pv+<--?%y~_zrw8gd7K5K%*+bz^A%4mMNepD-Irz180X$09(tAl~ zQTKn_8O&hmgy3jb;?~j4-OtAltI%6P zU*xm*@=BgzfBt0~zsc?hqI+)1`O<;D(nx-Xa%h*wP{>#Q2u|CpqZBPwxDfGf_WYfR z^Z7}ozQMLzFvvOVa&i_SCkJ$zd2z`T{t8a8v`k9Zb?ZQ^&?pe#twF8+nk%XAQ zkTb-c;T26z*3zqz;#Fi?bSme*L3c^Uf3W~!?iCWlI;ZLYm=qa}`dsAWzu(BmP<1T7 z{c&ofp`W+0+>lDbVo!AM5pb|Seg2zM=s;<9(Xs@@ae;qGu#TkUz&N<0 zddvBS3lS$_63{-J-?O0S?x>AmI=YhU8TeY&t>5~kzs`K{j{yoztM5!Ef|G-k0FU3Hcp}!HMbO5(@za0N0p0*>ZP4%J1&9Wb+BLm)Unk7=)MS8 zK_b2#eO~{*LhxX$MMzN)VOzxrr{*4Rz_f?bcy9K;}ufR6?e7b{ev2&%d2|qSo72&&M8hXHE97=6p|WEAycE zWiRVvYhQs@NB>ej>*TRLbFP(2I7*4lfyB4UHUoGz?wAJdZNVka?Yv&Dl`&c@zi3cC zeqKVd`AH{q%9`XV!JhV@1b<=#|4s#1^Ha}tsOGfJv0oSJBZ?p}$*+N@*8L9c?{F7Mku%05=(W(NGU9gV@u%pckWZt?s(*w@?OUr~q&n>a#C_b`RRC z#vU}r&Pecip^+*x@{h|?t4?dVX8qYK&UaRco-mW%_Pvh|zVqJRkPGzsxu-w79gp@5 z$cW~TBjcxUEml*cgGNBFJV*J?GWy<6IyVSQbD>Fq3b&Yt%(7?SUu`8Qh==HqKl`8+BX(c0WT$iP zeAs;bOI45N6V$t=lzv}?I_Irhd^Ao~?5K3#9x36juhtNK(4E@J&9rz*4s92{cz7tr0&Ldzgln3V#VHYbGX_aUCY>mvs$vpwlit)oXd z0+V%xrtgcp`9n!1sUORo9e*D)K9~3Ri6s6mxD7;iz!c>R(PLlK*X>TPjCFzH;@gG0 z%-*)uTg`l)Tt)0kYMy#>Kk-?gO@faKeOZAD$4fkAii)oL#oeJ8{rtsfeG+4orjIZ( z*hAqLRP+2n6`!d5`p$G9iSYTK?QF*p)WVj4>D7_so&h9!71AFV zyKp$BQ2mj1DJrs5%L}No$F1u%Y+P~r?9|r&3|(n@8jwXr(a7X+HvKF2vx@u0ptxsg z&4tg_ijsah1U1!8Z3x*BVbSxETR{B;PU6H6;Q*k*w`ON2&4V|mVEv`R`D>_ec>r@# zYr`vaZG+<;UXr|Vs#bTXljcaB895CBhpvdk7Hv{h!9e!Cf3Uu@17<9#Mw{~6Y2#TJ z)i9d<&6@930`HXYsI*(%}6hbfv}*<$S10QsbYj1S`u)z| zk`E@Nt9KpSE!332F7Hks*7UrOAGO@^a;3|PQAz0)2a}oSM?x?8f>aB)dx~(b(-Y0BXTQ z4xct-i@IT}&Pi~wR@5=7gnUlW_OvJFMWdID(h<~6O$KJ^EKH~-9gF$B(&)dcXm%|L zeaE}ykWb0=&Q~6iP=^?AtdEd;dYnwsEu1L{Tr1z;UCpg~><-Nt&%BAu#>`X&KR}92 z`t3|M*gY#fHv<93tA8jnEQkwvuWQ#{=Lv<~TJa;1tRnkrG?z}_ONx5bn5LcUUc01o(j2ACWY5HdfH8YxWYUy} zVlD#&i;D#q-|ew#eR5R6Rbs8fEkB_WQ@M*^_YbFZY>ub`Op9mh*>K@%@uz21hAR5J zA|-pPF@E&fZO7x6-`G%9IlVX5!#t_yj2jN6N*4fa7L2Z{S!;Mt*?!nTWqvogoh|KY zbAz2#QvtIKFe!P2HK#X!+4RX4g9JaM2;oGgRc=OHm5$rjLw!GhZq3t*6XWJp;`O;$ zb$h4TGrO27k&{+>rT9Fd65!}<^TR#Ins&kW>^69NiV|V_v0`}AOk;i{nVhaR`c`VLN@>$aC_U+| z*)fZCA<{6d%QT+WloyioMX;FM9|6Av3#u1hfiy*4S-#FQ{<{tD<92E}?{D+39TrOk zff_JxD!{caDAp{(5PkC#)jGXOR9)d;Iht2a9#jgyZU% zo%h;SfaQ|?9&o+q!V1E9$gm^b0F}SYN|gM{vNw`URK+)N^y^5d2r_}{#!_A%xaH1X zVHodAe=3#AZtHk21oBrYufO>Q{Zh@iONo8UlsguRQkF#7B(7&*inhWe*JH$1sAvT-_rj-;jfi@fA}N%RcO$s&(xT_Z35|}?6m1ieRUaBXr!bBBc=&B zR<0n9!>o${Qjr2+D|xUZv$t5`aA`@%PVe16)TNO7wplIUXq)Vv=P__m^qus?5sHhFI&MN8h+*CcmQt=c$a znfDy8>vu<-zXoflkMHO6z0cPwSat(V_U>oW;bF%b9ul>Mwt?(Gq&xvu=bhM7tSSwT zlnKDewQgR?qB*7aHX2W4b10+^*A4f{x)gxy;Z(FJgyB9$>Q&6Nks#H~ZY8|o_I7NM ztg2GzDws-O%VBk@K+Dc*aZuC+-bjB@S`!(+;1EzfR4?oi#U?J#>!s^}zI)B~eV%2(SAE+0pcXq13O0}XbseLrdA1Cr_^mu_aBV(W< z!n*1DQ|Mn~#X3l0t!1)`vI6e$S86&3)HkMk^a5$4^a&_cud$pwl67o>#V#!ipYkgU2N{@l6mHbH;wg zxzsSs&6I)#DDx%{$@8w(9Qwe+ha*gft;C_`0#spt+g6~YBPn4A98XEcqrV9~D6Fx~ zZ~bv~_>pl~D|;NmZZ#vunSWiVe_iCF|EF2;+GBp0tjA_e)pLsZvMs3M0$YGf_A)-cQYq5s9@1(2tE5Z>APaPgr=byI=4;58O~;JU*|li6qd1It@VtEiW~J zp84qN^bR=}P+n!l@+m{9Hr6nr#s@or*kE(4xp&dxdOTO_eIsP9>q$Wfi&OM90{-1e z=*2Gdl}ANOO!IY?f8e=cFYN7osEek=*HqC04JD)Z$o9R^55e7!7fIits-elIe%CSy znwkEBGPP>(?yvUEY|)}vSP%Z935ts1kLe}T(X)rpoDnOPEmBgUK-}Y`8awdX#aDO- z$mP$>Zw1gS>q%(WcbnBSfCi|ib899#xHowHJ0kgke;e8i{V{DzcK4>j&4edWU5;M- zhcV5?u8-8WmBz?G{`f2hKTbK9(#1BEe}d}iNA7PFa4>EjM%~j?*Y>8D;h10pP06iz zXjW9rXhfHWul5PvJ;vLW%Jog7buP6GhjCg8ht50>>#nDyzQf3DMK@oWahvlHhh`VVF@Ko( zm3rN>@|ad~jneRkMGGC~qo2iwll`E!$DbGbi}zGa%Q(}&N2vK#8pLPF{=BE^4}T4- z)v8B=*WJ0M9>OL=13%6qo<1d)nRa~253+^JW-8BUo7E1MAD_Glcwr-B&!jzDAr+AJ zjgZxWRb@Q>{Uyl9msdCFRRdqin0Enm!A#eCV(mWYH{n}<`P6oe+%}*qQdip?PlXI` zuSbuXn z2j}hRFq5IK`Upf};gU$m5Sw$co1loQ@N9c=@^CH1#xdl(#*OcCeRX^8vV`}6Ql$F7*lK;VONmdrEwU# zE`UPbre3lBLQz%JpMad|(5`Rloz;!n+rv|mY-sM3Opz#6hQRnQz4pJ zU;CNtgYZ=5>>F^XktczjZGDk8MY(|)JJrR_6a2fPYpG(xfqPC9_ClW@1io<+w6$k- z<*E+Pw?tPp3be$bAGp(uGL&`~hZWNqtX6Mmw?47~SCrJpi{Z;|;zla`p`xLsqf>CS zEqiFEfB!U)>t1Md-E$PjBvo7%?unVai;EeB)uI}d+&9JXtBBd_NR*>>i}0BJ(nl7~ zq|2XqUKi09H+7P!jfYsO+^PHBer}6JglB@2bQX2y0z&I}zw|s_@F^IuWBe5?@^8)` zL@Fv!{2rwcuuKTse|%?(Aw(VSP5Br4bRvH5S{ghO_g)nCcjpt2S?*Bf=2E@wVGqtH zV_|D^;~4fBF>vDrvjtZt3S}!ifJMB8mwXN2GT_+K7Y12<sK-L8lRdYOU$%)zCB1fx%;Ia`b6Q6BMhX?3H;I+Y~5 zAC*H4<2!!AN2cspve1*4}3H zqa+^+)UsXTt%DK$D*VgZL*)M2Ru*^%HvRDh9e%HtWi~Aclake?Bm-m9v!2Fk`D-W! zaKdM3DmPI0f^$Du_Q9*^7E=wDfg(1JZ(D^w##6*aI;RgJyb3_q_m%>~#%pYOT3 zrE*3K?$>-BN*;}S&8kza+$!;;QccpD=p4=0R<7wwQ^&(tQtgMT+yPz(AN;IO`~a=O zw_y;pq)fEBXskQ8@s|{?px2D?^xS>|p`M!3s=fQ})cVcY`e4Pv8~JOyTPZn>A4WV) zK5eFP{<6d7GShchxUgtSUH$@ZM`5*ON9$cRE4{;PwASvo70S=b3V6c8*0FAaO-(sImdg=VQp$ekO>*)v-qf$7QKCx))veQj&@2YGJw5EIa|Yu7kIF_$LtTw z^L4}OoPjE0{YHDmfQA6yC@-EjX6*6ST6Wtv5ew;&+;mG9bO_h4fYdhMl@Yme_WA&# z*2F#$!|YEp0W8w$K6GM-Gr#%kLBP!?*4<$iEr3axy@BM{`nMeRru@78Zw=yD4LrIk zJqWz|zWT;#vN^dPnR~OnR6lI3FbaiFL)>Su9I_)k~lGjU@DdM8y>Q$gHyMvzeGX9!SrVM!h6_}&N}zpgmM!l*rNRHk)zdT@AXl~wWH{E zt=3MhweMK*#3R2~HGuup=8n*ym5DQ9$1?6IvCxFH+f%{8X{m}|yYXQ~J;xlgn!dMR zsEt+cVNrc%HOy%ZP*<4D_n9BlU-g`GT=&7PrO~oDJFkk{`y4j32Y<*1V#`+!TPlPp z%!n-1?e`B=$y{7pSE4QJx4HeqLLT~75ctg%sW$rDBM1d8bBO3%UH?>G{wb_ZJ}vx0 zuyiWSVlrQ9Uel@(-B1_hF-cwi9rRd3eVKz!Fm<>Ms1tdsSS&vDkyIsd@0TQ-g_^lm z+F76dr7RoXxMgZM_4gi2N{g#TS1P!L;S-|r(7Ng`NZU`c>Sd-gTa>+$q3e|=gm%z+ zwvl7!)++xNdY`3}KXtzM<2fG!ZQXQoz6eXIl>&z~is!{dlUinB112xfj#l}09A*6~ zQ+JNG8;7u5J1IGr`Xepg&!(>s{G{w&4aI5?!b%#;%S2hw#B^*bY%)D<%3HhJH{^WT zv3k>D6GkqE)NeWG5}njS_%_t|JmsHR`i%x#(u@iV@Ue~7P0ofpV~;8L&`t0FH$y&Ed7Zvf$MJe zj4~EcS?K6FUNluUuZ_bUi?w|QqKxtwy_ZRyEAI)2^t zw}3hdxR|rtCT2DclRWC6;x(ieJwaPCE;%@QY!8Q9{feFKlGUvnl;gB@+`<^utLREI zbseGzma>BckgqNnQkQBqU#IYLL{k1w0 zYq1Sn`~Kk4-M!ySL=%lY-G14C+@d~IGR$1I8D$nsa}WAl zXD#!+W)d@B>GtXA)Cv_VWj|Irxv0_Y*SH7wxyZ70a_2v`*~(_yrhW6LxH+fMaTr_OdwJ?UJPtohQ~sKt2m#1X82g@@DRU#CH{C%wQC`tNw_-c z`opt!0l{SU+3LShx>&?h%=MmqZQyhNY+#0ZD&G&<7=NZd@0-=q2!*c#+73s>eDx)c z_@Fqo&|yWp3@#0=Xi>A#jv;N*eA`C?ENPsXLIyqpRK{PFHr;+#=>b)LeUVwHz0Lye zi5ma`HS;Ew2k197O6Zu^)664lAB@Tf1C$OU%Rrs1?P`llH*$i&ctBg$w^&&TZgP9<6HPV!g@K|L6QWZQN?U$jgfb%)7Whlg(c; zg^--WCF;T>0B!t(xIt+=EG!Yp8*ic~cO3>na%EnZ(eY*|uHfl5i@)$y3m@p8wq^Wm ztr-UrWks)|hkbAV;(@P2xX1q@9(PIXC;uOa>z>I!by&{-3&-cB%@*%6!T;V7_ywfY4;|8LJxx#Ds*Zm#@VR^l)kp_9u}$m^DXdnReSU~P?e^{YxKf19=B|1hba zN=n3NX{hO{Lg!)0eZf6jdoTV!*}8s2y} z?|;rD+yS$(MUj3H^tNGeT}iB2TVNLbQBzY+ z9~74b>E)ZG7bs%AZxuA`9w7D4;lI0How$HIbXJ0Fz74xZS$lN5?!SESjk4%3qz-v{@4o(s3rgZVJE?88Do3D;Jx97ls*-n81h zYmFwNnzA%lNPB83LN%BI@VWkcwgl3rl>0z9u?`!fqI@c120wFov4qzu-mkQ95@DZO zRz9rCnK3ZL9+UhnLuchMlL(oP)O7gqEB%YyCKXIrLGOEB%wwFw~Dvf#l@A^#AB~FC7E|RV~FHV^`6% z7I#qQCI75eiSbEF*1-c zCtW-9P88X=9q9j#ziKa`w8mD7_-5FG8Vm1P(uV}dXWrx#islHOLpX80kbAR$uw4v{ zvMTYt_=oz>Xm-6I7rUmPOyLFo&;LQ2v69U&&rGCbpUqn^MiSZFrpCVA8!=M_Gvv39 z^Vp{4Nz`iY3{;YK`s_##kv-$&U&=+;1@DG(E-*JzJ*i(c=%!9PbXue3EBo#H+*hYI!&^=#bSQIc$t{%frf!#LFI6<0W|sJ1 z7-VY+{g+r2L%qnu1fd>ttM&IsUD$(zs=J+>1_oX)_)0vBov zkQHqaA6fq{H5BK)<)qoz)akOGWp+9<+omkzhRRkrref?BVA;Q~wD-m_AZjqWAcD}n zFjrtt=p#AdYT@g9P`epwzOs=P0rlm9%2Gr-S22u2Yq;wV60WQN51Y=ad8B0LJ04xi z!3uI1e}`sbzr2sbJ86ZPpYmU{`20y<@7A6yuiU@}Q>1vddDSEheE5yQw|*F_B&I~h zPZ3A0l|F3iOSFz98{^}Tx>fGh!ytiU`Nv1HgK9*SfKhlVT^`>{VFEa4oYpbHrWj`pkX9Xris zXA_pC759PyQf=91b6n2*{VV-GR$2J83%zs(M4poH?6PcS?4I?9Zc9yrksj9@tt_FG@Z;FV`!*|BY)I(>prHSV&a4YD7#c?As?L)NwJX zke^(ZkR&^xsJ+E;hVct^2;=nUlF-*_a?&iii-ztVD$B8FCN2pk(Dz-pI!dOt4gFox z^&7fUFL3L=#H7sSy(g~erv{h5FRCK}zr$GR@@&Q&@i-5Ti9J1yFp~P@yBF>3@^%H9 zqLMm00q*B)vG8_1#|f#$Bd>ftA3rDT=lV`>;H-hYu1*_;%OquEkG%MY?Voo`9~#6= z1@HV2{gI>6tv`Oy{WIuD_fjvh?gGOx%gf0R&VjgLOb`j~-r?eUpNM0@om{I8+`7zyn}#0x<9} zIBi#7mK=WKKRPD z#7lT_Uf$VdwXEh#bp>|c{MW}+4))Z9gmn0>})jCZ#6B8{c#4^hp z$)gFJqFqUaaXU>2HxrK*TuBW@_bc{#Dv5qlNU;^NG7_WO!)8Rhv2N{FyUUUHFCHGP zZy)a-?(c3!jL#)bu<2-PqwMP{%QCE5C#8Dk!-baWVXJoL}Cw0>%! zrgip7Urk3%W=v?|{(Xtb5^NVrHAVarxYNPm-1rn#F3&U(aWKQq2ZF}l`^w8|dRGAZ zHSL3A^{v&YaVq=D_AYSA^uAFo>@e@a=-kj7ihF=J(Nv~{%l7E5Mx zvn?*qc(~!EgTAoc=?{R5pPxgqi_>|?h=l55Zd5|zK0$bZzmC?E$u?a@R$KN+uzxT$ zu2+8H8#?yNahvd?70ikj5v7QtqRrMitV&h!Zu_G{KFu8Vj4kT9fhh*sw(ot&E;%%z;G&jP zQ+VZeM(9TeDHJ%jneT%+lf*O*aMD1Zo@Z-P4Ec8)d>UscA%WcaO(R}$@Ix>*MC z7(xzLH>YwQkGkd%5K{~1Yi_!((5=;Q8$%BFH>d7xUzjLy(FmTOp0%$1;VR5cR-wV% z9eogdfyO4L?I>J2L$J%<$<870r#VDwfy2A`;Tt8<&sevRuU&kGtqgg_H9xa4ObdL= zl^{eIPF|y7IS1+8WM}8vSD6Y)R@A;y--X)cgpT9Cf9Zdaecvg0&!qnyeXF*f)@n{A z(ZLt82b~R)UN-_KC(gO@w9*b@P9@iG`!!1ri|{j`U`; zCUO9219=Ud9@LMnzEI$V+s&<<8OtMDO9Q}@+1(5Rq@4!xh+xgb3C0YDm=G(ZN6o~~|b4Wp&d~vwfhf{t8 zZmm=QwwRpgA$24(aFgURG<()V*u3#z`Lw549O&iK1MwKyc@wB0rA(WVo_;5rS~-L; zyWia&?%O+NJQQZpRlc}@A;g})9QuN zR`ESpLfd-`{SD`H`zJTg+5yS)rAsr%)2jG^8C?zs31+zVzn3FL{!02KVoj)6mrQYe zW=D|QjXEbe_2--AXS9k%j}41uK!jM+1#@om-kjch!rvsa+tyXOlj&2JV-M2zp573b z)t%2oJ>C~AxV|!}t6JY)4@9Cmy+wkoLw(m~- z`vzm_7%pz$z%oId7|tKeKsF4lwpjDOSis4sU5_9g#)k*H-_vhVYK<-Ga6x)6)^fzx z-S~TlsQ6bfK~%Vn%78FFe+;YhCpt@pc4Q!x6B_mlnR~EVSczAbEiTppB^Y7#M9GUo zA#~fv9RrCcYa=S5rwnvH>tjpu%v^!C0kQj?$;@@ONmz*q^4PWZV#wC<@pGC#hR! zT+%O}FxVfY+wxgB6lpy0ed9Cb$7_CnRJ=W2X!No+_$$)pD@(_SX|&{G46cX$TWM4A zx75Yo`jRLiMc6%|4FM5@JfkFM?a=Q|4h+AqOjVVKD3uoP2s&1!y>`drHi?L51tL(o4=1fJZuX8| zCCf^S`(rjT5ZlkTL)})Q<5_Yhj%yG+gHn5=l^o2rGI*D1a0jh#KeUmL!0SCF&!JCl zey+xKZftW1;d3-+8Erfx|(0k@=BmlW`Gs^j6fM0hU7W%pfaqQNhG9KjH6L3pm`h%HB&( z?@uCe{`a6HN}E{&)E6mFM{GOk z&Qmimb6Z&x%q@#qJVc?^F1xSK%{us-UTcq%a^B8X=nma)2z;Mdx$r-zd&{V{qW0}G z&;rFu3lx{)r9deZ*8-(D6ev!y;_ktvxO;GEad(FVcL=V*-6;-1CT*W*-kJF_v;J$% zS~KTUlAUwTcDeWcyEYj(nt$yF2PuL!0NpHRa4(D#x&ueChh$fg-OIi%r}IbSOEVk% zI++>yHfbYtFO^C}#9_DJ2z~sRW59YSo&`30ZaJj?D3s~8I*vp5j`3~mmaevnnJ^px zH@!+NVO;=|_x_+idR#r*4&j&?2b*IGfjZE9#UOXSy7pL=qbtz4^BPa$RvP9w@BTz8 zlkNJdkFl(8o1q}j^Uhbe=lS0V!DJ=6lG0BLK`q5SiUJan!&AMSXUr!|YVA$`*nh#nLRO%F9uz><0lZaz~za32B+jJSG>@bwR`f;UI& z<$rs|npF_;u-!jiA22ObB?o$^w8B(y*{?CJQ&_*IVl2BS=h@=q+K!cbtfi5=?;0sK z8CF6+V7|;HW7kC;2aCNfWD+IrI>hDw>fKc}mvW>nXv?6JR8 zzy1QLx%GFqAgP|A~ynY*WFokk%@(Spe{DLF)WpM*{wTV16vtjz+ zMYfJ@QtQnN-0tGC!F!ZPH#@U-eeBv4Nh*@ZtGb|;H=5V z{aA}F#wr?q1!hUyGT1dzBR?8st9TPb!WSBaMPodIoglRC$DKkz(ZzR*!3!-;WEs#l zfA=`_U?%LIcd{*Hu$K9QIk|Ej)+I#0^b*t$G(LKrlgN4+u#q3Wu8HBDS=uem8HA&% z5vjKP*tlmvWc4E@vs_P`2(4F?og@b1<|p4ko5}U+u=PCKCET zv6!#7b#Np-iv1ceKDdje-LJK%1rR-Cf^?%v1b*NobbsQWWdlEc*d^|bd>9Vut~95< z`~i;h^u5R9;&y&5Y`45^n)Rl;=J=WS`wS0PB?tRWcEYOhNv3@PW?C0k*y}^BYF*QL z)#*pxgDY*LyDb{L`^>2sCR0k@@cSi!k~lBapOPCJre*u=nWwpxm2QgS{PYetOPA|) zCpM_{N7M^X&%IGAm_{ziR+>Z|a+n1zQld9XIQ4lsE~F=NPH{Ns9X^T965l#%tzuJ^ zl%{MAu)-i5XvJi6`_~AI6!iN~f&fO`xBv!EaF{0EAlmGK^){ta^l7cILEzc$We2I+ zxo_GQ2C$=Tyk$x*O?of}cwfq?pUg4byqu-4S2!2N?4%st zyMTsgD}b1j$WoSHFR>=yh_z9Vzx00DF}Y|5fv|gDjTE9L&)zXDM<1pPi6JZc$UHkr>Ky_ne7%^Ae0d{O=EiJen0IlgF-%p1QV8I?zkgITKcwOq1y zVQ_uQun%q2KP9+lS?%sjNcaZ$-d5|3a%{EOrSO;Q_v;f=c`p>!ws3_~y%}x?vwvv} z{c#{!X~Fhw5PN#gWxN^1?=ncj&P<1ZR9AH*7p(KapL$H7nmyU$@B(lWvnNi z8chARCyuFFtv7_c5h$DC=yeoZA_W2=0ZK54S4j3=4R1>bXd877)sR0ZSfkLB`U3B5@P>S&H8$e3*sAhvmV?32N494h413 z)?IQBD+^9apw+X3DGJ(t=`rkSVK>i1clpnOTqw2M<$>PSlEQt;YkZiHZlUZ3otWFz z$cdQ>#X^5cEh6Nil=Tnw+x)n?8h0@+#P==+=>o8VP^Am0m?pkzS)1C>b_HfAP8cD1 zn+8_!7JC_72>0_>0yrS-3U+yu5B^@ARFr^e3Ed3N`CTv+^>Ptx@dhrjV>2Z8NNCr< zoUr{?a7l>PcJz}z!F>{Rtzi_E8&ylP!P{lqV0A>NQsO+A%2(wG+t}bOgxk3nz5W!{ z$t8`6N;t>)!IzWw^FBUYfKqz>sL;4x$Ilb@R+zLuy?H}|>-6N%| z;ff@eD#7q_DDODsW96;yOhV9U7+0C*CS+bB&6cEbX2MJr7vvwAAuXkZ!R~NG8 z{UlhQ>UpK2ZaJ~2^vysZu=~k<`^;QRk$A$^9y^e4CW;SuWavrYwqjq1S33(K0UQ93 zS5k2KD0H>C;+s{HD48Xe=VoacJvJqLpND6*FB1<&M3fv$S~^eB;8)YCQ%Ih>sx%}f zm8Kt~3~&1T-9~^Qtduj-1Y8V61kkUm7k=L{TT2$~3XRx?dftqxXjJbC<4_RbS>VA1 z9M*Y(O3$w+P3mkvuq-1bUHkyQ_Mm<0Yy8>wDD|42QU%-vO>IlJkrCmnZ%Qn>dY_af zjJ|l8@+m-yCZG5C3?lhd82X>I7s^C0z3s`4^h#;WZgtx-7=X1#2X zLEz|MBB|%z6?uSSd4o{ft#4QW?fcBFS3lJ4@J%bB)xXrL8AX&$&IHrUgc`Hk&tb?n zl&ijN+Vy)`h5X%YnL+4yCw^bMJ@_eY(2$%ZSotbXtb6`j~#f}V#DnH z$TA#bpH1goqj_n3`9tIG#Q>C9Z!Fug#zY96Y`nD9Qr%bxT~;%Q5k6Z`K2sAdRjKXB z@=A*ATDrEtZCF>QyEK?mHY}ngH%ai|suUeCix|~73Gfs1h?qkom}~ofoXz*;!t@kl zg)m2mnN5sUS_5{OJA9PMV9-Kr@hBto`-Rs zTm77{8O>B$w$$zZ!Em(9f?%?Dtkzw=uoYe8%*Y!Z7osvW?B+}q*-`WZpkd^VLtB80|cYtSS@ ze7;|0X%l>fdS>*D__2^fW+*>&4#f`=ef-P&*Ui^VIn9A|Z|0RIK}_HjhJOhoZ{u&G z#QayWH3C9PRu88_`dDu%X4I0wjHu9-?eDr=El&QFmUAm|2|5$>(-x?5>`bAwOTs0Z zU%NvIPWqQns`EVZuW7OFGm#EL{%pIJY>8A(cP^HOKv*ji4qyv7HC+(&NcLK4fnLeY zEuH*}m8EplC0gbjl*%V!pBYXd9HuM$2D3g%`ic>Eq$CQTE!>kqcV%{qlb7H7Jsuo( z66t%K14&k{-g+}}$}wK)dMIu_=m(ubI{0;-&nmr@k!tR7Ry20#Vqoa@a2rd}U-}J^ zEL=y-`VMQB{r7%yHhjk#$N6t8ZC}aV)9ZH~FI)9eP>|kkB}S6n&$BSq<1;A=pz+b5KMoRxK1f6zM>#y$5_1QPYtJWW6lBV06!MqKcR)W9OsPPf^92rqLG|c z`v6aTe*Au|4$vENK@`?|V~%G8DHs4AVTAENQp77o6tU%8gAb6&$_4lg+B0ND*K?bF zy(4-s*~Q6uxQ}Mvi&M^_Fs*|Y!qaU}@!+$?k+~vImVdK8hrhjO?O*?XuqV5@Rkquy zB#+B9X6bF!bD5e`wsDxo=9wR;K2w;h3Zwe+im5)nh2?|9gPB^!<_Fj;r<|2rU7C>H zLB6I;9Dien=GnAyKYiVdD8nPcRZPk6(N+$tuv*fR;;Lo{qpJqquZgN)l~xtOzy3*PMxs$xwG)l-mOrbu1DExpznORRBgCdeG+(8XV`j41y80 zgOY9zYVsIf{*vX7r=L~ZX9=G)09nai1JSWr{Tek+tpl*bP34xU$P;tLnbfA3*_;Mu z_3Zk`>ci<@_{$}=fOH$q&tAxkZ78dm$b84r>lXOav`lrtsFN-DYSy_4hYBvzVH4ujW*3+kOhv zqq$D~^^VmY2$w&DI||qeEUal%EvYZq&zY<$tz%~j^8OxBBzaf&+&bA)g-Dcn|GHb` zwtM_dJQgNSf&E;mZd#RRN9k9pTp#Rl-Pl&%1X-riCT?7kFuYJ0KK6(i;zlkF-^tw{DpZ zB7%Y3!#h1r_78ZvP@Xqd421E8+IfEEet?RM484f5u;xn|h|jQ`tOcP~OckKCuOFu;4<|)xX>|#ofQ~_K z+2ZbNcCAdR6sC6;kp0GVjP2K(q8i1+n_35reX0A%md?wNukvQxsB#gQ391HjEvNlk zSbR+<25A-OmeB3+Wh-w_v4hZ3O^gKKo%+sbl%xVK8`Igwa+AaIU}4m);b)4>cW}@6 zErsgoZ`6j4uqNLRb&mm_S##_Tx4k9kT{ik5qbow5{W}_zwTlI^G!t`)r>{~onH7@4 zUNogaoJ?B7By85S)<_*A7}UaH6Jf0ihRUauFUv^JGIVUBb15m}TNx|$bZ&S1A>HXj ze11b~(%p$7rD^{AO+%%@pJYjkVI~~+Obeur=#Hz^{@MN>;rytGA!c}{2% zTm&w47JoVx+(^3^g2Zh@RvS9o8`%_kIV)~gE0;@u6k7e2o19waFJv4H$+>j9hM0Xp z961|a#M2Mz1HYZwMAX9Q>CdEOK!mOHQF-d!Q}~F^Nd?0jW7_2DmDgp<4ki>zC56el zM<~-E-vQ&#QCR*|d%D!ew_FFs$CXEAX^H0KANoaRyHt80lpe2)>R$(eW6NCz0&x?S zqaAYB&oZ5;$LMa_obdo7aVN zs8uQqB`Jhymagn%Z6Z_vt~?Fy?~{BTDDHQ^W5Zs>aXGZV$3|)N8e=g~EeX@7tU9qb zS(?z!Npy;5UZ?-wZhLICKYPHT{;j4S9!hmSb9a&0^V=r0398N=7W^$sL*2bZcYx1Y z(GlIj83TzH0_vu9T{QNp5;vT#b2>OkZq9W+!y-}H8ozd6^o(OMJS&VTXY2(0RyKlI z7YcZA9}Iu!8s4pZZ?|CC!L?d@WS&`ttswkaY1(Ql)ZDA{ERo&{E4|eV|B~uU zK*;w;%1U2lEg_rK7WYCIaNm{GQcXm+7p%3U8LuD06$oxN6X(_sZAm9}$n&a^3zg3$ zb=1I@hy~ghgc-zWNxz_M{Py$Jakf>npL+0hxiZbgnAvEbf)MrXyHRFYx?}74=?K=I zq1<+D?E&7K_8}U->xA)sc4}vvl=gC?hcb#xtb9lwBraxXDJRn!_K<%x@E%T6*>*l5 z%dX=2K|?XhGhQC^gub2y7VFno;Id#{#(tbuX=+mcs$^}uTvtxX*#^Tn_2xK^EksZu zpEk5P#){sIRK!2HY0q_+b=>PaGEJaSnnxUpnrpH||70>xLivvfO$;aVvtBEjs0rOB ziRapGYa8bod&_d8ecmioO8LvA&3(CY`+c#8y?*LLmr!LIvzlqQ>)zfc!h3k!N|aAe z+s}HE{V^}M4)%VRe{5>vj`XH2-PjQ34tO_rT{$K_XK+DYG+A<5Q|gcovig@XrH6Aia;u-q z>>#_JR!8Z)G}NdtuQ;rkrHmzZOvGKSTaEZ2_G}TVrfa-_xo0`4bj`!XlDz*hb=tzt zdF$W=hoQt{eHmr7eOJox(^eP~2bB~@uTuZU5CVwSlpkc@IV$G8oaovZnuFm+nm}=I z)+h2q96Am0P{Qw-lZ!1laF;3>S;GfabY%I2s6Viz3^&j-QKez#6D~>1${A-?<-W`& z;^B9)8r;@6-hb~(WJVzqR~Po)rP`|5c;?h}zD{F1E6kF&eXdz*5NB>si`SJ_;JCwA zD18faLPs`q%fLPXJgU;C(_A#8c^THi>gb}qJc**gy>n^bnvN%A0dlCwBkjBWg;voV zZ`4|al!l9}_q{`~4ff`XOz-G{sh7O9m)m&KMaQ=9&wBLLs;n@7Ylc?NJ2{Hg_3KHC zr3kWE@rnF3+|Id8Zvw?AHlR%D7A|on?dKK+2#?oU?6I{2>lwGK0tPuNy+3E-DW`@< z@JK?PUmy!V?YrcJfg!(&FJEV+!AmJ9iIOt5r{kZR#!~cAp-6(ldn*iI)fDoiJ+0W;)Obr{@{wD%iwQ zkUyDNPJa2;_>-UCg87o-r~HX|IO8dDSpEe{*Icq_GWdZ%!Dg<*d zH&z(zk=S{EiT&<6CuBKGPzi5GC@wID`B|EjDVA(Q=%R#9Wivv)?q)S}UizikD4y?b zwQ=2tffVxxnKi;tMGL6n???69MeL)^He!$NHEEgROzCgzyyb5U&?&R**@X82M%wbC zhM2oIiIpM_!HljmW#Y^)#croN*U@^C!I4>d6^AL0TNckpNNy_63j_Tt?gda2!}ciU zBFx`Kh7&}2h_Kj1S4^`;5?u}y>5z~dguJx;vW#=H!onl)6H5I~JD!EMzy_HzM`~_T zfJD~$W^|=ID1JWoM(3x#lXg#ja3gW~m^la-CQC&1$!_&!hzLIxW?F8POefq?7I*FN zZH3oJui5QS3wlCJmRb@!g7^x73@xtJ%0YnD3JQK}3{Bh^#m9!|fbfz+OyD{ls{dD)}>HpPza0 zQu9UTYn5b;;9qpLWW%w|qzIcM^Zuvd#&7%jzjBWTK%VBtXe%}NvRz&y>})di-J|ilvx;gBQB8E?C0mFoOQc)SF|w5vv-PBQd^#9&G2~(h zyKxBFSmkr7>o-rn^^w`0iHbFp8IPq=k^kaAm4d$icN7pb~%Qas)v%g7V5a) zkbsn{?@0=oI7WvX^QZ)|A9aW<`SoKOtDf9MOgudZ7^Tv9_qxV&Sl+h-m!3>+nA+-W zq&}TT{N;-(GMm9ev$4{~jH%_Mq|7|?5Dkr9*4TAIck;y6?^3KgD?XWwGNv%E{fh073n(V8^Ra|~jx`aM#g0Cr1(&+~HhQ!m~-8w~1@R*UtQcJVYR-QRa| z!ajDC$$9Xs-jNQh)&kPdQQO(&&K4{0MyExXCGcY_y~^)SO2;JW_Sy6$<>W zwT%sM^6r|HF_fD-SC&qK{X%(s$o7_hZQa}YNYlGS?Gv{Wb}6!0lxNLXb+9pfZzx#A z!;Pilu_d>EBU_O&4VS*T$}VhkyNP2=I%ikGYW3Ket2WsZ$5!4!uKZQv(L>9gD$Lu0P*DaHyT$8s~89U-q!4Rn@{VlQiUu6-d{z0 z+kbu;dSJ%myR~jL{*=;7_Sdasdxh76hUhgVr}r$bI$ju76p64ZL+#?m5HGD+CC{_C ztvkil=;Q>qmi4o|C+sA$I{X<<=Xx@JX6lAa8@B#V$SQTu)DfHeY_ZxI)#bBCjc7?S@HahO~ zv9(IDP;`#Cbht|pv99fQ#b4)iU9;UWb^4czUIm}fh53tAfar8d;)ozI?wMO@Mp82b zY)qh-U8(PpMDjfm(&9z9xGB|^Lj~IUZrKMSSHy#BI~@|g%ddTFneS@`u#bhlJNLW+ zKXFB@lx#JY$qGzv?eeLYHm$|(4UI=ClCy=jm)7h{Q*e6Tt+`i^;oEV~WoaBtytY@y z`rJ&HCz~F_6ArnLq6=c#ot^#uLJA+PJ>_?K!>%{U;0zmxKcO4Su_T|zyRFY`#gr*) z)yLGS5}x*VDKyze_8^x%Y=~XTDn-c!+NY6I>uSxOeS@&!e_zCn!wHuotBYZB)qbkm z&XS=focsm1Nvn*GVkhww9m$<)|8eO1edwbdpUwG64C@sMFHf z@iU4=ix166Nn-i>zu7le{toty>cZSWo`|?ozFCun```eWV{o4{lJ*iW@$+Mcr#}<;QN3 zQkTSB<1w{jorqoL{A^Xu;x$yneMr4KfI3Y5Az=|Ai~NXj?82)jQ^-trNW!m_Tq?nZ z!J$4j)v;hLgfPNM-jtJTs(-j(z`nK|o3spPVkZRt`q3m)|0GyKx!2rCFQfx4x9K!A z&}*s7JQ@iVP(hks=OKUlh*>}ESDOZlBW82tL$U>E>@wyIIwyvEY3Q1299!S}r313w z{W(FT0?h;G46(?M=sHo;L+My|;1B=iixk&>^jqQfuz{Hcwze>zE`4ozr11ryN4|rM z-Ij&P;X{WoJGS8>zUf*0F+y67qRh{I_RagvRao*GFr}i={x(YNCra5TE3}0o$03lC zF9^9#MTD$6_bZQja(lwq-k$A${Npk*D4wBbp}u`P*eW0xEcUaum8_QBQbuhxnk6?p zE7wOZf!9=~ur1wg<$cX7isv%}ZSp)?fP>9loiZkxgr{xx2^@D*yJSayeA!ghR72*U z*C{C~YN#F|?#D-X)D`@ys+wfel9nPKrhc7^`rIeaTW0VDKn$B#3jiC120hf5lq3Pv zWz|$g9~UZoasD{>v3|Bf(_)p% z-PJM{ua5v`Rn1c$ollk-u zorWu;S!m78~ZPAO;*J{K7~XzK#P->`BYn966(^u&F|AL;#t zscqP4)%Y4b7$XY<=ZG+VUwQ$J|0LVd%!-wTsnw53NKdXF@iU*7jH|k85u+#;ma1dv z&WKo$4I>L>yqhj03`R0rSd1b^Y1&HOlpxaTFal`!nF%!2`)DRLCf3!aH}3}r4oXu5 zqN1o@Xe>+Ohv+gfIeI+|!vx>U^X()g?n{Syu}pw!tQMKG zq{P-%a^}m5M>%a~hwy62_kzn>?&QZ7gN*2?AnyLE9}vMHCDosp<}X$vK*&y?v~)7A zvR1&=wfIfnGN_{NwhlGq0eh*c+jfUXGy!x=BVz+uUX2N+PF>~{_!bBk>y&GFtlQ~7 zPv}>#5|+RZIzr$K9k8{vOI!N>U)U{Sr-m-KTbzaBx6O2eNOs_bD(`;U*9B7eE%(Xg z#l@+UgjEnpVL=HqBa>?Z3nMe5JrHO?h<_qUvrSB-YR-rReu!wjCvm%V%gX&25wE5y zG@5{K9)_9Llv{J+J=#^?wVXfH+4=)8tLhryu2?wJY5-Xf$4)Cx@Gbh=Q(zR+-=qrw z=ntgk66(EKZkHE3s@R>;C(`REfC~QYWqDOqzp8%dj2U~kkaC`&n=KAOd&79h!F7nE zSqe7In}eF08SW{~`PEC(YU57r%W_@ZU2DvzlXTwxl8{@|#?lPUZ*ER(X6VqXNGq!n zRZ}c;NcdBGd+H`9vO5;CVV&6e?`MCMjklJ-#MDNPrq3_!ZKg^>lfm?Eh76;C}!?2!wDpzEslCkot#D0EUVR3T$bnjTW&gnwzy;QT_|J z7W^pt6&o1cl~O_pNK||ApMO`s9bhUU2WZt})-;pCDgTi-fcJyL5h#ED+U%&Ha8*@h zpZ;7*@82Eo3uV>hy-lAd6THk5%EDYd~EILRS0lMKlK5b$s=T!-ZDW_yUk*KHCPb|AqUH zcmwEd)Whi>5X0Q9nf1&61g^lC-9w1M^(&Bp z?B64&0Du|gUsIclO*5m=g`P21qy3AQWvq@LCr}};5}KM5xU4qzH_e(oxc|j=lCZSw z4w2A=gdA68Z>`14;x9Nz?>D!$K8xvEX}Gqx8sOlpt#58_uZKwfJM^pVCVqDyOvWkq zJo8lATi8Zx>D(d<0mD+rp}4!>j9CCQOUrij85c1VUL{aa93j}8qocZ`RO}W+ckr)F zvYg+(MHK$q!9ga|+qlnQL=wSEI!ihiX%{6>K+^W%u88*?>GckxXcAt*9+?oktWWoz zTnhY+@ni;R1eYMptV`xBH6!CPcBSMIpx$waPwTa>f#7`p72Znt3)_F6{zr;||6glp z|2Oi73I^PcO`)L!7)Or@2~YQW{R~+CRks3i?i6b$;CEr`qd!^YT8hXjtvseF(8Qyg zyX)xaOlRk!-ysb*@`dk_$G`mg!k>Tuc1}a>txzJ420{lsTLfBL1zTG8c#%t?Q;FtI z9Lth_`RG6OXcqxUUz8*E?f>&SegvRxXQm-lShn-CPXCEb<}Ie*zDYO7gnM~`zj5c2 z6ppMA-TyQ%`A}m)QrBA!^POxKrpMBPoQ8&yN=Ui8r=w75(4S~#kJ+5%VgE0#+9GQg z3X0-j9@{&*yUYrKWFA)ktUGc-VAHP><5B7@fBaRNL^ODmF}>(Oe;k<_4guOaqX|Hg zHHF+`-T&gM18U8;6gtI9t-k9mptLzINJ|fhNS!vGCmiyeT#&H}Z6 zPu24Y47Iklo@dvBVOfR&u0<;A3jdpHfcN7yF*ajjER{kCJX*7KWTJEN5(@lJHU)ir z@|lEd{x!Pg1)!m(1U7?~p7}2>ZM2j&P^EnwG^=|o+vE|%sQK?*fTWe}bu&K`WG!yM z^zOio_EO;cq5s+fxc=P>c;7xa;rD=OVThiVG)48R>imJP{J;BG8r1L9%@zT(O~CE( zotaMZ-M^Zo06+mUHbrUr$67DI!74(G@b5KrdT#4*07wEYE|y**RTy`6<>b!nvbL&` zxwY0`TR#NOWUFlna~aJA+KIqOmxqZf428Mn-dx438q9~wXdwi*szSNm6bsfa1jOalL@ zV4D-o)#S{~%`!V!NMvt}l__qkfbg~VZqB9wnONt0qZ0=gsrnH9#^~JM4xqck;M@TH z$l?t;rSGT<4ZHJa(p&c)22u{o4g_1A5l)g%aIe}b=D(?>_sE~!Jp3rX`Gf?sA9>82 zzr~PjAv){9Pa~`8v7#pQ7;KzMb}ef7WwCs+=c22krsjEg#&`Qx;)wvhFM`BD0XCY2f*{#_0kRZQ5zqV+~{XKukSi2Q>bS1{HKxjS1CMQty!6i z8vO**(C}d}btnfm)P%c^f~@w(R~ztl^Xz068hY{M8_7?jlGW<0Q&?u>Y*IIG3bw9c1)c1H;skjtnbuyKi$G5Jv>S2i)humHK;A+jx?r!$lkhHEHuvA1x zZ`Q7wZTQ!4cX8%HT5}mdl zjq!>9N8lr2`PuIBOpYl-?7~Nq?X(s476CQBqlpAPFD+fnQoqj6Y5=eYV0RG0t zLPwzI=pTj9Jp?mznU)h4+hyEZdPFzxa*r($>7qaCEO86+2XkfSrJp5^6jwn@N5zw& z(*x+qS}9V-&3!y3Zw^}RLYJ!Vx;oNx4fLSpqR*BZJPVc{lb2hIDl6v_LM^ov{$OOn zimgVvx?d8xV1XNZVZCli6RwT22M*p`aq82SNxh!C$8V2mw@*)M=udPdYTZP%)J1VG z_y(~kb=dQDY~KCtL@Kb|aeOv;#e`VQ^pB^TXrlHfo9TqFjtK4x#PbU10Iz;N+h626 z;?-ZXD^Hg6(yS2^HP8t&xGxhfd|Sj8xtB|4`GRi_*BYlqCqk!+BaDgEPnw|RH@TY% zTzCdVK8Yt{8f+)0N1ia8Ga6uhrdS<6adIVd<>d<|#wC(IZ_V1T^KWIS(z%603i??F z{>CY?d*q4UPYJfnU_4X^=Tx&w17y-tLOJFkP-$0nwum^ge~883iD~gM5})!^ic8!^ zg^G}&bt5~S%QvbpT(#STb=VM-Ws%Cmd*=R_`5`6gXMbatRbO_|nEPl0=Xh&gs7}@R zl5*hv0fnA3&He@U*eQQ>zzPe4#Lg&bL6e!sq&a=RnL%zx;A}(2GY!w~x7QmG}+*zuWX(#<~W-Rp&^9TY2lbYgJg140e~+(-2{A@alECwlh{ zV0+PH)dzgtP^ufUtitOe0{_Z%c}gWu>X=y@jU5|i=eHnK0^Fm+c!T+4p_kSd+7Bc% z%JpvFPf`}CicsL2d~9^z!6&0xGvT5Gci4lu(HYCsnCMcL;)$9+6%bAt9!5nL^`pC* zBrM?--|E{tyc+Au8Sbzbs@i+lw^&z?^|@ph&*og>I%CnrR2ca?9WVt-aM{(e32Qd# z@FBIjWi)7iB|+pznxIH!bX2M_f=Th-E{rn{Ji+|k`5Npg)DuA%^fu|WBm@E_+Etks z+>shYQo17Y( z`VhX(&1|5O8lgRO_g_<9B+V2$<8sb673OWsMI6NHRN z5x=_Kw-*^l2xCX!1sMpKXtZd0lRx<^{aZI_NB}I0E?eby5sV7e|7E`12UnY-QD4=s zrR0Oi$6N#2Wzi8FL~;FJj$PNXeY)tJDtDSb3QooK&2%&9q3>(k^>rBp#I!{~On+iF zVf=jjAY8)(dEq5+xQBIQeGf?L3 zzo|n-rx^$oY>%0Dzd$$J9yw4}VMO6e~kzwvN9 z)1n2b#l&#QQGBx2mvcGQ5ytVG=7GYRLi9|O=?SR=Q#q~pX2O-@ru;1Uj*`nv^B?}M z>`*|t`RYhKSLZ79-u_W7@)G4seOkHa9D^FVkIiDM0@S~y#q<6?wb9+>g^!y!E7-*q zK1OK+`AjDc(E)ogqOS3JyXwp7GibY@!=z6zKYC^p>ZZ6fjbY3UG_%*|j7!ZY z>YDY@KwRBHc!-DiiD{DVDirrzIUEc9K)f(0t;BPVCGnXL=8<5Q1UJbYm+AR4qzVQK ziVW=38W)$x85k*05C%g|;i8QUw9y@Ih0WjM0-D#>j|irHw3(=$FM~;hEJ;sD5jP2C zZ0fTdYcrXqxcg;!B2BU|nCe~*-8@-A>z*f*^R2fL`vlReSKllt8R};}x%h3qvn@x4 zwhT^jfB7fC6cIrC9EQlFI;+5HhYDp5#}e8a&zCiSxBhX=P1ntd)w2ae%g&*sXXz6Gd4Hy__YJF18vhb z8*I-SwNvYtL5QS3Zx>leOm{d1TzjJg7&X}oe}b$$ySt-ASTd{;nNRlAN>}rn>obI3 z;8Q9g>)a-L2@BmVDCyzF~%3GcSFwwMjIMLSGDNYH*itO&3 z7#v(UHumi#TQP;3Z%KKu?Y-hb7RDK$jN?&rOzPP@OYue@*ZBn2TfxTMKf?1nrLj|n(u!bZ>8WRi((uM_!}Glhd~R6C4>$w758v-!AEL z^V`;zsuY0iX=$044qpu`z`{1`a`Q#n&6YY#FrYYnd>4{J^5$#T+I+{#>023J zz@-{^gG-q)QhWX(wQq73I8CtrH`n)8RUE(J$8)xFK%{0u{&>?c9Vb>;K5 zWsmnXmpDj*_`<8Cj}m*y;IGQ&OY91Ag}-r6>w~IYbsr?W(%%t$I_etMpI0lLP)5_u z#D9_Zh_3t1L_q@&baxw-b9s6hRK3=E7A2q*v{&WdvsW@CEWZj)VeFMW)Bwh@tRH>W z&h}hHV7}t(Mg57U=_#zNTI5`=6=;#;U@~Of9MCA)qJ2zsq*REfY%NYrkRh-Es zHD^aJbj;$k_(PP3nCbhphD3suw(0BFN@6JyV+4VSI?eBM>#)O$mqAF$7~5nE%GF$Gp@ibDYBDalnUxGl+M{d z%4MI{C2)R$t+B{wg1&ZVWxsk$r_xq>lG65@i%>L*yhjalQ1ob$7iT!0*#S`BcQv(F z_*<93Sd1d4_8UrJwJ3YBvh-|}*HHn}Jh!>Ok9`k$K?;jtnKw6F+i02xACX@_levA% zbOh9Pa~OF}@+1=?-*-GD>wU6MGr%oxbqAVEfMvrG9F7$wcC#}teBmCA(2>%#-?FNr zu?|_n*poXJFgadXdM9-ftWvCYxw+K8AiN1EWO|ZV{!U$9TjIzUZ?20ZLp!Z!t!#F;jdDnX~Qk^M8=c4uzlF6(=}RLggcaiIJ}g`wzI3fknJ4m)UEZ~PbkYYTvx zJ$4(3VLOc72255g^Gv34raqjl=(h@z8FXPT1}yuJw80b(oR4C8Woja~Iu~uWD@u-M z97zbcl=*RY<1wF~o}FqKJV*?^+P3yc)}0hNBCHMn{pf5185(%MZWA{@?j2g)a;dK9bxl0K;Ex>~mp2BxB`6!&E^3QWGKYL&RtI(Iyf ziR;VQ4a}5Pj<@H& z)_v2yE(Al>Pcw(1eDw}lZW@E?KDAX3uHTB*RyQ(;M3H-qM6hXQX}*Iijxd zmX719O(N@O<=+#X2X3z9Ux`|Dh|g$Wr(`;)2=&*B`wcCdm8kbE6{^T$IUBEE3VO>X z!KXmwR(Pl;tZ*8_*fE~VF%*8txBg|^k54{O6>Vt44~WI}%m}bC@uGHg$G>P$%zT4e zo>vX-eZUBTJ=nE+5nd9xXY;jYsT@e&t!o#GIsGDX*h81H`S5&Xij~TJ&LlKM&dM14 z@XKfDVj`66B4#Y~-qqE5&170Z&IkJ6Szoi$S}b!DlZ$)&plA}CM=^wL8#FCW?14pUeNH1A(6YCJ&iZt zNC#GtiMH;=?r_;kkC`~ySX{Qn5?z9Ie9da31H;)M+n$GfSdMmgWIyF$h3O5|;=BxL zv2Kbuw=;<+<7aY56-*q?v-M_=VzWn6^;#x%6}?GlCT5G*EZ8Ztm3SqRhrO*6 zi2xqDr)?^IvNs=H^WW`5@5mpkx8*=|19Prl2{!BGgxWlBZzyMr!xnIg#oI*HHn??P z8H{B$aM&exgu8>eNI5Pw9raD@B`&r80`^~EUHlskb!n47VPVC*B+8^65{rl7ZXa>v zR;8(#y^o)hh@$lzzX$_T89j4{FVw8l+80)!N%rOl5`tyo7(h1xCkvupFEL)?&nqg@ zR%qc+f=V8*tc9?^y5~N#&jzzjlgIrs?SIcw5)gP?S+xT(=L+v?@0-uOuTH0J>Q8Dz z;ij9IG}YG%X7gMD^1Q&;a!&6AMH@1vESJ*DD}?u!j}p-Uuy}(U7Me@%qfaKp*P`AA z9NA4e4gs~cNFMFeF}Dvk9yiJj;xYSksK962y27l(7wMWYr}fZ#Rb4$hUV*q&#sj`_ z??K?5Hq>qcsC$!ML7;C?Cu}sh9@dn_kEF%xwb$Xzx~cM2b612}p-e#Fo$U4fhN(v0 z)e7#rTTd@zk;}aw2gwLA4BkpDf|N*g@nsY8`~>~{QGNTaJ_hV*TIT3;SO-BvlisVV zAh{hsmX^j%*N)Q$w4!j>(cO0>ehr+|W`CB@<)Or2)r-nj)R+$4RPJwXU7+_a9x!A(1xPi0B! z(|&jkfUDY}M(8N1yrSyY=ZSv1wc%i1^}s5t#y&#jY*fYp}#x`Zan8#Dc74S7H4l%vzBx~cyxg+;%W{0e_W2UBZM4g zzxuHk3AaO8R^EoRgamX8l8OS-@Q#qUj<095ryT>CJBDPB;u~fr&&LH@J#~mg!JKT# zc4`}UPBiC?6Z0>t1`ac)Q=K<1w{~6~|jOH=03!}^3 zXHH!;8)B3^-f&EB^4Q_|xjN+*?#`N7w{vMnHPw$hICyR|zM}jLbsl^gdE4M1`nX*Y z(>j-Vrvr_6E#X|DGwOggZJWkd$W-{z=KZ{@IyAMgp2E zRfoxKN(2p9qrx;E`*J6NHxYLDZMc1A-`u4lTFOtI2yYzBxoGgfLWS@Q0(B$AFlJE> z6)@mmU{2aeh8S|M9mXclK6Df9_A=@d7>8eAu0rt5#GjZ%V8R0t=eL-klHh0PY^lHY z!k0}8oLWevax0Z+z)~E>$Wsc#r#w5Zj+`Jhw5Y65O)~%TcKM<$MzEioLBpj*+tk3L z9q`BF+TnDDW%Fo~d`#wXW+su7xDcCPlvg8Ca`}*_x%pzm zu2`1=spYd_Udq=Ts1$rO=P;#<33#z^aH_M%J4iCeLK@fal*Vfa2CVi8i4)DO6D=KG zt`_j{5em*$#r8wbbiBWOM%ax$mX1%V<`=E162eftv%>J)Fc*tagLNmNwFIB6s>Cvl zDDg=0uG6)t_5Pf4d&`#l-k#VjvV^p{e^`JsAT>{>zyPvo?crLJB&##0I@e0JH`NNq zfyfC1x9$=s6o4)qIj;&Gw27G0VycxFBp>`|UBc6}6JovxRK(FIKJTe)kY}NxP16UO zm8++W>dy0Y4L6Gn^)*v{wKO|;hfbM4W;kCd$t4Hx({JC(<4$-w^6maPWm<*CJd?ZM z42piIg)~t|;!*vkN8Y#apvFE1%M1Pv&m5KaI|2JmC5zy%^NQ(e??DF<_?_R}#CLms zbWZde4w?rfqvlpoKNt-6N0j3o;fA}sL~A4diCIJZRzY}8LOw5M?}wyo@SB%#fv`>* z(x^(yyK|b==L=Ms)fq!6Oc2R9)LL)Zx}g*-xTTwzth40StpFaIoSbOp|3=+gMn%~+ z?81Z~lF~?u2q*}M#L!3xBBgY9cMmmmcXuP*F?30HcXu~KGq8u-`+45=y??%cd#ydc zhU=QMgENk>pvN;dLOs|t<0`MsAfn+?_eNapSmmc_YBqsN9oPjC+(B^0X+D)p0!l4G zSC#VX%7+CX3BpD?bL0!i#JgP7E^4JKR?pgD%8I%xr8?f_d==@KOfiN!`dhiJ))@@b-rUKh)@ClH4y*J`=5Ce;+|;VQe;m1nCZu z(O$x&oYL;qjxnUf(Or67*5x;$8vhYn_X%3vYbY@Q74n6VU!tv`uj-F_Bi}?hqjT&V zJ+=Cl##R!J>?bw$_KC1UtG(yybHNy6T0jNFhsKj>N3f^)^7UR-@Zu3j$dSQ_QL_QzteMfGNAtqKPDs@^%_ zj!=1pV*9c^wAitpHr0Zlh(ObG4_NsJ=DYK^i%}p@2A50 zqC4joY{jRr_MFaB>nXVjzLW9Q>x_Wn?iD3OH6#6>o3F=l=QLBWCacZ)+#>(e9-tSJ z<;$vayqGU$n*(4F%O$&)7~I%vO~NiRuz8}3r_q}E;-I#MHu%N!e8h+8TaD^>eD$(a z&7~Tq3pqL^ut}suskT=N*F(dUV;=m%IpQ|nC>o2U_n9_<@?D-#qCNg0sQ z&$4gnPuff?-<*v4)AHhp9XyW{Vu-7FJ>8?F6X`ytyLF<4JiRwY`zuJyHh)aV0qUb&OiC{t;wdNSx&ZvPhx}84cRN@Xq8>_TgBMkA6uf;9Q26L zYyz3`nkrQY#QZv;*>C0e1Z|#>%sPWl$0(7p&I>Lvi2=Du1i=2;F<0sq}uWgz5sVKN> zPv}+C_>Jd6Q-?H{YkfYjH}%t2erX@-@YBn2cH6#gU7-v1O=@j2Su~O5HN$AdbCzFM zUw|0d2f~K)9;!l#ieAtSZ&ES72up(5+rmrHfFOBfmzP$I{|gQKn@?|N*)4^4|YGv^6fx;A@4a7n3PowBrS zcFSllk9EA{q#LIGvaRWdvwV*9jW}Wh|M+Y=97lk7*XHC%JxrSX9B{Dp0(2dDQntPH zj^>nhmzQgD0kL zNqTSzBWkI5AFaJ;pM2#UY!K?gvy@q!@?S{;e`rcZHb_4F!TQh0$S0*R5|1NnSTa70 zqopCd?i^vW$S!W@2nb=JSfJaAj6Eb8nUH}UID0>Sr7jcL+5KN10X8fQ9a=y!2>$Dm zz{gP zV7Bf5k!3&u!SAa@i(oYHU+(H=dRo*a!ex|Zg5W*qUbMkwgkgb1TRxX-FNI#9!|^=% zwftI3<>n*a470WMXz;s2f-`u=2@Am@DK4?RmU;nKjCh0wH~5?esx_5=3lT3$Ce!n+ z0|&)(bf2vcpQ!%1gc$Bk{hHd)Z0qdWFQy}Dhi~h!{c(BREIqnnd}- z8<(4w{j_}J4F427wMCKe_V{&;%!ax!Z9;zKL0uqzBQ^aL*Zqsbg;4jhLoi43SNzJI zZ+TNUr%0GZ1}iR0GjYXT!Q0AJk7FBcKJJ7>Ey=Od^v=J^6f50chvV!=&*55&j+JR` zFXH=gUg1G!#eJ!w#Im@c#*cZOZM zn%hdt0M4&y7~WMW!1(*q+DBX5Y{VWVZwID-``ETxatXCJ7D@qaCz9#=AoaS4q5N5A z`-P?zwqI~nu&pU|8>3VlO&*i-0@%QA+7>hg*cL$0&7X|v-$}#8nnKrOh+*DHBGQZD!2T36 z5i_qeM)wI?CpQlGeDRu+ZV^=WlKR+$Bp&vJNAhDBI-~s7cxutIvYCvJ`%TH5F^4Vk zY{ycloWg6k9vS67g{4c`zQytf4UzFn7s_jvVyhX%FG>L|k658@Gc9lV<|{6@eN${+R`I?N zBK6Z%ZMVS``vAvfd6^URMofbUS3-&*R5NNg7Zb*bCs1bB1u;^J=}uoowV?xn^BRtTCmo?UtYdhr_i7MXY#ETALsZjDBm4qew~LDNr`-09n~ zgsae1x#R0d+@}r#sSq0WlFKUIK2@|GaGd4&P`U-rMbo*Cz`?Y@9IY|j;K_)BuCBLp z3CCy|@+-r}JZcLJ7`}f=_o%3j-8};~s4D4vxgXGzI;bfxDrw%6ZkWjR=o^o%sg-p_ znx0ew?n1O~F_Lzb93z|2_Zy*AR_7ZaZ#o1PE-zar0PbUv$jV zyE<3QoEZcb$QnKVP*EhRHK_?Ei16{;iQZKxr+)GKJO`iMpWw0m!uC5(EvhyFm~2>@ z>C?ka7NVebNDKBwj=jV@D%OmVCg~XoUdvC=Wn7{ESbXW1cww;Dn`wG)6Ve}g2l#Y_ z^FLydXoQUr3Ne)Dq%dlf)g0TimT+m@zdL*vebw=*6Rc=m96=WLyKlDUWgf{fHSUow z!@ddsnnwrQ8Wukqw@LW~y2F=R5h%hvM3R-R>27)X16goL?)!f4s6Jh-5{@7DHYmfS za4t>_@dy3isSCQ4o$r{VDlKfwXXoq}xL_OT?6Mqwvorx2oEcx5GS%tmaAYwfAG7YJ zW)hqAc#G()VqftAV{deIHd!cTRncX2kh`!TtUx1B?;@+~({uM#0;f=LhQ)=Cm}#ykNgtCq!o zdfnKUGRRia={El{vHe`puq07fhA`JhV)$Y8BOeY8#in}9o^Qf+;4D%Sqh$1l`+QVS@99!wPD>pfCSv~!)l~Nbm4nq9M-Q2{e@A;&{@N4?^AjOF)rCW?q@6 zyMvs<`$N4w<97U~NDFv;`k$ckwx41ph9z{ZZ+dsb7SD};gPLw zT8Yzc{GEV3y(nT)F(vI%$F5F*MJ`?3Jn;_IK&}kEu%d-B0H1BdG3Ol@lPU-B^=tyK zLNjHuSstlMTAz=+0iPSYlBYFqxJ6MV>MuY0Wqke3o!3I! zs3Su-G~bp=G!FqE?gXa~KNEa2Or=E>t!B3f=`IN&#~}>Dh+s&0kE*KJwM+o8ojUe> zU|hFECt&n{<3?yd-$3^74JgUo?dVfnEWV4=%#9}_wyw_2T~U2ZSzlH?-x@RnGl?e( z7mq&}E6dA2Bv>h)GM%~3KHuOhB&P&G(qrHRu=!3P*%4IMVNAdru_f4ZYP|4}viM+^ zL{GqniQX*POZ|mU?1R_{v$eoLh&b^Fsn@JF5+}cUq=!%@HzPtIjsyg! zk1jfFd`p0oC_-(V`5-rMg%kftWXaC5Ow)nR@_Q^$vgojsoD5-tkk*&6XgGAGp%UkA z#SgwUD0W!{bz;% z6}Rm~*hTv9v6~V^R!N4Yl}g)3$*B9mN~|;-c9yF*8<}VAI{xRG?V%*ZHs|*m<1ctE zqgV{FAP?>rQ$mXhnUu9r%PM4rY4Hl*%GiDn8pb0Nye2@bHFMcGWZPgsHJ(!;xbmTu zcr;i~D<-7Drj=0|emwYoRMgoRsQIzL^O-0OlrW+-_;_KVZwlkf^U=5xPo5*cIv}-U z-iwXXVWfUr4PE-}Q~i?g}V*C@JVU0adACRU-bQZ zO2zh?qaIc!4UOJ-tEVIZvNKzeZSIr75`3dZVLAZ8tXloM8?HKfM?I&rh{vIHLzRv}#5f={xVe-H+z>5nBdDSdcX40`}h zBIcR`akZP62*Np(E6_lclD3RgmA1MxT(Isp4<1#iyTz^LNSbVhQso^E3;t2_HJbWJ zU@&Q(v640K`X}M})8gIJQ?Klosx+?M&6-nE+5W=sio@IppG5(Du8UG4S0tc}QDOXt zIb%S9{Rl)blmF@sCnP zDxD$wE_2bGM%BWZ&+#8NgUSuYX3~s?BYE@qKCgn1WJw`NL`}B5q2bsG`0RlF8t==6 zL%R_WL%GHr1#sw4+NGW$E>1o=;1Wu@2G>(t#;uPpY2t$CWbz0xbWY^~w3^bt)Bh-M zpW2xx=ERCAp>J(wE*mpr@m<|rAKgVwu`eSJQtt%#o}Y7Z4sE{FuRFVRZx{4SO5))0 zr0JWJ_kMo?vm*6SV6qzZub{2(ZI$1dUU9cIh;p5*ATgAViul-nI~VH}S+Em_y<;t^ zx~FwilrJEO_^h+FX7+w(x3L(D^4DHej|~|s22S&bddel%=A2qWmPvi2(0i2P&ee6|^ z$Zl{l0nK~Cz+WuI0!4)l8gH2EWeA{;tjwG&YP&dDVKuTcBz~7_M|1m-!}oLUk< zV4pF`m(FV-E*i3tG-<}))hC_ETa{XQ6#?@~7*{nIoIhbp5UEF^-z#jEYPk#sWSOS) z0R|WB^yGY#RnnGwsj!vC_|*3P?fiC(qs?Z>J50U4HTx%aX#VfE(TI@CpdC1*%U$tl zOTMmFj(VqT6{SR`(r;BDTM^;(TSH6LfF)t@TuNfHyZ->+?XN?2zaiO*MauGGZy%^{hV{&Tc!auzEo^> zm2IRq+H4={TYIw?#{(?;YC1)P_EqFSDxM_HvN#|wyEBk^g{nh!YM(v}d zik0(9;D8h|WT@Q455Kt3SwCw2hp}^wvf**iNNDi|jkv5mH(Q4suc8jK!@$qd_b`bH z9JFefB3m>2(&j~)5$binuTzgDwT+XmxP!{UDWkB8mI7BgeyyV=;6Ng*nbIQn{6?1n zbEf4mJ(?eR4+tL$!@p^&L5g5lyWa|4{-4BKSZSEusIG&=_xVy2T-6+DKkzYnePB%5 z6CKoP<%29(@5qC^1TH6Hx%XbbIC;_1pWiz_2+`B6yc2g%&8;Wu|{oK!TI z`+SM;A?^wiya5({Cs)ub8=w`hq^ z7(-cQ2-O3qQ>%kdp0+o(<_eXm*5BS-#j46YI(qM4dmVFCUnB=M-~5?TqKVE??ZDlB z-%?*bBkYFCmp5Oi`TkA87nHb%V+Bk=5I&{%wA9D}^_2)YYig^a!lk4J{W1H7Z?n|s zm+DC-W%gzc(wVeTEQw35T(rc831J8Rsya^ZI>yciZU$W4bV`Hv@9ZcSUXi%I+8lM< zSzAb}S3;}N9le4UuZuZTYf-b!E4+{@cOcuHz2n=iqSgw?1fw->Yjf$FjRD~{#*9!X zBJOX#1-o^&!bClGVugNG3~SUG@c_h{vi8MBNeWAQf4TS;DhkQo(dhPau&u=PrnYW!vBrbtGlz2CE9U#xs{9rw%nHA9I@v0-)nf$t=_3qO@)H@ejppn;j# zis{V|uJK>mlXe1qS2r1$t3#4w(u9a5e{GzgZW*8%dwF@{^=>Nm`N{#!Aedp&79sc9b zdq4CBeH)S3=8{E;wHsaae>(dA5^OEtuSO>NJ6P^K%!^Xz0VA(x$up zDC~Pxvqa8QrvnFBw4_JC2rNe zmUJ*Z71ggf5RhHN^pwRugc|7GmsXoF2mrmbWJr%R{d0(vOkjAOtnRNKtiRFYH9qB) z+sbF;aV5{4uaz+@u%Kv;$M<0>R#C~`CAA5SRa=P6(H|K(CJv9c>50CApd!1N-e$|Z zjHTKpE|%vv{?uBSh3kETyoims2~S)u6^HGWN`QKpf6CK_#W>DbnbXXV-lZ?=ZAV4D z{MQz^>q?c3Y8#E-h5Z9Rc$d$diqiIW^iOEujfmN;N;3AAUSd3BfB#tj1JYz1ZSu&) z7@()KdAc^$_Z=Q-O>7ih3ZY~q2~gVqa6qT#v~ZyC&B@7nZ0U=+SnmmWCVni(+X9d= zJ-v|YVpHmQp~{SyX`nhPX*Ij2$H#e8VyFT^9Bz-Lrm*VqoAX}F<+QC%N*_@Yz#AwF z?5a1vIC&2hQMrl112AADQ$pFo3&x ztn+>sE}O;>4%Pef|6zd}WRJj4{(pGju*%qvy8xFFm%{{P<9p#Q$c-0a+`W|}&EQ91 z?dhN9cXwgjCCbNXDBfE^ZjImClIZB^>F9Uy4X=8p1_rVvi$qm;)}H~oVp}iBc%vJa%@zwH3k)%$j*Xu1&nj zH}~wP_J!PBqb(WBPK}KK2;}QMLOC)FKzd-kex>iPMh_P^8syuMvw!xpk#z4RDJl#b zdkI4lD`COD&|5wJ%J`Rchk~H>&p|bu754NI!cljqgZSUS|DjHoU(ISx55d6@dw&%&u&N2k~HRWQ!qRsnt4Xk6^Sp002(@2u3)EHC8n&i-O@I9Uu5N0{lY z;J*Pw9voUbS6~+&=?n;3u+;j4e%Sf^EfE|dbxy;N(v!~>d@vJPIbHV^?pEHn|AzYl zgyFg2sVZKPAhjJooBBa5mKomt`!XdN>6;`Jy129$@XW2I`ILRMwzFYR;-vNW2O0G% zE95t~&Q8kir-p$rSKR)dg^8X4-2c2M6t#u90;lmU*VJ7q>cDuRR?Jq2nwCQ)f+2x_ zU3FKze*Ca^sy$|dT+~=Y#OItrJ_l$^#bVwr*W##4OE3_O^TOv9jK9fUepAi8KXMbJ zri5`o=MP&n>z!d6DE9VlA3Z{FqOGAdg|lg5fZ9T!W;`|0xhJij;}0`o4m0@?y=?N`iJVdi)L`UZn# zfBpIegCA3A0002u+dD^>J7pN;>~`g!&nnLU4*~iA)gPX| zcM!@e%Fi!eebiJI%W#9a9pd~`Vvl+2>^%h_;A&(07*o>@lb3)5%<6mNc zczG6iXZdWXep2iX|65;RS!HF|5cx2pFuGe>oWIyJFtM=Eu~3voHF~E8Do_x;2!-f) zl+_weh`@H6yW#3jF!_#ulWWMHkjIOUi;dY2A~*T3!L)(|OY?Qm4Li zG+h;$C!G!RwW~Z|3HlKfJOEQ5XqXY*nSk{`us#<{2958tN zcY;w%Yxmg~F=!TRR&gs!ajQ zLwukg1RA}4^EWRN=Vw=+z>ij=(<5;4dk?NnD1V=OymI49V|Sz}w@1`UElnBG|Ib{9 z+r-1W8!n|NP~6v$Y5Yw;XNY|F-%^JKcZJ7JV|bLf02xEwco!od|7P5SZgh2Q{*~Op z%QkY0|4z`-Sb!c(597I|B@FfUXw={(7$H%4gz)csAgXJ~q4R)hJDFH+t{Q$>*2+tQ{ zp>G(&iT1Uk1%K)=J$V5GgmtSrGuYLr=jz9U%*B;N?( z4<`7YX3>dHoWNvm79dTuzTe>B^@LnhBKJ<3%T5aSHPC%0ZLVImYau)LH538X83Ll} zZu~S_H+Nk8^_yY{Ph)ZnhHx0>S#blM8_s8wI8Bi`whU5d&pU#!nN-}?mq331)d0S>ce z6?>hDpU7FK2UQ9e;cZ?iJSmAyHhsM2>}ilH=9mr{_g}im|IrsTQ}ez#1I8l%iwi)a z_PiXeHauCLS817>u&kiGzU{h=+*Qvw%GVYuje43|H!;4hSt{hq4&kUW*txo%WNa1_ z$Mcr~ZAO%|Ip|HA-(RIVlS-B)4MKFEB-#^5*Q&6+PwZo0d^* ztJ2hF)S^RtB9%kiD%f<(l(OBJaSBcF&P*4oGY6!78U_G9{Yg|ROQp5?rY_fg_zNbT znZ)#OXT%j;Yw34Fo49HZ1Buz(T#}#62a{%wTa{Y{W|;8Jrqqfamby2bp}^Lv6Le2rlp3m=EQm&_pL zQ?nFtpSJXrQ88Z*JgpI4QS&!ibZD$3=6u+U5%(q!4?s^a7pk24V8okfhUs`k^0a8X z`TfmY;lPW2iya)({^Pp()D!sWO{qki*vDiho#&ODM}9ysoRR@BeaY3ZwBHIDIeotX zx-W_qav9lC!0!G2R=<5b#r^<(G8$0R$=sc=3oLRYx^1(0t`DQo zx;{9Pf_8K9j%~`RUhT~DYVi;vu+0XkIpdai8F@z9D3uI-0xM13{u1}%>T>Ol>aSY2 z5fe0`u_e=MejPmpAYYc`ha--KXg;@v8_#S$$GG`k66)OB*9GC&eiGj2Gfc3i{6W!>d2E8EL()k()TkApsqM^(A5@K<} z-mXv$i8DCr5P);PPr(Ck|L3Mrv?Q^jB=@kOHt%NT*TXWi>ekSSP-X5|%l0;odGr4E z=IT-r2j&9i;tP2#%YPQwWKuk^$h=g|BTiGSG?9v1GIqy2I77`h{QVbqp3FucWMGDA z6KNh8&`?oIbhY2%=IjMy1C%pHLu|1#jb+fYHcJ;&>2FKzOsZ+zX20FIDo8TlsGaNwUA zyK}LCMLt1m+Xnfx!rLL zWtJbh)t3e*m;r+m1H;w4AW^zn+WHMZN$0SH>jJ2aZGR zTht30=yRyIk+0!s%TC2s4-Y?$w?0RM^9tVTJZf&~Y(^b>e~zTI(F^j7HS!RKq4Otc zmaM>o5M+KviWDno>_1hAg92|QbC9yMhn)Am-1R3P^>ZX|Jz>#uC+=Zzu8dCZTAVxbAh568P1iP! zwEliNZxxk7nf(p>7gi%E@T#KCSxED<_^6U+p{Wxf|W`6?QB17F^=!FyXa zLCeQGH9GtLNkYuNeq8=#3;VY@IFy(&4||jyRlNJp1ACWTnIOq1|%HNLVrMo(abW%FM z%<_SiN)T3MVjjz$H&F@Ka07!Ek^9nL8h`5nrSZSw*~Li?9Pr9qh$$F2%a#2CbHl=f z{u?H6W!bYXI^oT#P>)oueq&`L?HK7yK=Ux~xJ;ESrXuLmI%+c_^VfeHq|^B1>!Mg2 z+|3Pb9`TZ7YJ+u%$fQ0{(%i`Ixtzi&8Njy&&t-sMzKy9nAPW$EMFcm%7j3Qa!vqP z^A|n&8Z!28?O0E8zs|#9fa6nFWl}4xHe-}|Co;~R*GmIzs1dMpgkW|O{o5KiqOgXh z8?)GnPz)Kb)GNdOYS!Bgqw^MqZ(keN5<0)s3HeVYWG{Pi#2Ud{c^@*6W@GDqT=whc z;EXjr*RotnKPB zi&R@=`(BT`r#@CLovfX!l$ccd*SxQg;iT_A0_iDthT|StfNbC`Z`+ynMH75Hzb2K3 zBjpI)LzX6m(-5`0o=dGExB>naSjtP|;aGh+9SPvGR!c#IklC@G(+z%qVr|Q;{IgT& zox;69PWqOgUVNDVZuld@Dgn&1=R$trx-Z?Zbo4Q9PW@=4@ZR6Yso%N2sU@GcuAsW? z?&6f*-QwugkK!`Um-EOAS$XWp3YAD|uqk7VFQoUq#X6qu-a#LIVr3#F z4!^Onm_Dc4X(cXa=OxK-vvAG&q6Q8DhgZj%H90)ichhllh>BD7gnvDFH~zn z`nijd&I)!ygO3jxlRd4~pH6i*AXl#+TJCF8+W3zc)HF3TXaE);ekvnONwvpnu|D3y zf(?q~)JkW|npM$}Sp!dDQYue(&CP8)JNG5SGxX2{51$$6x$0$6%9CxU2@kcUX)BTW zY}wF4n9p?kG{QXdw28Qzle)hM`6AFY_Q9SI}<$P``4+M%_H-$0w-TO?hQ1{H5RD0(l>q&T6kB;X0%^XNS|D zBH0*EM}uSGA!v*j9AaHIPJw|9H;)T%WoG>G%NscoT2ftiJQHLT5NRr(NG~ay9{RS^ ziPb1COZTjtu2m#+i;AI!6Pq90n~o|nBIeuKt`<+Bd6XkcfHBh;UshO?ZdydMtTA#k zQu8DYx|-Y;3BiK+i_qigULi}H)X9MQ z;B60gf%@g3*0BhP0@>P6cPIg3efh6k&G4Uu*6)rn-5&1a&k8%jd25CM)03HPy0Is( zv?}}Fk02oqGOQ!Y*(y<(W~Vdw7%p-fu-fxtvWB&F%g#GRy5TQpxI5fjVjA|$ z#I+bRSlZ#TOhN+9?6svv1XFhMJ?FZMs`C!XMEUW0MQu(VA{8c+nF^KejtIfWd+uv+ zU%B7HIT)k`-!F-bw@jWf>S<|_o;bTJhSQhUtAWh$T#Ldl#=G-^D-!JCric_Z%IY-u zC&TW>%ytAJcBRZ;dU#wWM2xPX4Fse8SolXuJXfglbs_$j6B!jVyM#0#?_JW|_G9k* zWzV5vBx`}zu_dmL*Xo%-z2+0`L`l0EY))Xht0Dc)`)ec;k;M7eKMzow&8nY!q{vex zqhey2%np|}>Y=h#K?x>0t|Z_7i+LuF>ZFKev%5u5?W5TUiQ&PuP>U`}bQy(8sKWd1 z#ot`tEYcBH5HgADQN)-!IeypHye4K>={KvLXNx}_WI?@PXYL9~2N#tZJhn|Mhg?u! zP-{=?pv$wA%Dma3^BfSZYZ%QlWZ)mEz(fj)BvPEotZA;Cszrr6`Z(5tT#y|+{Qh1< z=ZXYLq^}#_xho{JMB5QwH9)yL%W}+FK(d-F!SM4wN$(+5kDkz_ zregl_Y7g>Y9_mc*a zMZrA#dJBXSE&1Wldiw+(drq8FoQ?4i9a2wk>GUvgIYnZg2ar zG5EG3J6SW4Nv>ty9q*OLl>jmaHj&~x0Bnt{+Qm*?X1yg(QTlO@5RZ&#hABMmahVho za^*@@pSObSY9v(fsV1_Bwd+4;0VlP0``7uT^QRIy_8;Y*+9Ld2E;DuR7ft$9M%T<; zM%U=+-=V7w6-V+#43-7%#~)$wvv(v}>;%@T$_ajESz9HMRk`T&t^^7%l06Us;S-<( z#MlfZ?o~*bA8UtzcT(Iwyx-vzlHCWJbVrG>L9%Gwd_E?d3`JtSf+2GhA=5?(*4bOm^_iRyax;=-G6a=xj7 zJ$;8rNN{z z9sZC^jyuli%e*ck1v}FO<;RW^q>`)22L4n|Rr;(EKF&png)SF*OwxFYeHXG4TQ4zB z$w}fTV?4fsLo%rK!gq1|D(hVwuSB8964Y?|so^oIBh0XzH_!pcSiY1gwMZe)8s)E{ zpvxr$cofZKKvF25UbiKY-U~rC8|+1(ifjNratK;og|A;I!2#dx$ac!e-7i-6sM{Q8 z>6;Osb-<~Tw3`ah{tc9xzjI~`V7a=KQ=?GAc6jYYU~;kFp1PB;7v!{NN%6U~0d*z8 z3hpQvj)M}?bmp{jC=4>>x2-)PU}94n0%Bbx7z(b8942#hed=lOoIocD`85sie%CVc zH9ynN&9_>Q!KehxWL6K>`@=HQLbUXG)_FxzH;p;3iP*lClj{42CEBve4i9phUh1qt~63DQaKyg`z?-#l>L5PvF2rIrbYio8>3nce^xBnOt7r5a!%LNAou9 zy4b3BJIl8|PmBK3i@5Oe<_r6R={vKH@*LZtcMQb%2ybov*Jk;h!g3jp)3j=O^honi zKB#IXTAjG^VIp_o3+^E230Yk2 z6?7wuDfJr6=k$?L@w5uIZYFQ;&>`j7ip{VQ23hAGmp!T84zKW& z-W;{WLyp(%(y-otbQXQ7{iI=$+lr9oPdWNEtDvaJ-D1MDHts=ZYd=&og^%hr_dYk} z8|?W=&|}wJN#kR~ahJM3y)+6OJ0vWO%CXP{?kE8Lb3_8<^Nn=bW1^WAYk}$qTC~nY zX?}iaf7sK$^V%)q{pC?=&ui|sofHTrTbW}$+&z2%>^|^q%}c_aKCLO>GmEz$BjJ(f3pZRbWIiY>Cr-aMf^9?!Hq^SXVmHQ5?9I$@TpiwaKkuRR zJfh#c(SkG2@^86&uXMLX3IA0$eOvSj6TVIO1TLZt#ly2x4-13@myFi9*&BV&hqUwp z4z|;Udlh~H6aKO4N{D?)M*grB*N>uc^k4%$i^6XlY^Bp|21@kFUE*q2tZz1GE;k<6 z1<0DXkX;vUvRKHjWwy^Pi9`nMLaM!gn2L4li>1fFr6)j4CrMDW9@L|HOd#d+=(@~s5nQHJwGNYV+j=A3LVp0OojV&{G9ZqKM2Bj2U(2wL5xl zLm@=5Q&(_j$I{`B|2( zCDIx}1JqetH*vA@6F-8#(fjN&snhb7t8`Y_Dn0#tneT?ykGsQCo7(vsDBSs2`t2Z9 zCQk#ON+FI!)EIxFG^G~ocsG5%H&31X#d@YAy1{gWtn8KBZEqvQt*~q7I$3<5tKIOm z$2P2lTuv`Eo57AgN0Ft`rR-sQNK3XzqnLxmC+(`1Eqv@_Os_RX(CZElKo{9AZkkS3 zqUmQT_wSB7vw(JZJ+(0w{otN_pP} znY>?0>P-9_u0EP6GMg*1dTSgMc{kBGA6P@@T)NFF9m3>2=N|1X&RPVGqG{D!HfG1h z!d=2i7=D^QP}RhLT4wCLib8D;M6MRehNnnqXkMs}1qP0D{wos5cNU~-@tf$yD$yW4YCI8s7sGOlj_cvFRoY4Y(Fvg+6BSjJ$+R@7o z^-tH=45cby8KOqT6Sd(Jgw%Jau8s31rEauxca83&C+f16I-}fM4>lJ9D?LGFWk_7s zyGm;)Ptqx+ychJf1KX48)naXDPjw~K*DKl(wu8>`H6OZirW^W>HtgJhIBbrgVqzC61Cjm2CNgy2+UM@nsrX z-C1`C^Xf`Pgm%&JVv=XjqR{Lkcx;oKv<}lQF53k*D7nT*QopaNDy?O&hmWFLm~^ye z#Eb40!FO2Ojl9*aY2~4x(al1;io|_6e_!0h8Hn2&*a4GdGllY|0^t2Z z_d3lR#%a>{A-B{z_si+y{jCwwCID;#wIaCiE$9Qhs8u<+F_; z@>??ErgBhHzSY=6$`{L>M7gF4v+FZcG`U3X8p5!M;KWOy*$!s&YzwM8e$M@rh1>z% zNBXN1g*!MKSk6?JPS%jF5MvgzUpg3B`ak+>WaD31m*1V*ucRCCy9tGX#pFY*7d8|N?)h|_cG*i3d*~Q-* zeyVfZH+@8J_xc=9`+np4>Q&uV-})%=!q22(*tzj}6VaTei3|51W-e`wd4y`qHR#R6I&z<0M-VJ*A z>Sc-O3kRPzt{3TAWZ_K+?PcD8+Woe6(A2O!Us{H_!%^AC$F!3=^OnncPy;?>jN)Xv zHt#BCqJnkx2#qU>V&y3*LBZr+31!BcluLJ%+bwI3s#Vte8k95}zLT}OFGeNqa7dTN z_xpPcIS*%ni=wS%LUbEvjcuOB;>49Fe!27yuupk|eZG~?a>-`oTKTUx73$j~L*K|S z1$?6s941Sl_*6a8*JP}l6-xzx5Htu0k^3 zR$ti;s?C;4Uw_YDISM~uklZrv(B!HH>n+rUSb*riwd%sdw5gfY=r6`|uu6B8hmn3{ zEMddd$`7sKW*MSs>i(9YI3+Wm3Sm=JbtmI-eFxO5+R47tVqx%6AZ-2i8=)IXzC{K1 zOx(K%ub`-8-Yh7V(x2qE+CVdNV^pOeX~KRmFhgK~!7_C1NAS1BEW(J$iT4*6K+`9;Nq{E7Gn zHK;<-(Mw?;xpA%Rm6XSQSN#dD6`BLZ4^!Y%vPLi`B^}l=2f;d6}rHs-pBFARxU-Z_+{!AidWRNPy5wD3Ov-LfN1{clY`4i+!`#v)8`h zx*+%5bDw*dnarH?`JUOAI*Pd*Ho#p<=&okxzFDZ?Xp;VQmGQ5T7iDGMTXV0Q;k~Y4 zmOQqhbUm-xZG%gR0(_vpRt97MkZQh=L6BB;IBUI3ib1hWb<50o7+(5nKjXSMT}sr6 z?lhptP2mLEs7e}|SAdrnB{5Qj zRTn1x2?wqQBHs4#?Ks}O`m~>;VyxsVH^q-@#P>rCy4rG;Z%O1o#ro7m$0rY@-y4YT z+(r9IGHoU2^sBUFtk6_&cW>}S-6LJRe2?Hsy!@c}om~PPIqtYy0p4TdlMY+8=N=)c zq`DWyc}H`gX|7h}UMa_OUGuZ8uV-qmcg}XxUb3P=sK+{IRm_%QT~=e_;m_{fj%l^A zhH1EN9kcG)Dt6828}Mj~zI#!46JWaoURGtN71tD2263Uvi4hseU0OW;ws+2&i;< zHB;eN%y+OWF~A_8Bps$*rU8nf&og#0)NfmQ?9FDU*L>Gli0M2TkU6U7`wrM$EvFP@ z(?+D%_g})9?Y;OowjuC_>+X$B{ho9B-;~k+Hk}MU?FNKo(4a)J)(sd&Wmoyotnd|pF)0~c#R^wHPh!(CSsu3}?3@-%9c8#1O z#P5Y!J*_>Szj_hLT)lbe#mR@%eF^zcY5&Mir5}Gt1;~HHCPU-qhlmCvWax=glNNTt zjl{vMWi}pJZP}o?2O0lDQ9nQrl?kD!l=yfwcG#Fwov}txluPqZWQp-#WQqGvWQllg zzv^2qI4XfNE|{uJo{zu1>msUkKl@)*`G2dLu2auFDZ>}b6TQq7gbYq4UOe&d9`7Pf z_6mIwqoF2BM+tFq@#maXKDpE`b85-x8NJ)+aS?^0G$j^8icUUU21vac&zZ z*1S_yQ;^mw94rh<&D#l=7ZAG5w#E=lODIPO*2ottAy;zCYNMf5uB8ZzI!_xTth1PoyHTr}N20$Z$mNU&zq$B4l{? z20DvWSkO}>uIqf++OB^)K0anpdot6%Z3}CU_-i zv<;>2l`W+f*efVGyY=+kS?z9CPCfm(ayP0g*;DcjSow`j-nVb<{hlmn5ylaX4Qr9D zXn3=_ngPwK;eO6ug{(+6D5L3L94`b87=EstS^yIC%o3)2*h)x@4^B8(YF1`C-`6nB zPHGirg)P4U<@|UN%lwFyVt7_=-dfH6i6|kQ{ACq%sun4t7IsL8N7Da`N8XqQ47*=x z*;=xaG7N3+D{Y2lzUCU%PK?)l@>y0 zCG!7489?+MFL3cY(f=)a;r}Q!F)G7X_xU%t>0&Gr`TrDQj8c@*{=nP9&R+V2a3a&i zrx~;kQQCoDA0=fm5ea(!g$j*5lSD?y{j&jAggbK1{<4qP`ADx_B za*w_82fqGH019Hnc|PAGxEFLYc*n+UHt7H0B1FvN6AipPSpX^B_J>4+-3lePPJgHh zBB^{wC(GQt(5GR(L@8-24!eAO0)M&EPkem!GmKGWjI!TkMTz2tEiJv>>y&%-;)GdA}3&@73V*g91BepP?C&h)XZbBC`(VPI#jdfvwK`iB5c zNb=2#N4#1^qi2}>d{;iDD3|x2q77EfTr8rxI8EPchi-gIDGvYc$a_)lA&!o2Cf!GM z6_)!ta>}RKz0~z^OU7kUCEcFj?lrgg0qT9LsKRpRqPhKL#?~APOF-~Gza$aDz;9on zk9trycm&_;eNmfPUI1<78bcWo1~NY@e@9%*ow4NDKQPW#_u>wpXCu#oTb%*V?m)jn zpMr{LP-fZoG)W6}4RUb@Ty^gCx1Ug!86axr3z@Xv=;_J7c5=_K&aX<9TKL1 zxiH!P$A6BRQ@HtW?EJs;!Ph6kWdF-6^UNeIsEL<;tE!5O@f1C{xI7hR!i1!bDC>TR^+M`ij^M#$VeZq=uRkQ%64J zzGi0r!z~8S61OvX(Pm$*rX5u5eX_$v%@wAF04#gcUs_k65XC3X4ynL<><+IA>GJGw zUX&yXDa|ZjuN-qTiaIz+c}7bS`W--WVUfOEGZ30l`S#>b|B~Q|mR##fPhF20W5-Pm z>yWfyZ*A8fca|A^|C_t02i=~R2QbgI>=fXl8$&vn?{khd%ltu-zAn`perIWG3!Jnt zGo>NwvnTXtZs4%)pU20fkx>jy2nz5Cgy@8*5^e5ysHB#%9sC)*Kf6VKuL8$={Z-p# z!inbyrW<~cg#5X!Wu*^@_C|k54Gz({a$(E2do-?Mf6kyI?tVA2?8492xnMCzxMpcX z|Gc~BFQMg>gXPk3$3(w}~Y;0XTDjPs%QZCERaSYD9Gy&-wu;ICZ|Kkrv*94)$g$?(Yv z@?kxiUgD+xOllG*YCGA2^_QGJU|!Z?)cuVZ846aOIvGEl z_UB-Sum*h3Sr4bW8voutp^f^>Ga0{OXxRyHTf4|YkVEE+62j!s9Q$JRvDg7J=#pc8 zH5c^PmSc{leI*Y+IVauE8IQ`7wPdFK$hEkavp`!K+Ky{gk1pP&JSHY)!69_j%OCkM z+%F|di%SAaVF%&b^25uqyg(c&_~{o<7Gk%(My}_(H`nqWt|wLG%~IbkU~gDlVYe*| zNdNjVQThe#n_#Zcs;?~xMS24$S`CWLo|% ztku6?9BrYYI&shIriOiY7;;mw_Z{wb-W@V#NPePJvD-j13bPDW+poqm)txDw>wTGd z$`0yf!RP59m(%4Y&NdQU9pohRCY`K@rVk=}!pINEM%*)~6NInpjC{*W)5PA4uBD!M zK8iXvm5=PUr*$9OOl#=dcMDn7aHZpC)hJ~oyq*N{+9d5<>JV+fSD&5!7Iu!!mkU{n zm=s%?XIB>!W(%l2F)*LqkXfIqjuM%LpI*PNxG9Hw&j~s*xy1ceR6svx;9pW>Ho`|m z3@jV%<%;mj$IX3}8^l|vV57oIvCRbjlk%3Hl%1;Cm(A&toZ%o5n`Qd+)^9m;2xlr+Sms`fG1 zHgBPp=})t0CYCxuHPS@)q1uI&Gi{Ly{i?5GXLYOH(N`~*F|j`Q1s4-6@X%*XDN5#4 zE{nOwdlp98ZQ%-GQpCWFqH?di)!_r2* znU5h?nk0P!4Nzm(vDG5p+t_SyjlFDWxp?*4LrrnM(mVpuoMkwtQ`k9LRHJ{^K)TiX{ zjpwpRU?~H<+ek(zAD1q}eH%Ir9enZaR!053?_5LPuz-qPbQg8Wm&vT*FKa38*{Yl) zy@VDS1thG)BIlInn4_Qtj;3=zx0tiXFUxZX76MZ6D=QE;*-%8f4Grwu%zT2AfZB#= z-te1+p$diD1Zxkr_-(q2UhW?>vBlpnnCXphty0h&T7(sQw!b|} z3i!;jntVPrX#L!ZKP)C($W}^4-Jog3-TtO+&|x8%fJ{Rm?_02K zd4S;9Po39R<2yZfp*upTwDO@=u&*KeOxeCdF!LOIyfDL{% zK7yfN1AL_&G#+apPHtpBfxJ!w4b}cqXt1di1c+IoYS5kQa7bdZI_*(t(#s_rdxC@h z-YkUQ)`6+}^AN#)iHh~L1EL}?1E&2jhyHQ``1LzG+dHc0W%-KPNM{C6y+!-x7xO{x znJ${Bbru)wQO;%B*Bvj+JaED4#W|>T8{jj8do(6tkDi-NibD0jM6gO@Vpjw?(EC2y zeCN8z`;}kd-)2CiYz-G{H%iz4563lawPQmV!}RsKE9ckc859eDcx==S4( z;l4*@VubYJzmL`t$AP(WpxCa(+Y)AKYHFinYP`Io^-CXm2j9?y1kZk)_y?rMWP&nG zT?or6+3}SBvGYv&FC4OKNccVN!rwO^@+kd+&F$1zKZ%V_L!4&qHG4}czX?@rZ}y*! zjzPQRG^nW&cF^Ct7Iy`}qDqAWkKHi}v3a)a))isb2{#)#W_N8({Q7m}eDkY*T+B)q z=%I;G&m!)z2}VLcMOdAiJJ;=k7Jot@Fa1)5@x}vey9zV>h96$v6)Gsz5T?z72-In0=5dCF%u|qC6?~i@ow3t<# zvd1gzrrh^vUv{JW#bJ2EwubZ;n_J%9t;%e5Wj#49Jur19x1PLQ>vpFwNZQcH{?i3Y zeaQ)MxS9vlU_4I<_afynq)nO1P967ZjB{Jih<&=|Jg*J?#uBS85>ZZCpe@kfoR6hQ? z4dwHqa%QL(3F7&+OHQBUKc{>9$}wg)r);BiUwH>a}9<)c@&Mr9RGuT9BEU+n^XmkvNzbR<*EKdD;1K zbY)ghRBHe0IOdxLTr#Q{<Ns&c8Y+L|Ge~blb0qpb}cHiv{70r*3KlrvDE;4*iO^vr! zRdeoPzm+2oFA7XQ*IFPZ(?u z369OT2b?g?ghmE5Jlf0HVyEC6SSc%H)T z?5q<@2?TYj+8BX>p`DtnP#arZsbqs zPXVr2z6dV=R=dtn@H{tC;%!d7f%D!}^e~X&Q{+=tF@j%C*@Uq#Nn{q1I?r@Iqh$J= zZtcKzm2o7J1AeAMo%63ebvePQI_o@6Jgy6QR=C{R<(>C%$8A8?8N)de#Kcrc5Iu|=B4q&GO+IkHndb=AfejUgkilR6 z*{RLC?lPx<+!H3!AnPa%@#+jiu>1*q;~|qHccfWsjnUFg;$g;aBKa)5+uh@Qzo@fC zALdz|j9Z~bKUMG@_c`psf5H6v%WGExNKwOKFXd>8M#d$x-4AB0BdX6_=g<-S#}VxU z@y)JLIK|q`aZ_)%-(E8IpR8zstPx3@x@$Eb2~-+>J91x74Yim@( z=PcQuS6f9*P6nxw+)DTLg6@n5Gz3tmH4ij@j;W~zD!j|Cc5(Fcwn^J*cp0vXr#=7X zX*C*KbhelS8i3r&N;}PUx=(Ca2lf^W8ucdZAV{xJD8w%V4}LRakqrlimR@yDffMx) zXmCZw`Np%#$=7st9e}R}HW^?lGiJ9U1cL7BXNMhFH9wmyaOuWn{^}$~rIn=9PIYPP z3#r+DD982PvJFU{ADn`!PF-T{OVSxakO<+8c#xQH1CuxF;%lg{z}mIuTZp%$Go(xP zsLrxW5WD({#U%m z51kr;LXew>0-3A<_2&EZCWeFKKI6Eg4dR11JX826DU5t{U#2R8O;k zLAxW7kfyArA|E!Rt#8TLjw)fQDI3+C=yChx3#$DK({Xw>&Qf5A_p%|C#nAMFir^$R z-!j;F=(6Sk#R#z>o`CvbJ6gcy#-YT-x0;L_ahT5Z?;nd~JLBc<7YObCCZc-XOS$t# z)OwS}v@gB!fv+e}FT>9zLt}2P_iklp!!<2M;*GWmR%zVBumUH+aZ=7G@Av0>!!euz z@gER;k1LG@!$;1luR1=VMHu2SQ0e1nKPJa0fv6LFe+Srfu;FB-tiTU=l; zaRj<3MJe(B4^vezi>2?ZcTDNWx}r=vRck zxF;SYPR;#nIMP+{S%RO#so7%lx+I2}R)2-OE<53PF^aqJ=T-zwO_6w~ftz`Y4X)V0 zX`I7p189NW5!KA5**^GYl#~f?K9w!quk-&n)#axRnK>v1&zqx%T1mx5&Zuv2sCmzlE5N3vh$U4e3p6T)29lQ zze7%;{f@F>YzUz5+7WF|P?e=7!AsU{o;*;Uylm$)nD%xe9c<#Fa!Ka>;UYH*2|qkt z?tx5iCihiO?xN3M?Eo=phh^vQoOh$ep=x`rA6d{-JIy}YUMDk#>U-gn6$efDGey6j zo1y-3<05XTB8WDvt@n{w^Suo>+Y+i9J2fn4mP68g{n|cTnF%P-@*Ny8ZQ1jYEQjns zN0D*;(`x4)^*D0Uuulc_D)(Vtpe-08Ci(JiKY8+#uj7H-)4Q&Fi{QSu`Zqrexo{mf ztsFR>W0?p)2OP>`6+1%^0YI*SSm`z8)kwOsxSz|iRMT{eAG^XCo(GKbVDWGZdsRh; zl)Qzd5?^~YKXg>=X_7G((ZSKZ$&YcXwrK)ee6aU|5t1PkiASDcF9Yqp&b=3bJ}OsV z+#IiwZN&eA*i;5~Lhf(e(eIU7u?x^j1>s%gK7yIe13F0j04AJ!W#=9abWLQz1X1H> zIWc<5-3a!xa$iVko~G9+^7Oc}?k=@WSKh(7d%vLjag%AB2})1&+i8!#e->wyRU4<5 zpQ)76qJFpJm|?m6yWF2g&y66<}~xxK+Lr) zi_DDadqi9e3b<{}+gE+Iptd<4QR?U3!1}xV?inz!l@osOvr> zJZK9;&ZjV!N}DS#*vxa-nKw2F;W5YLpyAi(HP==~jnitjrZqMF>LiINqvJjim$WvU z2_n%{-15PE%Iu_L8kHKB_Nz8GFM~>Mc(pm+@a;`xEc4yUOl+$b-}B`=5H6u3p%8lR zK>v)vWRyEdNxDLvIfV*u{aQKBp+qbzPu3i#8LFF*ljdWzg3L~EF2SGPvo4SCd zM&=f2njhuty=Fx80OozkA{a!5Nwa|QH)zE?9B3+*3m-a#zLw0!c<7f_vWU`4CJ+gr^W6Q6+N zlhS2@+i)9vL22zmj*4>=OSVvbXZifYdT}cWmXY25Y_qe+oA(!^ELPjer=Ld9QX%DH z@HV!~p;)FwJ&GNV(%bj_DnX!T6Mh@_mn=6h^kI>>@q-P$O5v(`SjIJ{;@zgN+!t)T zT&Sk7o-q4d=?t|n6u0ZSqF@0mAB_f8jbR<{y<&~U<3{yQSG4yV{08hG9PPbq^5255 z{TloAUX-81=yWmJLbwVuEm+)MaN_B0H5(-WZE)O%@6#~ox#ZLXwJi(=`b*;F$Wy$8 z-XA*S^N;2nxpa<88unX9^^y>i{St(4_UkNUMy2wB95XgDnHCe{hq|x&k2=5-9{W@9 zg9ob26^y|X4@TOSZ4;1hJiPX{1|IYmi4Tic7Lyg?_PToJNAuca_b0|w5dJnw%ZM)ne$r)iqgQ!L)@zMC+7zmAy0xlT_vHK$Qrgco zRM#rCLXp?aBa%}hUd|rJz@x?mY@qzMaLfc_4GuQYx$@wA2H|7jpnFo@|8{bs(J7Bk zf^orlM4e)Dpew5R=||kS>!gg&o7&OW5SeY{9=9K~(*yl(stxR9 z8xah(2*!a3!bTmIh|@bITo$=W2l>(ca>GTmF0t_k2$7-2xPz@~h}~YhW>|G6e&sFK zi9qF)V|-%YxX8}KJ;O|_`+&QG8*Up8e;dT8w@c5Pjh`^IkjSTC6ENvGKa~I!-}0Ll z^w)h7dfLl*-z@+wCC_M-k8&qGn-O48rMJC?^eS?jsWB*v<9~D<$oYvzAZvMJ!p1Z& zVhM6Lk1+NSLP%!WsRkf7Z-nQE9moE1b4RZ04BfQZ8Buo6yl{q`@FXPOWa~95cp*u% z5-yNTIeI&~S1lQ~FKL;k!D7pron%fqv&}0J=V!>& z&d}cC9i)9MU@K}OA43^pLiF*vKeGS9-m};&XmmX^v$^x>+CfzBU7Q%mVEcvRS0U~A z^H-KLb0(+PwD%m$j_e-it*+$1k0XWwu@rmkrd+LK>ilg20aa%RMM8=Wn(RR`jIZZ~1c5)~?a&f3|yWXWw0`raW?2y%-!6kKSjZpFYSPxjGSS za@zSG^>nRfn}vNi@mEGKF{Ur#mdWpu{0?n`7rMy8r=l*anYhv=fdH*B)Q*&OZ8{>I#cX$rJ zjkbkV!g;d&XQu-uahi9vo)~A302=QK?;jYIxI%{CpRwKFWx1G$x4KRO&GZwS&bGL5 z{Dnz)C6$~g9r8(Sj?jrNE_~BO?dw4)OG76{|ls}bnUvJWJf;ed!>Ya=lMj+NsnOMx50Yk#LC4QX~UnS z88_TV?|1J*fAa$T>~hpHX?0zJp2^{2bJHN^lCE^Ke5pPjI8k^?GuY%)A_S=;%$!nW zA91Tct=FEKj05_0Gq_B92VN%!#vM73X+Z^SExu2f1ZF_#oxh0P_c-E*d?9t@*Q5n>Of0L(#%XVC76jr5Q6wsNQAiUqGdENl)=Xi6Pa9;N z$E2pZwYh1B6EaG83Z(|n#5OG!*NCx+D-p`p_oi<3hKJ40m(b9p`wgN)9`DZ=#FoCj zO*q@(Z9w<>uKCAz;TYFrL@}3N_~R3~%0cflXZOy!O@>!&|C2XZ`r3U3KFgtr65q3B zeMR4L7i=fDa{6tew|}`D|HqOyK9jnmP-}DE}j)|N8s>wHuFlPBm5t%A6y- zHqdvk_GRQ{r2i5@u0qJ?P!)I_n1{UTPaZ{dU%?xvp7`ITI0P?gY7!Tb6)`wSfdEe_ zT)9x;L`heh9CI^L|9@0CDZLNnl(p;p1VGVizs)fBMNM>e+!hBn-a<)V?h@XWms;eZ zun2bQrOBilJZkGLMxyw|4K0_@*2g+#{vXZ8FElvH(@ILj{;^L4n=VUCvs@;tii+Ih zSI9IoH`kNSIHG&puGV!#_v2mhg;4|Whs^Q9gYbu6Ot9q9@qhm8CSqgbX(2*<^gYf} zG0XC!_(_y;V~ybhdCQ$)z#!jsA_bx=EfS9^)1eotr9b4B|CP=IB7kuD_9`hHHpz6f z;Zq~7YmCQd)6kIP{A7fF|7Z*7dFljW##K~S=FWP|@GduVqzOZ%Q(IFE4*I;{3?Sdq zU6_~OZ}jUC9uk`PSJY3n`P}9uZ}T>21scu}WHg7%@oz>fNVLPP6I^2Sg0>jEm{Q-> ztF%jDNzV`RxV;|>3lzmTt0HAajJ<+Xu%vsA?6xzlr*pd@Q60Hq)h)AKQVz=B^Rmi? zAL>#P)y%Sr76OIe`^~l2uNHEzGvCn~*r+)@!9vnMX?z=M#Q00>f6wD4b!<91HmmhO zIZ7T8G%qCLvu>l8i)i-m-jKX@#Oc1?j7x@MKiZOB(PKk2WlvofaAz*f2jlX_(o3kh z;AomhP`BD4a@w^)aO-@G)$C{44NJD}R#A>ZqeT8is`?j{bkZ5fjE9&t*D^niSBPLw z+cW&~CrP-b){VIb(Rth#61UGAWt%B8um9+`2+1Uf9#av|2|PXAc8l{nb{$xIU573U zjqpSB=O1Fciohh#K_)XpG+c1rynxVyZ{GS=Ect#$qx(+zEN}jno|qvy}Y3(nJ~?FzP{10PM-r`ib;l| z`f8|~JIszj6Gn+w5|8TjMrzI;R9Oz&{FBRw`v}#V^q<|%F>x;FU!)mC9ycX1iPWOM zQ;K$p?gkZAhtNeijxa^b&6=;gUoK*V%XQEwb3)>(f12&MV-+QLP9t><&SAP?1tfTC z?Gioq+!F7H2Z=EZEPZ(tFu8X$j-zrUeW6sVrFiqiTE*vnYNtHD?Y2>P_?S^nAp;`j zYy+0gugyWqO%+>OsMEOnnQ~?cT+LTpT|SdbwF=PigZCEb4RmxnkL}A>xmawt{q%+V z(vDb;6>dQcZMG#$x$U_N-o;^{%G+N{I!-_Pc*&|3KuAtN2}chJldPFLXpoT^_tt|2 zgs5yz9L_%eat^SBF>e)cS~kBEe&}?m-1>N6W1nweILFpc_^H7Z4=2D>HlpllIYzLp zZ(sG2=U75%t&mDd^M~Cs)CAc1rbLma|EN`bKieyl)qs~@e85ehhGpir@ zWLFJL^XSBvHT7~#*OW^q3{tyJ7#4FE$2Frj|+fflf`gd+iEd>v)RH#OmaZp?~WT3 zJv$#*M;Hvi(rDkqbg8SoM?8#00A33mn)4!g1ALnFfT?G_&E(R#%76;P{+AG9g~wM~ z>__ZJl{^Ck&)j0Dzd<#QPGjzivy@&_p=xoURtWNz*%f*RsHL zT42zR+S3rEB17TLI@Ce;da6(GyRYR`Rf>y_Ak>cM%jP~WX^m%-Yc>n6%0gkH%ofj3 z+3_O|?LO#8qs$4`Gag&VowU5|cON&lRQkPjpl@K1la{rhn+(04$HvTydqDd6of;;d zo8N{U3Tn?Hvo5PBdkp4>aJ*ac8FDUOs7+xISC{ub(=O>J7i9r$y{hR0BEC;g8@fbr0#o_+ zZJ=UO?S&w+q@7=C%mLC|5}KFhA+`z7_kp=%>2Z>Tyu8-ap*5Wae&Pr|pR?RT$w=mO z3rsdIB-~@4%k~l#NobBO@Eu#FjcoIJH7@UJvKB+Cnf{7kRA~B{UcY;|$f=ECJ?@KI zrnbxJth0F2)R|OWmT23o9$vjN<}$W+KcXU45muz(gx_qJ6kOfn!rB*W@cYb^p$gM7 z;(qQ$w2r;mdo-D!k#yw?o#s#}fM=$Lx+?0vuluH6h1YB$wo-Ew)VE-5POr6GpyO}~ zy#b>5+Wd^#b&2Iy?Q6HwO<8jjDAii;yK)O`COf^>fQG$qlaWz%Xy|Gw>OFbTEPfI( z>k1dAYhAOdo@Gej2J##6i_>NdqVj_x!)%{yP!u{DzSS}>jmqc`&1o>>-`3h$YB#U= zjMbYwIhnP!AXghH`>J1(R0($SObeAdh#TMu)1(NUZTmv)xy{IN5CvVTh`4MX_ldz^ zyQt1$c*X^Gyn@=@Y~86Wdm|Oo_0Jce3o*z~wZ2jce};65DDMda7BDV_S!mkVL_NLu zV92uZ5sN!BXg%8cIG|9>SRCSCD@!yj@CC(ix(1+PMQvjj(j&a|Mb)>K^py$R=O z@X781TAYfY-XPCTv;CmHnEqusXW)@s#2S^2A2w+p8Af&Wb1ivtU}Tw7T0DFU!3zxEEW2trzBm zA=7%BgKKb`t{)9xV{@w!ope-luHnJGVh36jWPnyHr9Q z_=8ASPbJIr4{XCowe1hM7j)uYOmL3fKqXJ?Ip3BWa*i#wD~S`F0EI2eO_3g;cssZ0 z5Y71?nj5!Oh`b%fy`Mfg?I(j>o8h;Hot$b4t`go2nv!?_naXeJt1VtNo3zJ^v`|a& zLLrysO)5F>?}GDP0c>_t2VD6B5I`8yMCPCn1^P5pvXRH>VyL*?v9P*GDBC zWXqawFT!zNa=g|(T-O(V4e53=o~7^3kRtT7=2&6m0ZYCXMX4q&JQgP3zHK<>p8SQ} zxiJic(QgQv{fKPeBW9cOx~(6KzCZ z&8=A1g{jt?{$AF5rz+4`WX2ZeAjkdmG$&mr|m#ZVz zjwK8~ZYi9({U&CXXF-w{2#wajxp4B}@Z^b&JV!siQgGjhHE6&ItTZgguYWglc@mYx ze1QBcMD|fxLVESuTYK0vpEAFEk!#Q-y(P5Qy-+BNO-n%him-y4o00=2LmG_iC6k;m zc!hmuPgdy@h6`QcD9A!(t5=g%#>-MX%trAyC6uJkXfhRYB{2v5{$wOb`f%hW=?8u6 z;r7Fw>E7k5C@vRYC{plq_}7S*T9d`}_MeVl@cn02m~^a8<_dPV33X(YdAOEsqxzd_ zi`CD^+A&yrInwvIU}3}yRWN+#*D^Oml-x1*0qwStT1HL=Vc~4P^<*|n66JX+WE!~^ zWppyO!AxBlW;I4>S&yYk?(OGh6+Y~L;;h+Rdi7K;SZO1ibeIHAYpt2cCsvM5RDg!f zw~(W3JA#=bYi<@57wa5({6JAG8L*}&wXxkiwoldvy%~3vFooM zXT$?>f-82~HxX3sQJR}@w5JP5*2zb6ayqZUH(@t&>(S#x-vfKcTG0SkmIIUWZTN~o zuNM=Lt#k{+B-eZW-Trn?>Xkk|>)Nc{Yet+}1E{5jv{9Ct1A6o3myt;@?h}WhVcUWJ z?_{^rQy)V%qxKgRmq*Z0Fl!-d_Rfc#H>GRC_1FQ%G_QRobEHo8BRCF$o5)R-mnYl2 zJtg<0ETm~^w3?6!06A+CgZ!Lzr({DAvYBPv%q-0rf6XU5Dv1SbG_q|-(pIKiY-CZo zJ-1iXv3$8<<<X~|lmdnv2 zJi3auM8@_|TR)wa^FSXSBHQ^`XR#URTZXfX!rZTXPcvI?hk1KcJp81Uj%$3R?y1L1 zE{!y7zSs?|LnUbuwC@(vwlLpHYXN6#yx~E^vl7I8!npXpo(ErS^Deiiw=CJ@|qMk>6sx)BwLJGbHf|YplAL}zv_#tmr;^9*|c=F=>4%dhd03 z-MLaM0oIN_Z(zoVr{?~m<8ww}R;*YF`tH#+-Xkc@;Loz|6hBU#G#}H58%@`4ZHvm! z@)R^c;f|Zq2>}=19hbagEeaa>K*VhK60K4}Yw?L=6E=S9>JkO-9dm^UwufEo zHxlsdEY^RL#NDIss7%5N993BsvGo3sz4$=+tSGj?kP8DINGaH}R2km}GYH$eq)kkC ztH0&f&aL$2O;AbZcSUebxZfJJ6$2f})LBa}aAW!bzE~sEwlu!Z8GSs3NUxI~ zXz1T1<)*0&fBiF;dZt2$UQ4vfD%kHePhq;NS(4DE0dIE~ zBRE0O2+G>qqwErbxe?EXSDvW{^9I>`>F%xnk&%sZX7}pSb@Wm8Z8SI;&E+SjrLP$i zrq)w`%gjOW=~NmfI*$Mvu?q`(cekPIKF#b@;`ZxJt@glzB#FM@`h$2ejyIN0Ib*Fs zdCK)XvIhH+ytf|Z^(Dwo=Y!3UkY9M&svL%FTmd9O8TK5!vsQOO>4YqHVtYqjQ?$+m zxfS$(a&fg$S`qb>?84lTr1AtNtnJ;wjxz4_+xA`iD$r1LSr<68wa^m2RqyhXc4r&u zSw%yu->!v40%VLhS@O^{6BtHeZ;6I@TF2r`NQnjG>wNxZW zIzCg`x2|P~8f>9zuhz+nxx90B|H!OM(^>zjJEW>@zwH)JDq|{NWu5?3a<#V zM+Av<#>aQ}iEn-WMQQc$UZ4aUTcAWuOnAL0aWH!noRdC{;Pzr|Vk{#v>ET?+0eLif zgCFLK{JP8CBt1+J3eFx9I72`PEm2-hj=)V}GLu5dS_0)L8^q=n1Se!ued{?rOCXve zKB;@RYh>rKJBR_U``^}Qbo3?wPpR!>{$pcm=%hxY3WH#(M70vf&?t59DL@ih^n!;H zv2?HrPM%f8I1n_F7E9%|&>jZCbcSs?NrIcr!~Ad~GU`t<(MwLN%89$pc}cI7S>%-Y z3(nmK2k$YY%>t$^>%KBNjvVY$Gd7=X?OCSmokH);CTcAy7An#mnPPXzqnD+jhQXUIDYt~Ygi#4B2Y z{IVMID)S(+@;rP&JL6J4!VkPq4$??H(jZdR@>v1yOag3F&J_uN;5#e{526I#5g>{` zUIK}ZJr1~Q@?K^n$ZeLFaR+wt3U4DrXyQTeP};+5a`#e?x@R;tLb_(1A?46xzLzhu z*4EaehUzyKcPq2Yp*YfudlR|EtV7!fNlRn8FB+S+$(8%qGW#J#_){T0HZl8q6^XJ~ zWPP?Ih5?)I8T|1!t7W7hf68vVBj&L7jL-AEOiwuhxYY6!h!h8)mD5?T$$jX~h3U{F z5U{StN{_3;p07Tu({}wvV#l0^Ne+>U*aJw-KXk-NCrI4zxF<7ys<>n&B z(gg(CoiP%PPB4C%m*@W@dvc2Ue-iSFFgn6NjIb&#{is(Hg2@;0d3LNn!K^wsSE&B@ zQTu$bVNG$>Jj}xa8`)2)HtMRT2FUs0lzP2nVJA3OPi4_s<*vw)cRsfUr);uCFwRfNjamN4`OuKB2@0ccICKcYVEJhFe6u72Fk~qQY<0 z#KOhOD#{x1;(oMipGZMo&z9;cBQ2rKol?-D7+^^&=f~}9EG|Z6T^rWBgwm~IfMIHa z{1o#)BkfJV=IP#3SNRnSnt0R*Mv;Nt~E>-Sp7rx z|D25yL;P2y`TzF^Qc1y25-5$zv@fh7qKxlabPibyb-4D5wF9=q{P!3kzP7EMUP?6? zb%IRlNz|W$51*;TvLOg^svwEH+VS)Mh)L@VIQ$9X(3#P(Q8haHeOEKl)N{oUEZTAtSU; z`E`GSR<)6l`*P-=yDky5nnw1UKZBtJecI@ekS>#-yWAgh-tX~jK zk)m?m-Sjl-Ki903y=f6lvsj>)W>dfFay4=W{QxzOe z)+=vW7nJsg?<*%-mq(rbsd;4Lf1@Q$O-n;#J=E3J)zl5x* zlKt`Dvtd*vTmJl4{$JFP{_oYUUt2Ey{>ZAH$32C?H<)x>rnix?Eg0=o;wrli&a&CN zjH^B@n5r_{yMaWp3G^p2+RQsGSB;OpO`e^PkuA5JnXCWfO2cF5MrPTh%YPL z`rY}ZNMf8phsRPqZclxei`TZH+1sGIBZ)>btfHjPGeVNI>btJgdtmUjuBO5RgH!pU zkZCy-OJ~3({F^gtF3Kv3wnk8>~J&~+4G*Hc){ zsy<}ycZsMyj4!$+^!eSsUQVrqdm)fQM5AuMUU@;YRkFj~gi^|vuvyz@^`>L{QpPDJ zD+y6sPlTb<&dWuz258{u$`klJ{J!Dty`$H_A$8-i7Q%PeVSpiTy5~c@CBcMiBT2dq}@_T`(-N80a^0k-A z5yD)v1*aA%E4`#ReovY*KSNQgP7g7BpI^n9I&&^&^W||(Wk*(BnRbypcdX^;Q8Lqu zG_|rj7Ue5PZzvO^DaRB;mpuEgIWLdE)6W#H>#mGC|0+w7a?6EoGPxWz>icGrB=XF8 z>ljW!oxY2((Vg_woi3a2-ZE(q$%BU>m9q1o-+&o-s9JR|5a70_Ep4YTGS`<@I-=aYf9p1`tUB%Tj&2Y9G*q2>v z>Z=HBRaFs(8Ka2%e=cAx|CAO@%vWAR#t(E?^DHXpz)TbVs(FMzBVoW8fSYA#X!?M^2S86 z+tdqP%VxbvT%Ao2F8;yfBr$xs7RJFbTyGX_pq+N!Cs;&oUj6xuwYh2eqinLuz3RDN zO7p+724H7^UNB}?;6A3*bo>8W{fo{G8@H|vQ#feqN zhpZ&KeJHNm*zF65B&-hu2aY?m;a8ziJlL8Vzn=E#6_knn)+J0a_PN!U@yW<6EhG{M zmHGA^AX!xD=vB#G|yeRo8~CJI>YC)#^IxuGOki zH&GF{Qdb&B#F@6LsI#T2jw)J;#+?vEs;;_aNgQ#kTSJ73Gs4&IAMxGa=BJtWW+Za(Vd+kl=wzeuW=laLGyF{Vu2)7JJCgkF#=?HHCxe|C zF;{X&Ak5oV2BzPYnrgex&Gld1{`R5x-e+@>7N~w}P&YjQC$L~#NQGXYDA0`&_>PrJ|9F$Mt4 zeh~+7z0$`nVs8>0YYD>$2^oF&fsg5kTTlyra8*@n0OqK}ZlOwjRNycl?@M|5;O6W!oBq8L7N&FW-SLwTmJG%GO@T+xxB zb>o`yg{Z@Nots$-uZOY%%Bd>FUXUg;vGiXSbyhmY61mro;aeXAce$;+t(LuzIIAS} zOK;M4?5tDf6*6+GrCpRu=y|NAB%?%oR6sw)8iMgtKf3`E4DPL)5e!D_g2ZG;{T)4* z*bF(5Z_m?IP#zsB>hDU8u`Gmp6(-W_BUISQx`RR@$RhQJ-t-tKuryEgWxFoSM&cQl zzG>ddgm|iF2@+J-t_+FvX)xZx4K0Db>NUSx2#e21(x&rdszSbRLuW-|8*H;hv*}YloqkH<+#t9if?`y*7{@?h_e;FC=S539InTPNmH9U6SHY0vTJ>9@)F*pH zd(UlH%|&e*{!03Z)gO5uUMSsY+Ktw3Sv8T-oFeSC^8>eUH!SnC#PLAz=AQ#AXu$;D zgb&Al_*m0**ynA2(zx}COd@RTYzSLqoW=SyF<{@?N>(`%IrJ3AVpr}AL^>|A$nb&# z9{wCot+K~cl{Gxw1?jNN*_GRkl}cemX_91f*%6}Z){^upGH|pdcb4rjA+*CDRy6zD zfm-Pa9awBC;ZWO+1fu?$@@4}_zLP@&zUWzcr;#oGNI5?Ws3UIg46RSW8w7PN(b4@Y z)2de5?5pZNMh$p3)B3;oi!N`PO}=-_kk6mfi%#mIUdNh!YF#=>?g!X{Tl6aDO;|&2 zv9~+lqb}xt#{x##sPrHO0%ry|T1ZP<2EPWFZI3&Wwo8QLmljN#WNxDeS$MMTx})Gm>HEt#&;}&X;&ZzvQPfK!5nB z)kKAMR&}Ar`tY0BoNA_Vs~?C}98oTu@Ld-aC8E?|ctn*~_^A=s%HZ2nUfDaT%w|@| z;S}=Bd!w7jOt`QLDn9zmx)kH|wzPJ^cy^eb(91GvID#LyS2xu;F`E#gXk73_ zh=R9Jgbw;(@B-wHRpbawckn>&p$8|inWeeo!XF{;#|^;~=3|}1rWDlaXyLbEwo!JV zK`QFYCWRw@&+$33cTS0ilItkl=*FZ|63cfa_*h&DyLpX4?i!^j7Z$5Yb=?n`M=2r{ z?uNE*xne(o-mlELo4JgTPYe8N9c=o@e{e+MO4+xBASY&C{)n5rsuvwm+1k?o#n+G5 zDUQYGrt_DRJZ3)toV1T}e!eytbP1|cTI9Q~xnO8EQ1W#lJi*U;nIB^GIFC^SS_1$$ zvo_hPUk0tDAq|$B1O12BENS-Vo2O@s# zLaKV9U+itFlYvrqNaxacVXw=+)$0L zNkIQry<448yI))78->x-(6ePTcy2<(f;C;i1>>Ta(4IaZTduubuyhsJNWSZpx>Wm9 zxnR5U-o`qEJU`XTx=n@d1isj%%9-Ym%U{nM6SQwOK+^g90|aVi>|=o27q0Pj;-PQr5HP*>GkoS{-*Dhj>{ofZH2K8Med3}2^srCvx(K5ev^ z@k*#>%i9({o6|QWVin?4nO1nQAcO;8fHc`syL;eaS~+EP%&?;IzLAfLM(f3usJ9Zs zGpn@TL268nIi@5KjFF^ZOkQmN$RXidSRfunHz&TBTt-wl6WS!ydbaoTBN043G0O83A;H@! z$KRvI%s?BThn-_wX%Q4Xs|8*aW_8I~o3Y+)LS&M;ZT#t)0v0gr!y-J+3J#w5rlDHALax7S;*FyIxGVo# zsw=eIt)&i(qCcA?^lGCU0&<#Q)sz5Yqf~VXwOu0oFrNy06x}tm@QrJ>ImJut5SE>< zI6A$Jomkd3kOwF6z>QuHOyYW+7FrXhh1>>Z`KlscaZj(^?gJE!qDRi^;@)o zz|4^|1+6u+V}+ILbiZE*7%m66YR`I2N78E<-zV3g2691kl786Y&}$;-PK0a?NZQCV zf3yGxE$aL-l=QF(LHs)q=0Co9ONGQEp0?tK@lRUs)+&TzQ%Q_tz?1!S4q?|%l#Yqn zXpar;WqPscviaBxqj=ElszHi4$vGDs@=k9yRR?%zsaIrO?4k0$JSE>IW2-(JEfyL! zVv#)OHmMd$Gk^GQe6*wocWpDyBbH1-rAHX$a`ZXeEonoml0Jp4C|1sT-YB~I`YA?_ zWx6qagC%d{0w(mKhc{4Mb;#p!mzl^>C+DGa4y5Vcj0B8D4jBeScA)#@>-Zb%2UcS)PFlrWb2YThu zMfXlTPU%!P<=gUJ6g?l-r38@?w)fPTUxQD|IutI()28hY+E^M4b29m6BcXvk-F_+~39Eo-e}C$cVg`f}-6<(7S) zgT}HhGw|+Wc{{uZ*`=33)_esK+DMq3{c+cwbK}gz*tc8Z#1FVu?zb=}oJgy3j<>dv z(`tzAFYT!2=bU=S&*x9ViT9uXm^7rw_o<^oi{J{z!&_61g9rcv-&idNS=@8W93zM5 z0wmdRRFSDO@IMFoq#rgMm_A5HCO?Xy1U!jYe^3`LUqRjtR4d5JCNRYA9|Q-E@c;nB zh96c;<=W^Nc!DIu{CgQsYS7979vmo6EkO`Z}1jwu)a zl?Ic%BHJnJ@GpPmOc`z?vPPb^X1-!R@rAf68ET M+uW$q(Dm8>0JzCm`~Uy| literal 0 HcmV?d00001 diff --git a/docs/backend/images/auth-observability-workflow.svg b/docs/backend/images/auth-observability-workflow.svg new file mode 100644 index 0000000..314a594 --- /dev/null +++ b/docs/backend/images/auth-observability-workflow.svg @@ -0,0 +1,110 @@ + + Auth, security, and observability workflow + Cross-cutting workflow for Better Auth session validation, auth proxying, service-level authorization, structured logging, request IDs, metrics, tracing, and cost tracking. + + + + + + + + + + + + Auth, Security, and Observability + Authentication is DB-backed Better Auth session lookup; observability is attached at request middleware and provider-call boundaries. + + + AUTHENTICATED API REQUEST + + Frontend request + Bearer token preferred + + get_current_user() + session token dependency + + PostgreSQL lookup + AuthSession joined to User + + Access checks + expiry and ALLOWED_EMAILS + + + + + + AUTH PROXY AND SERVICE AUTHORIZATION + + /api/auth/{path} + registered before generic auth routes + + Better Auth sidecar + Set-Cookie, redirects, JSON + + Service-level authorization + sessions, groups, templates + + Path safety + hash paths and filename validation + + + + + + REQUEST OBSERVABILITY + + HTTP middleware + bind 12-char request id + + Structured logs + status, method, path, duration + + Prometheus + /metrics and provider metrics + + OpenTelemetry + optional OTLP traces + + + + + + + COST AND SESSION TELEMETRY + + Provider call succeeds + LLM or parser metadata + + CostTracker + model pricing and duration + + Session totals + total_cost, latency, calls + + Server endpoints + metrics, logs, model catalog + + + + + Non-fatal integrations: Prometheus instrumentation, Loki handler, and OTLP tracing log warnings if unavailable so the API can still serve traffic. + diff --git a/docs/backend/images/backend-runtime-architecture.png b/docs/backend/images/backend-runtime-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..4220962aa4560262ed8ea34c8cc33753c4fc39da GIT binary patch literal 78188 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 aUB+`ICaStZ?#J;fT~$S`N9FP_U;hVQs@})| literal 0 HcmV?d00001 diff --git a/docs/backend/images/backend-runtime-architecture.svg b/docs/backend/images/backend-runtime-architecture.svg new file mode 100644 index 0000000..f40ecfe --- /dev/null +++ b/docs/backend/images/backend-runtime-architecture.svg @@ -0,0 +1,145 @@ + + Backend runtime architecture + Layered architecture of the Summarization Tool backend from browser requests through FastAPI routers, services, persistence, external providers, and observability. + + + + + + + + + + + + Backend Runtime Architecture + FastAPI coordinates authenticated workflows, hash-addressed document artifacts, provider calls, persistence, and telemetry. + + + ENTRY AND AUTH EDGE + + Frontend browser + + FastAPI app + backend/main.py create_app() + + Core boundary + auth, CORS, config, logging + + Better Auth sidecar + proxied /api/auth/* traffic + + + + + + API ROUTERS + + files + + documents + + extractions + + evaluations + + sessions + + groups + + templates + + chat + + server metrics + + paragraph APIs + + + + SERVICE LAYER + + Document services + organized files, processors + bbox normalization + + LLMService + provider dispatch + timeouts, metrics + + Evaluation service + DeepEval adapters + job queue and scoring + + Workflow services + sessions, groups + templates and folders + + Telemetry + CostTracker + session metrics + + + + + + + + PERSISTENCE, PROVIDERS, AND OPERATIONS + + PostgreSQL + ORM models and Alembic + + Azure Blob Storage + global/{hash}/ artifacts + + Document parsers + Azure DI and Docling + + Model providers + Azure, Vertex, vLLM, local + + Observability + Prometheus, OTLP, Loki + + + + + + + + + + + + Runtime app + + Routers + + Services + + Persistence + + External providers + diff --git a/docs/backend/images/data-model-relationships.png b/docs/backend/images/data-model-relationships.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6b17519d18b4ad25d6bcaf9a3bf8a111131e23 GIT binary patch literal 99915 zcmdqIRa6{J+b)c|OK{hq!DVoFx8UyXJ`hN7f;$ZE?k>S00fIAFaCi4jp6C7E_5W+1 z?tQZRWcpsKr>nZI>-MS$6(wm@Btj$z2nbYJ83{EA2-x>qXnh2z_n+^C$sZsfFd$?l zL^Zs#j`x5M=2^+q)4ZGbAK8=Cqj*whuV5lZ&7a`?+g@8POkr5j+kU86tJ3STd@Uy7dovsleQ?B>$^z^dy?7kA<=*I~M zW4@32pG$|o8R38KAs{LtFna$h1!+P+;r~~vMG}F4{;#A3iD3L+p_c&y66L>=4lH>P zVIIF zLj1eZOa!7s;PkVcQpyjKn$q(V`d+A?NgqJJ^qbF$<8*M=z+6_JrD*=L>HTu=m7s;@ zMvC#hi_OWl$8&l!F{5MF$>FyvO(lWyCA9aBR-RmFSr}Z;1XxcGgu-5_VNvpqpzytJ zhD)Yr5S0L3E}n1#TBx3xJ!X59Y#p<2wwh|_D5D0A+5dPyNu#8{UPoPhh(`TXAfWSE zQ_9GpGfO){>YjZ3i+1tuUcB20sa##7AIv{<*+=|22*qSu$uKtY>^XQX+lSrPS2JnD zRMGBXdNzNzM;&uX(zcm79#gu9!MP&*v<0;M9?*Pd(1dQ0gS)@1?Xw@2h>%NF$-U2c)TpGGE~;xsJC)+mP>z* zjYtKNdZ)`L%oCplBJC4gc7sxhad>U-ueBE}4V`doPslnpTf7oFBWRlMW^X#y!^Kj< zCZEq*ei6wb1x&;Z_Bdw2II{mc1AaCNJZq0I)|~sNiHn~r4386fMh_jjuR zBMgXHskd$H1orj<_nXl)=?vm8JN;C8>s|rolkI;MTNyEs3_#6f3n|fsIdpAo932K z3LWiDeo)FJEz9su=x{idy%z-1w*emAl zuF&m!s&$mYx7VaersG=n3L2NNlUbok!VVCx=KT`~d6FLGzW01TV-&l{EiMXhTG=;6 z<>9cV!&=C=-kjJf5}RFa^=_Ni=Ig@c5w%$Bb9n0`P{&o0NQm7_PnJ402n_0Gb8FkZ z{vq{qmziGh>Q_5%qK1vV#MH<^qMGosoB9ILZ(QU49S}%+cOaZPfG^%#D1!jTXkXaR zkTmj$di4N>$~85{*bCPH7^KyWD7`8Bi^=RB2Jh2 zre9Dx7fD!AP1YM9q5s@18c$UzaPR&znr7MUYA4%FGKN=Z-16s2F-<>Vbr&e>lP$pu zgCY;dI*0(|86M^vkp}F^kTAkC>?kB|Z@b*=B4;4%6OpKzVW2~~fBHt6Vi z@wb6*14+NEH^bcLwv;aA7mw7O`)&~zn2y)_JT2{!59%FVFE$8P7z4L(4NldOa{-l(I%V(4BzVX%d#0*a7M_q8lK$D|oF>9o9+QsiC zLlQCr=LgCcA2+F31TjIfmIfzknBkvBm!~XQY?>PgcBOlK{)R(>vjqtEekruD!>mRf zF9V92go)@rU8cUK+nw1YTnrm6d``w=Pw+{6sygC35;sz8gIB+QlHZGUG-p-JW4G?F zH4olbc4Y+j)Rx#46!T>HSZo*aosp_}_TX*iAN-mPrdlW+{=k*>q%5`_E%}bGg@Fm` zL;!rV<(kO9+GiOtie#KD`03r4CIm>%YE8uX0P4{Lgj82?wHM7te5buV2NOXy8Vjcm znLN)$;+my)oTUC&0qxHuj}tBBm~EF{N^-h>Vop;FErm=kQZ@D+x1-N*UNWgloe!tT zXBF8VZs|LhH}!aEfjnM+5yAr~ zeA?v}cg=}QY-SVs2_orEI>~*KL|>M@p9hoD^=gRtLsQ$RE$fDedRiRHavE&WIy>Zh zN_*aZH74O63f9?#vp6Yccl|=}^H?YqxPQ*P))2P-b%|Muw@GWy$w}&MHUX9-+sPx{ zdgb}Zp+#KA<~?|wE}O9#r_|qlXXP}PyhVu4tXo9L@H9;{c?cAOgHq0XsID&jg8kOc z65Sv0_VO@(cgU1o&W^`CuCfy{7EN>I$;CBU?sgpFYAMP1yIy|r8AYZ=@e(DOio1DbY|IPd z9RTGSIr&tYjt1cM%Jk0V z9`_qamWAY%i-+fd^I-#0rcV+#bzfrw8?ue<$DlO%$6j7*r(aX&zcm}sEkZ8f1|E^|)g8NV92;1GVZdg)vg5e(K|ZJ!BW#5Au=__1^t zd?qkuBfSmaiFmpxiR>t(n6L%7P4i?WvaT(@;yMt1+Cdf!6nxrSB@@cTIgS2TbADnc zlECWXb*{X1M*(wYdKT{jkPN6$b!#0Y9~&Vu^coimd4%M9_zsPbh7ZK)So4!4T| zk|0@-Rb?CARky!&QQ{Mt*2ENFV7>h1{0b{}8N>pa5>fYYoZ6b^YA36R>b9Wa@+F&r zD5~$#z=SsCc7mPT>utJhrc-|RFyBFTHjsr#^o;h9dbAGjk2CDHTs>)ywY zWGmAiTzwa)k;CEPCha{hCz9n$gLXqhPBT|@MWbO=LKpoThf7y}mY$RwRoH$W50AB2KD4YWv! z(>|GHxE$oMZw0i|5Bl%{0=}_71bRCt${hl6(a>U^y1`Fp10^0UuPC}e({(ouC23ZF zA+{apWW9mxOjzq%l`KEIeYEkl(K6()v9OD)i*!sr&mlJ%vYdv%pZB__R|&UzwwpZ~ z*`lruPI*e`{S(=DM|n+%k;|Pe4u<*Ve8mT00ao16G&zPeHPN0E;U!wMQe!iv3x#wA zw1N@;;So+^69fP~TQ0*ieEr(nsr{f}cS`;$XENVrF+yIrgfk(xu08=sb#&iHChTW1 z_t*v7n8=X14VN`7zW!VV{wb@Zt=@y*5%iUH0%4kjapQt#;Z)PWXX+6}cfm-XO<3#- zGe+J3UlcHBCGlm9Kgmz5WJFZ<07T2s@0Oj?9^@#af!3Z0WXrU?xi)OqD(Y~)rb?++ zK0$v{cy~UoH;neBmEB&a_kGdXc3M?SuCX5$$`2=QpigHrPpH|-8rfprCUSib*Ot9x zC^WM0eiE7NKW7f3PoFY5KlncC_41o+NV86@0cZM{#vTLSj`=d8YVI~sJEOq3E6Wun)Q2l z+|zL4ci*_w0Z-Jcjjb;(C$2TCH*s;9%uQNd#k58t+GJ`93h7%4%YMf?d{%c3v@MW( zF{Mnc0;W4Qb?Zws-QPPOtP#+?;=cWO3cF(H;6En*2f&~M|wy5WN-++6>-r)1-hn{d4AJ+uCu+Mey`HHrUvVbq^ z^;sx+)OsHP5|mDD4L6eG>D;X(A#K$%lDWHlu+E=XP}-pSR)&Z?DZ>(5J3rb9xYkSK zR8=p5{kAn`$Layy4HW;?O3bdY16D){&Ac7jg>Jd-`C4AD^l0z7`^z&WZwAk`M^opx z2ADVs^!+@u7J$@`XZ83a9!_~L`FFb-KOJVVj0cqTfLol}mc|7=tKF;OBXV{cMuE!* z9-I>2lkQZSqHDCqHbzsaN)g|YVM5wEM)Uy$xx1n+cJp zg~UMjE19JiofzZJ?*bBlS*6x^e_W`yJAFq%6m7JI-;Iz^yTXcR;s=F6RP!+p)z|L3 zy<*dfhL!9=TQEuU9^#H*O0ps-KCCYf3gKBYO=XVfi91w79A)y z)Pyt-*Scr9CL1H(u~wud1fK+{s5nuo?pSfjseY@s?BT2ToJ=sgr#?rM&7QK^rsX64 zS>v`P*DqN6;^Kb#%QL4Gwoz&1A#4?3Xq8ME>M8 zhWx67!+hxIWutZ0TA~zU0ABmKWBKv(v6w;8 z#h>B;Telj8Y*J_o7>`rW|sB*>HrK7kV~iXSDSWPx%atJBQa~xJkjgk-n0ETxid&5{{!bQo`-7?k`5CvT9jzwY^ybuAm`6PdLg$69 zC{@^R89i=`8ya8fnzdo73mCtu+#>@mi*CQsmA8L?XXN;tHGP1*nWvW1y)g1wK%2WP ztwF7i&`rL-&@NGVbprwo(DPP)j$Y#M_E1T``hC$DVU&QU8}5S`idO4yIxb!-2gu(% z-B}ja^=_Iom53+A536hGg;UoCIX6wzK2;FmR!kUFNT^;jqwY-{Ho`t)IG&8TM2Kly z9s4P*_x5!KA4S*n@N%kf-Q8q2v7TlH>g06#F*$zO^3zoM5i)P(FOMv(Z6|hzu)WEn zR;V*B=&jWHNwUg7?mc^1T?gt&cx6|+AYyT{-Pk>2Ypjo~bU$txq$UqaZsiH4E(}FW zbp32P_UDXPb19&>;|&n_lJ{yWnPNBl!(*LApU!@yr`1dS5&V;Kht%};_~BzVNigz1 zikZ94hKKw~%gC-}D#ktwlTBi$wOx+ukli)U4}*vJ*%DM8C#6*Korz{8{q}m&zp2gu z#%H|-Q0jDS)DdB0L|0*HF4{5;(*EUmh|7^+iNv`0DynscZxvA;!SG&%Q37d}v74QM z6HBErwbp)mTbPRFgQsRjXnbA>B?GdBESZMCR#RFxAlc+Oj&-{|6Ti5;XCI!hH3V1R z7*jGsBR%7Lw-4e;a`-t-;F#5l^cDgO*i^5g8d;J?fZsaDy)>`_D0g~zY6KNJj>bZ2 zI{iKz{IJ?a$SCb>*x2g674=Y*)e{bF4rC07_<`9zyjjex!LE0bZFRg{amR_*gyVHu z8DFf?QHSW><+J~*$u{wYJL_{E_nFJ|HSes49LKzUM={dcgx%tME_J?#du=jJ`1B*W zzH4ql_kkUt-G%0-G-Xn#=Rk5&7ASYb2bM{94@J@=QB~^ z(q|7lv@UL{`_&EE8L@!yC<8#6IyH)NNBH#y`$2zG16q%-h$`PdN#1wC#&4YXQY--! zfk(PuS8H~A6B8X3WCBh{F)=r&puYO>_~eg`hoPBkP2N8&JEoucV|^INJ0>11Kc7l4 zu?ka?A>0jNaLVERjosyZC9Fi!oa|e#sL8+T9#;7BP-NR{Tew#IX_@Ba#z%F3cgaae zNqAt!50{Lr>2s8FXA1k4xfnHrUAW%cOQL~ygF&B_?$`)VO$GeEu$^HNW{E9zA;2!t zRQP))=f}91P>ZFoIlnq3or`+?BX7kFyeQYCD>^O`ofL0pnd!>BiPFo7;6$%$&wv=2 z0KwB`+{o0mv^M`gzY0@Riw7h^hn&CiI3^=9){u+{7_y%*wwHBEBYHb-HLNRWmpx{l zRj(#KGqa&8PY%Z1PSlhXRf>`o4f$zXojo1CoGjdcwh0JgVcSfrY!`k;SRhOBYp-n! zczK^tzm!mblIE_ubqk5-t1qS~C56P2@6dBPmGBCK$Boh)!rDb=BD~b!I=F5@j7HwR zy7cibglA+c8KQO^1UNH1*$+-dbW-NjH0YP2iahMfD}{8t6W6JUxZTt(?5Y0#^pp>C zT;qwR;bjY7lYwVXv{9btQ*nlb)~Np~q&C}UNPW*gzqHZ!m-(n2?ue2k5M?s;-2ZN8ImLVIA>d?hOiNK8_?nzV2CVOMWE+}vDQTofc=sw`ul z0z7V5y*;xfJ5t4f;zBh@$GMjD-qy3Kq$Fq^r`?leGet{wT`hMle(2$QoV;r*NLG2c z3&MkH`;7$jV7K6;{8|1Apqc5oFqk-_Qy>p# z*LD2`TW?k&2?ldUg_Zq!u11~1vV3>qmqFd(pXluSd0%?R;JU=t{o?w;(A4zXLQ`*3 zJmu6OLcueAsxl01I=ZopYM`qD(nO)xJnM*Vw|4UE++{qHX7G$)x$E20?(|!CncYID z72b8aa>w~ZL;yIDAg7_ZD!q{QQt~1FK%RZZ19s!>@{&cN``_iCdi(2vTs!IB3EvmC>M=@K27NS+ztSBz_}R$x7Sg*Ph7a1|u&<-vu9s`*2>IGnN7RL1JB$03F8>q0 zMJyp0$9l;;cQ%rQO{^@Y6Bba=v7?>p5E5;Op?(_oBw!a5-1>Q8kp~B_dz(?m*1+p` z^ypKExi*$lYL9ox&;iU^i1z}GAIBUWkhlWEHy zzr3tN;gNl761@anQ$PVNHT>rdDml=-6|85(0cd&|IRmZpzPl6I_xk`d<;~>r6EGy1NJUuLAt@xWWICrlUg0e_AH*@6>@chWx+Ux$LEeah^y?RsUf3nqs9z?rzh+ znaZ=BpF`ngH+DoIFzevV8`ZK)-mBxCe&)V6ie17QXa!7<57RydcF|2MD0f>>%9;Dl z7h9>d3c%mUT%J;C@F+=jcYJS8j3}&`0!5A>B=R^G?H~%OyQd+eZ0C{=Nnj_$_qLQl zUk8x*789TDrdE4>9M`F4bihyjAa4j`~ub&=bDJl2#089b8Kli=^iO5(kD^}HB3!oKK{Zv zxJj64#Y5F(g@Aa(cMgVQip+9rfgT!8FF-r~(5GL~Vc}*a0@3+zr!A;EEaE4`irCvL zI(m4~K(d||V~k&`h0BbCt*QfnWR5LlU8fA7V2WoFrs}v9pU?nJl#htbd4hGxEH}oU zQX@1(mm-h@&b1erL(>9k0Ahu?!N;9S&prr0^JEjM3#t z8Hum}Il5H2G48_jjmUl;YT9$rkL~bGC!-a;C{EU@%q(VBVBz_IT8GOcl>y&njpB1j z>7trfiMRCo|F1MIo-`0LSYZwLv$rlr^o^>JKblDjs1>%;V;XP#V2T4;{!cA{x#9&k zb?y3+;L!LN{qMD6tU)4yhQ}Q;PWAX$b&U(vZbd=Ja$y34Q4tP>2Y@fOL%_xC2+xqw ztF=?r=Ejs#iDZPgMI;rjazY8O+L`?9jSQC&5_7`rj3IWj^s7SSS}Fr=`*gx4g|$^b zd)1w0u>v)9qrI4XDM>Nep@ig*g3}fqmv(S2IwIYv?! zb1IrIb_l&MvNQoclp!Gu%yMH_mP0YERLXByWEw?jx-DNwQp|`<{t~~SY5p4-RnVOV z{(wcT)YXjU5n&zR;lGECCCD|c7KMo@J51uC6Q}lIfe4nMVcxuL8$+OXT2ir5E~wO# zY$t5*3E>~3$tHt3p)RBQ2Y?42b(%`|bH6EoJfw-p9WYy5llg{U8 zV$3D|`fP{-1n3q|xQw zE<95=Qo8-VVZ#{4l00bA9!%J5BL7YJbF(9uUr7X6Les z%0PHk?FC#WmuhxG3j$Zxf!M9@$d4p-C6*5bj)Xxqlz1m?R8xXYgWe%H4nUL(3Qbum-(?Y9C?pnpFF$gN4p_b zr(8`;=cd4lO{!t(5D&P%^{3Z^WIcAn;v9;XqCXuY0@$gYy4}79oC!wZGJHfFL-_YP zk0ti2xYAE!oA*;>A2G=u3lgzA1kCUP_0vtpysY+Z81ogsrPRaPd@BaX?#sb(ZW^v# zTGSqrT(D#MIv>L~(we1a=!~h%=YMe0j6m9nA1GQ^UW%$jd)S4aOhD8AXGz z$b{Y1Ag&&gmgYxFNf1PX38(pyN0iu0Y7W>FfMmK0mMhZlq^@8JA zar>w0bO{X$dVedGwj0uHLCfPq zKRccUGTPD$ZZn!J2ag#03St=_75C`W(0zr+&||ykfeQmKfoTLK33t3c{`uj(&`G6q z=7KcIIbzCIw%TF>NQIqLAs}vFEUv7yn|#1W!WUUhLA?;f?yUrUMo8;-erk4kKbn>L zX7Z&Iyl)c8tw;+-cJKYNGm96SnUxjp2%UC8;x5j7~b`O|bwhXllp|qpykTuCHzn5rzM7myVQ$|(MH*l)$hJ3v| znuy_Ie}&0=)v)e%OB}@lzOhMk6i6ZqOk{P;&e3n&m|J8=WL6@1>Jzsc729Nf9|_f_EC31zMsE$k;gIeU2m(Ph2KY(@WJ=V zY)n;idDU>()Uh~WwrtuITUm^UEQ~&&9+N%X>iSa7kGv2lgX(XbA1&H!NyfqI51f6Q!f$(?gTjK#JHVi z#4^-Bnw^qiQzCk#nF7M24?nB!D6lC9hM@0b_I}oM8u685Sml${ zw$&zgI#I*#1>86OPOyfLSn3u39ghRJkkyn}cK*UsPJLEOYpg#v4guMB$v8k0@_+{s zRBZDgSkf+2BHkE*dvJla2Zh zhX>9Y3bV~KfMw?}`aOOIG+p!Uxm#^ftmiDm^Go=KC$}8+7&;>C>XqDyyT!9J?jhvC zc;Sx_<;Bz?YDaKz3!UezmSX59W9pZ3{pT-A8+v~>^^ozMX7?1Rz;`>vpW5)fBS|6K zqjaTX*4uN1)e6{lZOjBi?hi{2tlx>HT1m zSAev#W`mp4AbQQ2$TxlSm~bB^7V+5l7Uu)^i9aqKL%D-{h#j2GaKkab-R0+1|Lo`Y zuZX5)IAKf4G{syFm%_%Tr7*V_c;#hIY?Hr8MtYWo|AVi#aJKa~l~!o{__*u57V^13 zT3NEuQV0Z-O7$Xihv=uI399S?o z;h%nb9XO93Ez|>VnwkF!TC~>3&FiPP5xL!sO!PCv>_WPMEz$cnShEcoOT;pYQY#Fa4 zKKYN;ea65~4CPm<`$lCJeu|1wDqONJyQq_nPOdMbHC(P?M;94IN-|M9^A+fY?LW*F z>=;Nnx<;%~lbCvgR`hozVJ!KPIJv3zFAmfIQoq=uOZao}8Dw{C%HWww-wju^O&o3e z#>0~*4$~{Q{evsyqsbf1)+TG%zGQ<5JGetQ%>E$N&+|qh3CDPxs2a}Wnwf$y0F@UD zyX&C26VvMn{$<7ki=wn+5tH@n{r1eW(#O)+*zBP5oB(RAhk6;W{bVri$Ia218l!uN z8{#2B2ZfABQZ(X)*{v{^QvR zCP~Q&+Tl{M7j!qDlx`XH?5sfLXX>CzA0!gLx-D5Dl)%5CHumHpBDYtC zYl5tyOV#g-o<`BnW?KI3BSN|W4Y#>zu~Ypd&RpPwYfE5ZUR_d`d-$tlc?G?hOq2kh zbsQw87`Y)*zOM*oU`hE|`fKT7Na=aJt0daXl~EnAU^+NvrCVBv*<>Px;r;gDj@7tWDQNmRGJ4#f2Q~#obh_bSpuBd0@gYo;x+*a(?|J9_xbb6#d z`lan#L*B$)(F1ivi^9>VqGBv#v8ooOyEEy6!|iuMiH9!ern^(C@f3nExh zhq9(NhK0aU+rGS>)ZmUScnRezBzGKMoUu^bea&*5_@Sd-X~(=252cTlm=BRJ>8bp0 zEc~F5YrKycqF&q-Hf!Az_7xF%2MPcN89>OybPnl*BkqCwwEz#vPC_ht@EWy5zJg7= z8WT4z%YvH9H=}$s=W0;~nqq@LGru*}4qjsSGwXkEL&eSj7+urYp(i-m9t+tJw zG?h~EzsR1@_j&0g991Ef5`O+`qTUo{rj-fKx+&SmW70R?`$-}s4IaU<2Cq9`ybSfL z&W|JY65=a;5FBZrvEr-Q!`-iS`R*l)lrPIifmOx)GiZTkJAXL*l1ixQPFM5J4|HFp zR>n2nih^L}a|mlgb{)9xW9s(QKb*y}o~fZE7jo#l1wDmI$5G~JRQL~H?0v)SQSN|Q zvW{HV(!Nwr5|l(dYhsa{e|WeRy4MNN^-Rrkzm6YxsGGKu_-F{`=<@LyV}rW0 z4{uXi6Ve3pRR~(Xh5#1}&-*woe+)R2r?X-?g?dZF<#_gfF>k!s!Pz0k0{hB(B}^rw zM8ngip<1`GKX^=DsI5~;!kw|W?4->^8NJGMcMjHK48j%h$dt&rqV|O2Y}c?79RiJP zZ4f>B)MN2aEYIpxzHaWo`Q;KD&FQEc_>{LQ{p{%Rm6l_hoa*~#8tZW%%Q1*Fcr=MR z_;#3escRKnkfU?E-a5J)H{t3_H*5KY# z2>yUotucRLDYP($Fy_Ere4i%(p<9MJbfIz_l$mPJW&2Qi(Dl1Vw+6x z@@^4qdH>-~HDc*MVXps;e{lfx6dQOUnDChJ-7vdD5(Sr%K<>sqY6PZ;R6njDep3Z? z;O^^fbCm7d-cO}@5Y!?#uL7G}ENw&J&?xTy>Z41|bLP{Xn<%s#c{4c!#y6!v|I zeE`8{gw^}T8`^E&jqpRe88eQ*TmRFL|NMUnRywf%f9ZeUK;NkW74<*0fPYF{?@sFf z>CN>2r;BY0|J3q=xPG(5hYuupx^$FwWt-R?ogNmh(BMSa9c3n0*mKX!{QBGmUSVkU z>rrSsw4Nlu`H-vcfF16Lcb-(?Njx7HF0xZDLf@TiRPI$HuAO83taDv&L^W5*hFL#q!{4_GXW$g*4<6oq$M(HzMhZ=J!eP*^Ln=Zx z4pxy^KRVqkWC=n&#VtL#an@X3UR;s-p4<{GxS3B_Ly zB`hmy)%^U5$B*g5WNtSWh^yAs6miJfi#qukCGX<#on*xw5Yx znEE75pGyz?zKXe=-ayT@p?M;|#h2<2Y8_?6E14#;b2FJiHzX#mHoBmHyff{%jseh+ z=e$q)C-Ofw;jdOq(d?fNG;?#4a)y~OsVisq(AcJ)-MG5>Bw40cmX^J(vn8)iFU*{f zC7LBypvEv;xnAKb=%Gb@)ue9@M~nT#aA%4e22EFp2b~qzA>Sdv@S$Z(3;H>>P%XVB zEW_<4Sx<66sZN60#tO`yFjQVStjPSOs|Tc7ZMxBQUI(*dD{gkw9kS$kPlNf!=3Kia zjh+Y6`%&Xw+u~Mh1z2>+%0B*k^k$0gKgVFY!Uq#B%lXHYqEhQVkl-KItmtVjr@35z zam9_wO8QukT*0*%>GzxvA{d6n=Y2L6EP5ShN0cctkinJYO&f?6p+H-3r{L&-z1FHH z#u?j){fmr63C&&FJ<#W*FEKT_LiE*8-KKfxaVRBf;mRe$rPgfgdTa9-qS@k|mN`)W z9L+JF3ty(`gy(XAGN}-mon|oqWRGoDkmPqnm~lrordr+J#w#RbR_UrqjF zT^RyRMC6JI;SQt3m&}E_*c`nqdfqb;i$<+z$Odo6j+s=X^}GnZJx1e%cuGjkp922F?AL)4S7y6@zTvM3Ugeoy=gDYSX62I(y>C z)Ez4C9UKjmGv$1_epfEJ70O=gTog`4Fw74kFL}LX38*{_)-CSrgDuU=*6y;M^Rk{3 zK4QNgZ5byAr&y9i!$kY6Klyn?4WOoauV)&{BqZr=ooui-q@V!YGryM5zzdL9P%XaA zh=Wijer^>bzk`3VJ$(PL&D%@dW~93`O>1w1ZA^q$ZLdgHbI1`P-01Vw-9n`s+xG?s>n#cQM3L*nR5+%f*RYo4oRCG`32p z4iX}bgtD%%`!jjsn10r=bGcM8{QCrxFcAiCVwgQ*m~Xb7hC;&*{NBz=3TBhIZ;839 zb6LGPbGlo~n}7GkhO|)Sz6G&QRhCuTwY#*y@O)}Q3jI~}V+5mT0ACY$FVBNqv&KN% z#5Q5=2`vr}bMH%@yYOjeMPj#ro1JV)TnH*USYguPp6JvT>Yxn-`^oXg@Hz!6k2HY$ zrwob?8n2$_LQ8ALnl_{>u`wlQ69Rn%z&}p+Y{2Q37`sJ2sX~5N$-QvN-sNN^w%^qi z!mg?e+9Fp{ZJ=U|ahr#;X)`Nan=Y1sos(Uq?amXDj41Bo4BJF=;F=@LrxtZ(<1cLy zBhrdctX?D1A|-6`*DD|>9eUg0m=_^ZC4~3xRwUTc4=u`!9Rxq(BC@W&F!hD84<*A$ zf7ht32;DZRB1(OzjSoPt?Xfwn)VU(2!0y#Qm+ zs54u4E^e52frq<=Fa7~6Vg(~YR-BmWEM$uv89+e>xthB^`V%IaW(EQUn}RY&qo7R2d^I4er=nSp*fOI2^t9yJtMV*yZ}7yB^|( z9Vqcq5gg%9Z9Cj-1Ou^J&|VEoxBL3YfBhX|2={^;vhfF$7Np~jYpvN`P02e$a;7Jlx$piMc3=S4upld)g2eXmnwX zpJ08X0~ID;$b*X1K0Aop1${?`f;bA&{{81*chN}jm0}ojwXm)YHIw9;z^!&nk&uHk z^e6^G1pP%HZft~oifBw^rs!H!QPZKo6{53kW^+Yfqsr2Xbv2$My(0154N|@G0d$*| zWq>=hrScG6qQIiPK#X3G7zf7*&nYL;T2f~gJ8P`gwrgcS?)OheHV*K+3;Y~l#>fr+ zGly6ZhC)<&I5EsvV}l*;tT?+mhq&7X!4ESgk_)g#A+Fu8gF)v>#;kAdUK(7=8LdqT z4u4|g5fy^+4SAKD49JfCn(tY8N%01u1*>!(!_B=-d=a+=F_T}NSM=`Imks^9R`;<3 z=V!TaQ?&R2;4t`yNAGsmmswiC#-k4<#HV+=5PTZ*?mT}G~>A778A7C!s|f74RdhEHQdspXb1l~ok42(vS~ z3k@ZP-`_I)9kR9O*et*8hK3rmJ9D}6aMDm;gXi{NvsV|&#V87KLU>>p0u_V{3E?q`0-K{MDAV? zJH|c@rFAtUA!iHHc*S9qx8`iQVA#uXC$;(OwTt5yf0%2AmrIX*bx0;0`D0#VWr#`~ zw9JSG5zr&k`%vtJ8M+2GTg+Eub{ubGMJX?xtDMhp7ayL*A*QrN2oSE>7SB7}*9ud# zOf(J5Bt1eTD2vVpVRu=>TvrUT>7QynjnLJLS>3R^F~U#}@%AO1p^mm{#MgC$22UI( zKJ8E$7cIJ_wQ>>iU1yu1x@HYheULw0VXdG&@|^Qf%sHJvCX3NNy}5Ny=k!eb9)=vy zjtQ~Pnu`MS2rCUATM*(U6SOOH!yft^AW&{gM5ZxMVY-xk#G~7WM@Pz(c5<>fLVYm) z7sj+lgnh|lzRKPClud-|r&JYgM;Hh##Hi8Uo?Fk4iA#8G5UyF0Y72{-r}-J< z8kEU7ozBRtynKr;F9HDqb1P{liHX|bq z|LUUYAdXoxHZ=5&B}i{Mls9-IMS2!^t@sb9r~wZiA^{9RMeN_0ImsclP$!@QZYhF^ zt%^C{O)1z7zj60AY60mT-nuaoV$7hoIT?}nw2=d9Or%%8_jV&*T)fO8Smr$hZAo=p z>q*g$#!d^%Osr_=pOSH0S4LalX+ylkoZ`9OS%AE0g0Ng9hp078L&+4XdKQi4!1ms0 zdXLAFn@|_Q@Ah;uhaJ4WV9<6)mT0;!t_KQRb6^V(Z#Dw_Ft1ZX;# z(JzE`x0Fd5I}Mw%`+EH;?H*N(`ySMt$J&Ed$_o3bn@%tD)G80zN$GJL!6D#Jy`r$7 zsHZC<-}yiS=(POyz6N};giQk)?9Q&jm3iJ9-U4c)hnXJw2?NtgeGpx#7@iL1wjC^F zsGi9SGdV7g%JWMi1-c(AWLm&VC=MLZ%m){LX83nucV^r?x+!`nPvQ^1H%wR)(VN6wO4-uP+>RM{|oD%pN$T+0t zyXQL;qG>VqLMlFYuAG0@b+o~)pDjTc;|DerRuU401qkT-3m8y|Js_+G=oG9F)OkJFu-cM`A3qD;Q z1wHBY_tpXwch=^2Z%P0K!9ioZ$+>yt+!CZ* zg@>y$+E!W``mCoG8g(2zBNY$A(BG^8QY-mmJzYa~9lQh-jV>1$=#2E^Cmtf&f5d9v$D=7`m0!RFJpz;O>zmx ztnN4?L8`p`&YZLLWI$MNHEC9SwKcjdR$3gNkCnxQ;~+rD%r9EAOA5q2s81On z8;)s(%TROYVH#ad+^*u12;euA=6d;Y#!CmkiEe(2xe3iT9$x;uj}8b&hq#7*6{Tq9 zm3pvfb-ydY;uc*()%QV409uNDq2D@(tM5|(?Jc_#KMgW`v%#rC)MDOCcvN?Y-aNi> z1(dXFsw*?r5W4PE{shd@#CE+44OA8bb8y%NQI$+9r#f8b9r*6&rk%t;-c3oGjf1LP>@oMXn{1% zdg?}8xe+z_)9gp}#Le$nBSY(F=-ARk{5j=3A@E4X2G&Vp1!VF+l2_~3C(l9TZuE1! zyj-$5itamQjv}~JvKQ(RQ}qoCu|pPM?De;2m}wg_^&0wAV(;um#gCOHNe*(-vaVjs z-#}P>nFUq|mv?Hh<(kV=$5LeNMwBlf6X}!Q{a&Z+le0z|7f#Xpv|kHv9mMbgA0zI~ z^mqEa`AdVxze3#DsLwi}%#*oi>*#7oWXVjo9hVX4nX$x>``toWIzSd5b6-As0Wq-h zIhb=sX&EjsFIX|>IX0mqAbJ(ziTK0obJ(-cP^cqa>eCa)f?oS&x}=+e(Da*i4MpI8 zJ(?5P2+ps=094GrnFsSZHX@0gZEi1c`&| zR6~1)?jmsBIH{DUD$!B>qeGrY3&xdlx>NnS&U=5?2iZWs*eo~5I~3E#$|w@tZK}`g zDaWGjOs{T4`}r1(PU6{iy!Ny*Hnppo(5Ai8;BHjJ|L8qBh}h})(!fKv|?|Brr62RH+Q1e!rq z67EkJj2lN@a<0`YlJmXk0hgqZ3?F$j>;(?xcKZH&jV=#PO~%0lmYxN|zw>`e_h(0< zU`!{c`Q%!!0<7~bW8&_x(vPaN)r>(ShoRRTogM0o2>Kmi$>bh`G zY};*Y+fEwWw$<2a?4)61+qUh-jT@t})8OvD<9_3g`#a;DjJ@_+b3OCHJeLl-U+l;? zv7i|>@3j@4n;X%6`1Qg7bls{_7KXGglLz9S7d z@j8>z1Ue=#w8c|YG^L70Chls-@#Crb=@_VBSV$NqOb@DVFK|Ab&cwLxldu(kvj1rt}s8BMIAt+m(H$KJ=+2r z^WcHndFt>K^Fs_vm7;?m?TubCOfEdJlQ1%F9uw2wS>@??D-x)64jkqX!izM86iM|I z|CTHM!0I<+4ik5xiXmnVI9$b1EuGFNcsQ|kz_Q!+czwi3V=`c(W~{`H7#bs;yN@kl zez>(HCtB`{?~*q&t5^{*cRjCr7Zyr9m1n-sr45h1FjFLBLi*Ln#r!DP7q)rllURQF zRyE2Myo@gAXR)}h4c1qcXICoDMys6&4+lpoYx!zkaO$fQrYhg07d;-UXm)ySb0LF+ zA3P=?p4_ZH5l2KU>}PV#6~+pSvhaIaqzx<}N1UM&i$jj9wXCe)*>rS1KBvxRlDTy` ziK_A8&#&&I_1bs0ys$Om-iM=&$Nfi{ro8IQDI^=g?w?F(@_S*O5GS!M3j5V7W^df& zde&Popgv|==Sn9}joW~Wdj4X-lc$0vitFezA}`iB7afpMhe-0w#BP00<8Vs$8a<32 zK=PQ;QG%d(8zAI=5|5FVxKz-1GY6Jk7DRB%vwUD&;ox~z{ioc^dMrmE?g#wN(`@`l zmXDr6wVEud_;AL?(&oE$lx&&iLL_$KUNV|wytTx|tZ*~7>gL=1i;#wPvjywD)j$0t zI*#@=1I1dMeV9#b&Qfnu4W25AMy~~1=J(BmPx86TS zL%Zwlx>;*XyTH7z96Cog(>Q*SRCV3GU15=2?5lr{ni?xAWqIb|EO^i3PAv1Uj163$ z{$ssg3}0esTrts3opM6IxVYVQv4gM0_hB~W^VZ(rproLN>_dzh zX|-DYiZMY^ayRo+^%$IP`(V9I8cBnEFZl86fN!n^%h(-JIYLBjPxuk!?|*1qn|=AK z`=|_L)S``*{eKiZ3Yt&M2&zAw>j@}J4#P*$Jw}y zAvn@fbn~-@wJw|tEs%9wy(JwZySA!%XmPJiz*NSx7^s;p%?H)zf#sQE3Z@E`%L$j! zdC6>zwb_%XUyN5S-iUtT=%Xjvlb=u{kMwTr+Rev}rapl?2XiYMcy^${{qaJ=hfK%a z$LADC5tEi3u$xdUl5hcoz|(OB7Ry~ zlpBiHSgSXBj)ylEjzp8)JdfC$iw|CL4&%MZD}s#h$HJ9H!WPukn1}{usWJ(*BvQQL zCijh9oPJW=a?vU-sYj^FOG~w+7*FoYM%oy1OMH4VMcw`_d@Qw<6Cm!KGIeR`_hj&z z*xDF*f??H2GuNAE#=!R1p}e72UKrLvm#VVj4!I9JKV8!xm|)GT>k8~#JD%~iyP)CQ z8i&(b*)ZFjNnv(+3kq8`ekbGirWtVermjy=a^J(lLy)5pchwh@14)SzHF6X2Q?NF zu`2Ix`@O)RSr${pyuO~i?^^B4^z`Gm5#C>tE#V7x488=T4`4kHDSFhOJe~Yh%yY1u zozeRT?@S(UL5~YDBI2~-YbCVpk5KlmRsc*$uI_zT^i6G#Nv$xZMe9TS6iOOIYCNgh z9QHdI_tEg@eR3es8&i3y`ggGxLNX)-sQ3@?TEcNk?^d36L%^mA5 zco~~z_j*0uEdf0T5z!BdG$yXr7HY@Qon*wDAiDiBfO|R5C?)u~ODNz=aKLP}7H`5D zbes>{OO3Zjz(|J>9vzF-Z2IimkyX@#6N%O;p7h{?d~3N8t}- zbs`Yo76o(Uo)j;#Vg=(+F{|))#p=dU%pocH=@aKyiWP$r4RZ~+WZVP9P0_8d{ z6sTLlG`dco)UC;~a0|voFMN@ceVeJ;^{X=?%8j}MeQgC!t1R?W8ynu?wjeBs5;{2u zG^c8{U()$FIYDa!kO$38XxSdbxq{2nOK5HcK-a!Kmu3iK*1|XkaQ^y5wD`j=Ty+39ji+_qvJD~j~?6oDb zg+Gacj5hyiG=1Pao;R1f4K|VDQE3n`p70Xqge?_q5cmJmhgv9{oMY>QSRcgrgcbE? zBiY|ycU=Mu<3WZfhK`z)!9k$Dc`#rwTHr4-)W~bDOhLO8=?&tE`$*Uagquebmfv5I z|2+b)5i4F>{gaLX+WYUNNaw+;QVg&_35b8A*S;2)ODJinC~N6l=T$hoJcSQ{tD9;U z%MpkE`yhx}TFn#m4i~c95Vh7Yp~K{1`LealnGdJPgb31y{3q^MjECAjZx6^aLjOc)W)vNr z&^TnmgNojGsVQb{t-Sd7Qo4U44k)iQ5|Ox%uD0DPK0JV)a_>j`7kcbFTJS02e@mXu zs8U|l)KyYfwCitO9NqY~x3HJo+2&9D-)p0X3R0p+Z;igL)h}S7;^{5Gz8vlpKSzJP z6blx^ZBj--&Uq!cRId?C#T3(|>CJ76QBGf)zPTJ_`aMabX({Ks8aJ&{W6-He zidq4U6c-17IQO8j%)Yv|)}Srt%fp!j1n`aU5D^h@3HzH(*)DD?Dr^Z(PGm?EqbSh5 zZNNZZ!TzlfWt0~Ev%?2mg+*91V|O$VGzk2a|JlSeEcEB2fBP-7-~I5S_1%(C<2&|G z@jt*4cC69YI#}6b&}yzzpzlq)qL;@+sPkcOp#ywR()h6t(>A@GwR$K&`8Qddz5uf~ z`QD4$=o027vu7;i%#&lIBcoaOrd9-mJCQ)IZ-(A#=|TfS_!6%bgQc}ke?3EzdKTgy z2q8fh;Qy3ZIVioyf>w$>c)}VxFQ1MONYAf%v3MX*($T=u>r077RU-^6$^We#4GOKrKL`W;8N>|p|JNGkWJ05E z_7>Rn>&LB}SZ@-x31^k^KZ3QawF!m;wf=T0ayuYJNKhb!37EOjOo~se79gWdSA)~L z=4P-3F`9T-WI13&P{&3MpB7?}xaa*Cu9`E}x>%Ig>Eg)hm^GYs%;Ui37qA4^2VJh_ zhUUg&7z-R4C1&Abgo4~&D7o;G;)&Ikv69sg<956|cz!Biev`*#&H-r6Pjw&&&|I(H zG@W3;JuxSf(L#TpQoswdCa~H4W{WdJIozg0m~Andn&5FtV+vRH0J6zBlZdgrYa^TJ zwVQoLULMc#lNlXhJqkhJ38xdUX#p8x7B1RuIEkP?LfewC3I(9t+aIPj;yFEixIT3h{4qzQGMHFV9ZeE2Rf`CXir2D}jOX z9%(Ae=xS?54@Uu2w__wVdA#sAwbH~uT{RlCPAs684U{SCsTUKiU2uU=u%uc!S^`{N zP%qyrU1il?-4A^5r=4*+2S((Y8RUx^1<(GR1u0I7z_?(d(u$Vrfo9)Vf}QgRt9fZB zXBat{e@FQa4S<(jqJlN@g6Kke?ar%x!T^B{0tv`x3a`%3hfz2<1>$tyo(`Wub=}~1 z^;xjgztv@PLbF=TG_k-k8~-_9{o*WEe&8NilwTg(18AWu%Um>_By4ywM>z5&BuW5R z@cT3*(7*!F{m9X+zi4y#<2?Nceu0vM|L^}Zn$+*lPYGmz6csrSPrc-FNFRwGk+ zyLjK$?%LTkyFA>*9k>s3&h$pf>J^3X6K$6KsHt}B`PyqI4T+j#k6{h&D!412st zK3=;v>k#8$A52+dvzrk>-jM$-0eKs%H(hRcXwCfS)bcVVhj|u63^W`_IL*oiZE=}{ zhr6y@KrMs3x9!#2fCbnyn@i&>3)5Q))wxGkBK^#dka@90;aFJ2d5%o){+rz&pn!q0 zC1VH(psD>9m~)u_`E$^_!4uDRx1sy%22z!OJ+vffF8tpOmG4`&aXe=I#%T2Ce{B}ljK!uF8wg$`N6AyjfBjH5WT;Wt- z=4|UM!fpmpXPAn^Pa5FD?E``5w?$=fG@7c%PRGt{Ma0 z4e8K5+#8e+!QQgM)nWuz)r2w{Pk48gfC}k~f^;8Eu+aN)UR0WSyK@D4g*Q5;&rAC^ zyHr@y3M1}ZN02!6lryj!Q0MCoEOXs9EsD@FaT*Z?hPMSMO)Fk!`i zmwKSWr=)yR1B&-~O*Jn1OrbA4VYHdUkN~0x6a?D0Dm)tk9q`w#WCD9LPAyqauQo(@ zgsW@U`x_@okkOiFJKx^6QFYu9AO=T0s~^>F4H^ytfP;P=ke|B$u(ufte6~<}9Sa&Z zU($kZ)XGy0Gyp@#27yLg-W0~STQ9hpVbc-UMU3Ci(P`vJ!9jsw;=2z?I1a`qy4$N! zn!oH0H%@55{v4jR9h2<+g8{(xVCR69oU9J0SacO<9E*AzYAxf7 z6u2L%C-0#;G`Mhl;KFOQPjMSXgrTw&lJ)y*!T2VQqhC^{94(3Qy-#OguI3m{GzB52 z?XTA_uyvR(0B2t{3QnIgQE{36&e^?-TGsYYwn*t}DTP1uW$8iZ>kWPc<7ij8<3c?Ck8#Ve2T1&N&-6f2ffL zn~0OFTqdh6v%mc9kU~N34-FFpw$CzLk})4CE$+vai<`bGz=rF;?LsdDB+Ov2FK{Q2 z_I+xTApRJM$M>y02IFuc2Du_t{u7<6A17^`^ixeQ_m7Q6&3*;lHPf20YIkbQF`_3Y zuUylfi9zshW)q?2A-qz&pIlFaZZHvA`wLq=E;| z@-}Uo>|yCn@oy9k=5AtMZUUSG_ zWO#SxH?u!Lx22PuF&yu8>5R~MxOZt*<_ZKZoIpkl!kuor_oBaD6$9U+zI})Hz>z}j z)884{{5~*0v2g~)GB_B9f(Wz+s0f!3divWcY8r#}9xWa&Vj$u9H8m=9_D9)+rNx(C zIPqNq4;kYvQ6w9DV8WN}Y7L^4kQFP0>w*d+8i%y#n}EP3y%Ya@LliIGcc`a|lKq2} z70la8h*$nAN$ke6XYdygL?GB21$vgqpVf+bXx6xcHD|5gB0a0zRV?SID(OSbC5rrSO-zHJLtYE3k3c zoMnFWs3&WVjZWH&qQM$bJee8})&6c<;|+X)x)WoM8#8;t2tF%GRgP(b6K{q0e7(`B z@~2Qa0XeqvER0Y;y1emUqkw_tGMVuZy&sLLU}$aefhq!YKb+A#)KdX+u8`qKV%H}Y zPkDLy5D<<>w~kwMZs9;b7Od3(V%*VW(&ruA(NE#sqAQHk>~96)#Zv^_i?IT5S%Qc@E0H53dStZxYRilT#SyYvG4sNw32HU?=n7V~6+(euj{jYbs3X!)TzrK;A zFo@ak)o)`omRQF?SyxM^pQ$z$9z{BaCo|J>Upa2h4s( z3$pl~HE(IfAItYR8Re;dO$KArmIi?xZw_ySri>E=O`Hc2kwKS4fu1Qd-JEF@tf-Y& z>#{UF{+^$opXVPNPR%9!2Cm1`wYM^much;w^N>7t}Qmr$tStvZ8D>*~#0WQ)eK0RLU< zMamZAPBWZ%ToO-D4)d8CPX9p|hkGRb+rf3cA!cEO`cVr5ytpt<-*|~(6q=q}r>d5Q z@&a=jr|~6-juF@aRIZz+9<6 zSFi~^!Z>iguQ*$Ku*`H9SON($#f4Jh;-N6ye4mq)U)H}K%1gl`V7kvmb9NKy_nXN)%XhvViCD&Y4+{j0Jr9O`&pD2H`h%93Y;NBZ{^}ZkeR5c zJ-|XCz-4#Q!hMQA-e3J?+cv^sJcY{4tVPbOqIN`0*P-|d_xu=S+scG0H2>;zsH*a2 zV#{3beCyhk^>_x?bINY_ceu0f6~Q&Q1iQZ+q1Ar^FaphofmDmSo+$k|0`^uP8?4Wy za@EFv?z$mt^NXyf{V18wf-)?k@6G!QZ*!^%rvIi=s`ykar~gK)lvK{!PpgTALCapV zre7w(!4}&U9TyD63pll-rn~S)8823W;lDQTZ}T!Q2Efe+ARKy+qnU_I9%r+>-j&!+ ziU4y%u|UQw!gIj==l_1YVR+bzEpZHMFChcIi_1G+R;4*kV zWHdnN+P!NYo3=G=qWI*eAC90Le)TF<>%l~I?T_$=*0{Kp4qvprdGvk{bi0eyO5w@XynC60w zPND9al?@H=1~vH&PKs77Idpk)bRT7JIVrwJVAu}PJ8K{?W?pLl2c+kb$4 zErsT=R4BA>Zm*D<8{b^7hf9~Bo6aW>(+>`f+A8aCaO*GN``-Dc4S+GfmA*N^A_vN= z?UR`Mtr~Fo_7)?Vi$F)kY9^fbVnp{N-~e9YrhU5}UAhbfWAU(EW4rC9ajc?$V?f9M8x&L6mm_ZaVW`oh_8mkBNZY+5@kKCX+eUr z3)|JN9;g3$z{B-I2syUyfE*$3Q2&1LADsT-c9?=i?o6}f7?UGNv7V~=QmNK$gJwOW z9_zqio_(R5w=`l&8G%duyVYv5D0XN}s|oe{g3uyyx{Xyr?}(GO3z`B(WytI;4jn5T zUCpUpu~d_wD-41wV&*U++*KSr3ed%@t__H11CK@3hKY}cg^sryz)<_-?r#m(yS6zD z2X^{rH9*C5a=Nqlr*c1Yc%2s*xE2bS;$`PUlZ$+F*2{XR+9nnKL|6Bi(I=2mV5bC% zLN=B6*B8&WI6`dbB!YW-Ahr^&p!c+=HM0m;pM);;5Qg|*`0!pqQXZ5>rjhM)@uD^D z(5LDzC#9%Da1G{I8S9*q)q_{8tV#`4yKcHcC>kYF3clUr=>RMLO|r^67#9hlJX{em zQN6++`KcqwZSu$xP)$8(bb)DrchH?9o(N zA{AArF@Oaj&d)2I_emy-_?g+-+o1s{1nAMv_1%!BOPRKad74Ix91Rb-7mn5iU}p8d zE2`}88`Y{p@3|DJQ@ZXxcsAy_5E~IS?$J`MoL9|2%7t4PbOs|y8M1t{m3@{tXhnDe z0IveFJD~IgR^dXJ8^uEaw#i?B!&Bw35CH8#%bV;0Cl(_;J$7*V z95p`(*v#n!TohOw9DM2@tv82~ zthuD$mLYVQFKx(#yIB#y?1o49^u*LPie~g4Lx_L6Yes?xF2)N1GO96JXL`rFt#tpm zoGPjxB>m>okShzsb>=oGF~1D0f?EyTq6@LJ>(QnFaR`R$Z>iWKUo-RCCFUqmRlWm5 zfK$FUg9z9Xyi9CuCrwzn?Mf<>x`VS1e0qZs*o~I%x)EF@Fy0o0`CGPN9~3^wJ4(Ra@-VSC*HfUV^g_($MCpcA#|#-g4C^ z@MGff*(}7-x$wb+sQ@ed%>E&O7mbk!@sIyg&nC&}=eWkUE4wQ4|DLwi)ZhDSBQ5ta zx+@SrxVzKyH751YZX^x|n%4=_TVcV;Oq_Y9+~T)NS2L>8TefX-o3G*Pe_4I6Kos}(1r8}l)?S?x5Ar+4J~kIbTBR1 zX@xMzs9=;34q;A4>wCGC-Cy0l>Wf&r&4ExzJP-&s5FnY20I%I@YYQCqY<3PVL8v#0 z(+A)g>l9vJuP6Bu1BZf;SZ59k;*IPU$Q-n&*OvPsr=h*Bq@m6A@H^-ZqGvt#;N)=L8F)PVa6bi89M4@z=Rp5&D#`7?cREk!q+{KViQ zhR#=wRJ1phUh<%8T~?>eZ%IUeCqG2#E#Q3g{$1_+9&?TI?BzjkrMng)Va}mI2&2?% z5tNqHsYW?mJC!r&`ln^P^9vX_Zk-Jw(H}Hrv6PK=Lr(H<(V)tp?>+J9f7qo1ivt-3 zRnaAP0EtjfNo8w|vf$DlD}uJ-H2AeU4{U~&Iqljmh`4_ZdS;2TZ-wkRaTQR`-re^_ zeW&t{%Zvs`agsIG8@UB%Gz4<%yn+lhI@MIhD)ER<;^|>g`ved!=O(cDCY3hMtdEoQ z(FlZciJjh~p)5zw^_TYcPSuD!%`s&DI;CD`Os}TvZSs3KURjWdmTcSAZkUqn>`S{| zcr;n^X=zMi8-ajEq1R~C7%6r!YxMcztpV`h7~K=#u^Isch^JI04gTIQRy-9>v4J`; zFuUIo>-M^9xaa7){^owYh*`GqkjPhhPg<|J+5x%a(+J2{?hE*ACjbsWu!tLOuDZ#P zRPGEpgEocCz{`nqczubQSfeCCu{5Iz1YIaIzIU;a+aHy%4Bc$8jPXYr)NfPMRCfHj zHT=Y8;k$A58x4D@3hH<;r1)})LDglHM86{Se#XJD>jE8K&dbMU2Knw)3c<%^OF}i= z{qhoPYVBS_h`m<-&FDGjs@3OjJxb&bz=S_jV0l6LzGiYVc34uaLcV|wJX-)&p~k^p zfskboyQmiHbK)8&f;N)<1H}RQBr&%pN!x+L`&7L-mfPCtr3WX;_^wNmX7J zH?kciyFex$A?13Iy`l2ILrM?J#|)tZR>K%~`@!PutJn0b-RV@H$4%`!Kaqgno*vhW$^v^>@ zF7EA0AiQC7Zf}W@wf1DVv zHjaX{ei7T+qJeo*N5K8Pk%9@mCn zm*gl__z@aXOMtM1e7>}!Xt{YGtYIwZYwXc%Rqy33ZAzsUNGkNJeL}1NLPzEg3S@e| z*B5{~!6pkdUPSWC+Dyo<`+E7F?X&ReQA)Bq$(18&&G$I5bXg9UIS%nqD|zBbypyk7 z>E4qgJSLspZCsC>4AT~{5K<-o2mjkWr+O*e-SjR^|Q`foh8(1y1Ed z7u~j}``=xf$?@$@7+K740iUNG7|-ZeBieL^r*xDCWWm2%GxJf|vT42brFwg`H%>VM zdd09sGWD{~S4#|0vHE<8T{x_i;%1ojWEau!;>5Pj!&b{lk zv@J>DDGQfGLc;1Bn3_UmYh||#WAQ`zzZaI6q@sg&6&4zmW_WE6Tb z7?FeJ9v%hH^j9XV43uBfR9^L|r{Do!_js0Yt_@ItQ@hlT-gD1QAvt0~85QFGMD6L% z?$*`&9?ner`68*2+nq>Gvru>FJjJ6|S)@CY(0k=(LfF4r3?O#caG5kSDy4e?%% zUi%T+Rq;DfgR67M<8Pym8_mZ=1~d%vlTaoL5Q8VJKY%r=13eOnxa=`J>xz(&Ml1C` zD<-USF)r&?@7^L2D8XH8ccmi+e5Eb7n2dTZL-`p8N_`XQx7fMv`nY-~v#bH~Tc-rN z0Dmt5n)B+~+nYvg;cnqu zYO!d8wndy1DgTsDLILDL!+Z&qF%T0k84cS#9o+>*d7@`_&D8-O$uhFnBdWz6%Rac1|3s?_+UR(Z9jZel4|J1 zlts>J@>t=1hur+@QA7@IQj%M91!in~d|dVfpIfTZ&K`#M@1e+5g--SNzwSCPkLoH0 z*kkAQKbL$Mn?4OtR<$9JL8pym@KEc?J4*G2&c+HX{biiPY;*PYmE0)&LI&Y0fNI2Q zWW4u#&SJEI&9SgJe9=z`%U!sZr|~QpF`Y>oyQTveOW0JoioT^IF`o4oUl}B zla{zz_}RIO%8RQbwqcfZ`fjeGv!iYTrX)hpIJ|v5XGYGuuCHAX%w~8)r|b5wa;k9k zd6PP?)>}tOyc7*DHRc~YY4m@Rwc5-EWkuG91P+EReA^I!(zCM}(Kv26H_U~PkG=ST zVGZdL+<;8DxGq=<^^-86KGB_NL;zAO|2$DQruO2OK~M)|oY?q-5U+U`1>)cJ-sO(# zO)hY`DeSlD_T2hkkdL;PWojwrBu9fRPSu- zOOvC9CZtuXI{<<|@5#-7#z!!EbyT@k_+8-78{zqfvLTiVs?$yblw z>TotWA_$rNG_93(Gctb=9eI$xSlnaY!L){Ev4Z-4bll(ulQnU7hs=x>M-aOBKohC2{)D>i5STB$AJyxk6n>M+M>1 zd1xUY+5l2d72dfcFQ)8vC2xT;AI~(dYK4*eKxv4HCZZ_<8jc>pVWYVf>gk zmsL%b7-=Nridom^OR-&IgD#2)5NaESEqahdb|t};5p16Lks=}N$Pw#jkvK&YL_NWbpE$s zW-X0phPCfNvQqANDAb~#QOW-*xU$|+{wx#E2b1mb;Y0#yZpzl9?zcGfy}r1=kaK&w z;epX;&Uf7Ddp4>St4V4IuatXvc@E{C9Xd$YzH=86O`KU!uEV&XA3&mDJpE3nVf3EWe&d{eUqwt8J_Ge^#|)Sp=tMqf4)zASM5-I zJdlu&8T~F9=jUluDV*&5XLq|b3U#hELi`+&U=j}z`d9$B1)c7)&dAgh1MSb_(_Pz7 z6eKg&&YwL~LM@#UPtg)~?@Q~?x|YYhu(F0-IJ(}?+r>$z9&21f{hIOJSfW*LiSGil zWzovNscTeRcc@9Q3?>VqJo!kbdM)Arfo?-9)NJ#aJJt(ss#hS`#bW1;k87HxzoJ5p z^0NxyPul!!wI=0w?~quYF`Ay99D#q-zQSZP0T4MaLqh0pejC921hlzU=^|DF0@dRg zPe1K_^w}|rNrn8J-CE5^kbq%#+!Xj$BKIy_*IU5Ict#J*3KPBc&c?;g;Vye$C&2}+ zgyt8^L%gr${bc9{h3wV%3mO7smzJ8I#jt3+?ob@{=6!6|Y*s<8;$u}bYTR&*B|v$aV{lvO_bMx^)jlw z&_Ve(hL72R$y$TE>!D-y)I@7Q#l`HSFJZvugdNF*KqO6(F=PFq0QQ>KaB(Cw+YBcG zTZa!g&{=6P@r92PKRc0wO(mUmekkb%>^Bl|Ji0IXauWXdOffpMAr@#pfP_Txj_KDG zl!3hS$1wnxo7d>30QHsXGI+Xl+^tyZ?$0&#CtuWht+q>C<6(7AjY^Rhep62>l zl{N&m!biNe->wJ~zr}#@#y06&kj9aRE9eJlZ%je8H`Y&tx4(Rpl(&5bG)5eVJLk7&hXgquz1z1@k~sI!n_k+<+?

ABIjvdKdiM4=%2*&mfVYgmb|nMoD{EA;^lygFh! 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ℜ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 TrHCdX9eYVpS&^c5dLRB9cuMBP literal 0 HcmV?d00001 diff --git a/docs/backend/images/data-model-relationships.svg b/docs/backend/images/data-model-relationships.svg new file mode 100644 index 0000000..c80943e --- /dev/null +++ b/docs/backend/images/data-model-relationships.svg @@ -0,0 +1,132 @@ + + Backend data model relationships + Entity relationship style overview showing Better Auth tables, workflow session tables, evaluation jobs, groups, and template tables in the backend database. + + + + + + + + + + + + Backend Data Model Relationships + The database separates login/auth tables from application workflow state, collaboration, template assets, and background job status. + + + BETTER AUTH + + user + profile and admin flag + + session + token and expiry + + account + + verification + + + + WORKFLOW SESSION STATE + + app_sessions + workflow owner, config, metrics + + documents + file hash and parser metadata + + extraction_results + entity, model, answer, refs + + + + + EVALUATION + + evaluation_results + metric scores per extraction + + eval_jobs + background job status + + + + + COLLABORATION + + groups + team workspace + + user_groups + viewer/member/admin/owner + + + + + + TEMPLATE WORKSPACE + + template_folders + scope and hierarchy + + prompt_templates + entities, prompts, scope + + template_versions + snapshots before update + + template_permissions + per-user overrides + + + + + + + USER SUPPORT TABLES + + user_preferences + default model settings + + login_history + audit trail + + legacy user_prompt_templates + + + + + + + + auth identity + + workflow records + + evaluation records + + group access + + template assets + diff --git a/docs/backend/images/document-processing-workflow.png b/docs/backend/images/document-processing-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..6dd50a28c93dd136315e6b5fb000cf1c4364e50a GIT binary patch literal 102002 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

pV4FfvIcKluVbL#_kw2CzrrHKF4~i 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 literal 0 HcmV?d00001 diff --git a/docs/backend/images/extraction-grounding-workflow.svg b/docs/backend/images/extraction-grounding-workflow.svg new file mode 100644 index 0000000..44e192b --- /dev/null +++ b/docs/backend/images/extraction-grounding-workflow.svg @@ -0,0 +1,111 @@ + + Entity extraction and grounding workflow + Workflow for loading processed markdown, augmenting it with figure context, running one LLM extraction per entity, normalizing results, matching references to bounding boxes, and persisting extraction rows. + + + + + + + + + + + + Entity Extraction and Reference Grounding + The backend separates answer generation from visual grounding: providers return text and references, then processor-specific matchers attach boxes. + + + REQUEST AND DOCUMENT CONTEXT + + POST /api/extract + session, file hash, entities, model + + Load markdown + raw_analysis.content or document.md + + Add figure context + captions and generated summaries + + Enhanced markdown + document plus figures section + + + + + + PER-ENTITY CONCURRENT WORK + + Entity tasks + async gather for cloud + semaphore limit 48 + + LLMService call + provider dispatch by model_type + Macbook processed sequentially + + Provider response + answer/content plus references + meta tokens and duration + + Normalize result + stable entity payload + cost estimated from meta + + + + + + + REFERENCE GROUNDING + + Load raw analysis + processor artifact from blob/cache + + Azure matcher + paragraph and line matching + figure reference expansion + bounding regions retained + + Docling matcher + paragraph and page matching + polygon extraction + normalized output shape + + Grounded references + page, polygon, matched text + + + + + + + PERSISTENCE AND RESPONSE + + SessionService.add_extraction_result_fast() + + upsert by document, entity, model + + return extracted_entities payload + + + + diff --git a/docs/backend/images/llm-provider-routing.png b/docs/backend/images/llm-provider-routing.png new file mode 100644 index 0000000000000000000000000000000000000000..43bbc6e40ada8a819561038af2ab08382a94192d GIT binary patch literal 79047 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 + LLM provider routing workflow + LLMService routes extraction, paragraph generation, and figure summary requests to provider clients, wraps calls with timeouts, normalizes responses, and records session metrics. + + + + + + + + + + + + LLM Provider Routing + A single service facade keeps router code stable while provider clients handle model-specific API details. + + + CALLERS + + entity extraction router + + paragraph generator + + figure summary generation + + chat and future workflows + + + SERVICE FACADE + + LLMService + extract_entities_from_markdown() + generate_paragraph(), image content + + Timeout wrapper + 240s default, provider overrides + timeout log file for diagnostics + + Response normalization + success, content, answer, references + meta, token usage, raw payload + + CostTracker + records provider/model + tokens, latency, cost + + + + + + + PROVIDER CLIENTS + + Azure + OpenAI SDK or REST + 3 retry attempts + + Gemini + Vertex AI endpoint + JSON schema support + + Anthropic + Claude via Vertex + prompt-enforced JSON + + Llama + Vertex MaaS + primary plus fallback + + Macbook + Ollama-compatible + FIFO single worker + + vLLM + OpenAI-compatible + 600s timeout + + + + + + + + + COMMON CONTRACT BACK TO ROUTERS + + success / error + + answer / content + + references / raw + + meta: model, tokens, duration + + diff --git a/docs/backend/images/session-sharing-template-workflow.png b/docs/backend/images/session-sharing-template-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..515773f3ca295c779f327daafc4cdc5f97e6913b GIT binary patch literal 72251 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 literal 0 HcmV?d00001 diff --git a/docs/backend/images/session-sharing-template-workflow.svg b/docs/backend/images/session-sharing-template-workflow.svg new file mode 100644 index 0000000..eaeedfd --- /dev/null +++ b/docs/backend/images/session-sharing-template-workflow.svg @@ -0,0 +1,108 @@ + + Session sharing and template workflow + Workflow diagram for session persistence, restore-view construction, group-based sharing, and prompt template/folder authorization and versioning. + + + + + + + + + + + + Sessions, Sharing, and Templates + Workflow state, collaborative visibility, and reusable prompts are separate service boundaries that meet through user and group authorization. + + + SESSION LIFECYCLE AND RESTORE + + Session API + create, list, update, delete + + SessionService + DB-to-Pydantic aggregate + + Workflow rows + session, docs, extractions, evals + + Restore view + build_document_view per file + + + + + + GROUP SHARING + + GroupService + create group + add/update/remove members + + Role checks + owner, admin, member, viewer + system admin bypass + + Share session + set shared_with_group_id + shared_by and shared_at + + Shared read + membership is enough to view + ownership still controls edits + + + + + + + TEMPLATE AND FOLDER WORKSPACE + + Template API + CRUD, fork, scope change + permissions, versions + + TemplateService + read/edit/owner checks + immutability guard + + Versioning + snapshot before mutation + revert creates new current version + + FolderService + scope-compatible hierarchy + no delete while non-empty + + Tables: prompt_templates, template_versions, template_permissions, template_folders + + + + + + + + + + SHARED AUTHORIZATION PRINCIPLE + Sessions are owned by users and optionally shared to groups. Templates are scoped to user, group, or global workspaces; explicit permissions can override reads/writes, but immutability always blocks edits. + diff --git a/docs/images/summarization-tool-global-architecture.png b/docs/images/summarization-tool-global-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..7ee57ba8e6e89c9b9c30b5d1a29fc9817fc7feff GIT binary patch literal 120372 zcmd42WmweB8!)=4fPjF2w1jl8lt_bgcb9a7bcb|zcP!oApdj6_#FElo3roj~kI(b} zey(#}=RNZQh95Kc+&MFMOsKr97%DOWG5`QTl@J$J1OVWd0Dza^*Ds#Gdfu^2f;N)ZrU)u;2S?Hjw@{K%y!3WhuS4rECeHc=dV^p6>(~*g@fvOgPTla<_8j zc6?M@%kpR8e_EPK^nYF7z7I4n|NSEe{`Js*-_79sSpMs4`sTd(x9^KX`hR=)=_ibo zexPp9l6cG2=1sg`9(7<<);oD05jhk+?8Hv0znJEzdp$Iz5wM$?Xm_RB`J0Rgq?<&( zXr0$eVWR+$^0*rt8w&$$K)gsDPT9Bl2(SOnDwf05I)}^DymPXe87#zy1N@xumNz+q zTu0(QO8BV$=(Ngta*qC_YQxAlDnr0lu-+z$nw|=_wJ{JD@kIIygGN>P_SVjOE&l!) zGhp_+cUzlX?YAHAP7)&4pPG{pon<15+?*ftM=1slF+ zBHn)iIFH~3#84L3j>o{ehf{z5m4a{AUmd12>!1oIxC>IUz#WAO* zarpGX>1XJ!&(vO$PPK=vS`TlndS98Imja zHsS?(fZ2CZf0k&y_V=}%7w(!-p}CK^Uaiuw!-yowzmwC{lyBV)!J~SQtqSUj`?FKf z2_rp+4TM(!HYo?eRC@HtWL2sH&1Y+m$0?d+v{8$!&%UKQ=U-k|-m>5T{B2QL9xgY)#39$B1JhcEv@!NuUCw=X&c05Ew1{nKKfe7bi#nqC2 zBL3eDCWvzctj2bB7gDzVlTc0`*YOp=LR|*lShtIou#xgm!Nz+7AqbJ$+sr8EKOy~) zJ|H~IzW(5Z(a+}o878VUz7JnqXF*3(_hEm#HzA_wU}ll^@#R6E>n-~~boio~nVYm) zYV+?aGSP8*9jrDX0l**vq@j072?&`jqNvj6`-ihrasLt>wYyS!laNJJ@?E8<>K`kU zH?*LjIUl|ubOVOVyn&fsdME!oR`v$!WjcL}0J#CqiLH>pFsL{<%+Xvv~le~|;^S)a$=oQ*UjQ$DfV8nNYyA^y8-ahXY0$!=4j z>b^@X=fxERm_2gHtn`h^I{u2>e9@Fl&dz<3Rbl_6AQVPH#B+Z`% zSBV80E=9Q8#ks2{7+RjXq0{|)s zPVO_Cjmvk7P9&R9SZ=c?nE)h~-)kaSv-{E6AJ?iwMIeVj5nZ~l3A9^v^_hV7GB5Ji=?BQU;NQ{lncqTl1L}nwLx+?fzQ*hvKQ;STUsI&?ABzH@ejk~?>tXsQM$Dcc>frQ zZ)*uF&H2tzFu3ceVkC{g{36NO-qlF+#>F<6Xq;5@^5bn=Xg-hXvZt)TVpyGx7;ze} z^|7(lvhE>2VnIiPvAhWbSJWk?zMAJG?kl@k&+)tO17 z2IRqG382cG%5RHmqQPuU4@`vf714r+M?)nR2`NK#k*54!dN4ExG)FWViP;ixS;b`T zG^2Mx>^?KK*{?llbzN6=hzV>g$m+G3HZxA+iLWj>cn*-sH0hK!&)27)?r^PM_Kj30 z9;{ohB3B(@J}>U;?$}rWJewcp1;_)PE@|VHPQz~J@)U&fB$~P#B^H=CIbFmQ})$-ZBu5ju+Lh=RRvsuuU;cHp-G4JwovS1jk09(|LyVs6*lw|V%)Bt{@6sFV#lsV5mX-Uk zn?1+m9OKQ(YDIVpH^WTXJ5@!<&Yr}rOCFs%J&Dcx3=jZTOe(x!G&LFmfL}R%(sRFG zr-W4nmLRYi=Lucmgy`X+5>2E!GFm|8|%WC4P?5WvMlwur#m(@&?VUaB>J=tB!`|*>Yr^c=MwPp`a_@%JLCWi;dk)mvO zbx9XevU7F4ao*$|SYaoV{q!L$mKhxdV!r6{Mh747w?Up6o$Sy8t+ORWa3eqo|`+nY^Bbas*3@Y1w#r_BAED zzYVZ?o9nNlt9ulXTz-uotq@G<%4j&z%-a zK<$p{scj((x0oWe3LdKM2p_*-_V;K~Op;fsNJD*AgI9f137scny8kFPBhC3Cw#&M8 zZi{qkTnzv9sFXH;PPX@yVKF)EfLSxO4raXz#K>%|;Rka(+a7}MGwBNtK@aH3MRSoQA@@7ZX%y* zeJH%j6W$dH6A|NJw_X3fm8h_n17rm z1>N&}$JM0;!X}dIwoj>n18rEH5PlYeGh#PAH4#}|rp?b!8gmldw-;TF*TJ^@PVCdl zU3FV?Y)2!k97Fi^gRc>O~h$e z-)nUw!bsjjNjf*8t|=Aj!2Bk#*OD|(-gY*c(z{V96CGo9v&01YRgXlHxKLv$1E|p| zZn8t2Z1_`?nWC8CR$c6}5}4VW`+2XPLmSuh@eCj;VIH?7N_&Jw4QQt}N$DBYE?Nh- z++A1jzlnFrP%Sd~rnaQIwLcCiHFM92oQ5>Ce#BcF-YXq9l9J%dE z?$Ha?ct~|F)H}_*E3tGfk!u=uE{yjvzpaMUV&Hc!20s)LDK5dwt*pUM*&)}&c}V$v zsS9mQK_ytDyVE`9m*OMY`sg})MXhA<7^)dI$7bHY3;TI)M8=|+O-FyZznD%5L=ItT zQHU3{ZLP41NT=_L6E*@5EUAO5ltQ5*43T==hEw=k8_L9L1Lp~mLWu>TN+x3TcNV=K zVVZ~R!C7$x&}Zi)I&_PiMbX~C`5Im8My+#Xai#?F%o%|k={GHJAopZEzh4N9f+0l_ zLD;xh#FO=n*8Dwg>i={eHb$vC?wpiUXnYMQEjWT z@V-(?)NZ;-K*~K^NGRulGkn(zjqs~9}@26YHbteEUoA|dWpUm8*?8R%I(bKPgHvWJtyAZ zSsoX(h&NOjw1~FzLppuLq_S-fg+;XK$jDC|bLQ(~;@8>K3>Syc42x+>3zOwiGsRu9b~UlfSCL-gV==K+9yvge~Z zUxTtpt`Dl)ie`_ilZ5~L>qG3ixU}Hrnii`L7v^Z0tpUZ8f1AfnAat4}V(F#-r6+}j zfI>#?PWq1%iW>|_R|R_Zbsdd?on`;Uw-PXtLpCH>4xYK-zgPgytvJ)Wa3#5_<=jpy zjKMExw|3|5z}qiPyFZ|2^V}GEeF(1u(J@*VaxByZzxWxn#) z9D*=fUE)|RDYZtNe_XRCnqBBG-35<&jJyydNpRWeqBwGu|9$qd)1z_p(E5u-Kx*@i zoCLH|!^^vk`oS*U7QHfa%<(App`Ss`n>=$QeG(t!-%Vxpd@lU@vE+51E8@?a2K6V? zCGp&3ud>Z))|gI>j}_773tL3QZyDFD=gX-J{6IS-H>mLbhyJ&=#FLv;cp@RUs!^Uf z;Nge8;l3X>yV2pQt`aW4*SA;)0-JhwzfK3Z9ffFy$tY}6?fnSoEjpNGy8NBij?LRq zZ?sU`f!>?l=iRiW_XIhiNl8`c3OtxY|LU$mC#%74*GIRKp+shD5?!rywNWPZW!5H% z4xQ2K$|_|ceX*AkGMRqgg&hALT_JsT1$vWJ@wH5;?jL^vEWwpdd(P$w>^h1IM?5K! zGU<58aLvXh=5vcT6WSNrqjy8}XiQ&ST*J6WfAbQD=rLrjAn^G%LSlNkA%}-`nOvOt zo8@AzJyRkEL`Y?&B+Q z@9vM}5#cq&(R)OP&41M9>?_N9L+2EgZQ1%v_I*9A*H=ytb9%DKE(`;xs@Ff z;&UfH#8gDFxi9EjAn1~Bw4f_-7R}VdkLL}+VvZ&r-W9e6wnfdRW;ev|2^wQavaRb} zd)imp$-9s9MJ%5;!CrWXT%El*meQb>o4JK0G6fc}(0O_xkvL8^WS`@x1{$1uFPw>+ zi-BBs!((cXf&XRh&9teT)YkAOZfOS!vEq9-^?iv`S^7^Shm4S31RP@4tB5s3MdYh6 zy7_&u)$*_o_Wt#AZ&8ESDBDJfbxG&ij>%wEp%fO}kqqAO z=5kCR;M}To5SHeS43$X$jbN$^f5=7L~ zIArPudwVK&2HNj>4VZGPxm(>v)(`F1(XsTMiQQIh|Kn;x)F-_mUN6OcJ38iJQ3#_F zkGtT-nfm&gBpdx!y(4Z#5F;cAArJ_vrCX@Cr8PD1K-9uyYB^wNCCQ?5DzIKE6f&Mh zE-j4>t?ggMOce-}PCleuUkWR)mvkg0tIPd$Vca>IN86x-B|ciVT7SMv(iowe;?8CE zlVT4oFubQn7I?T;wT<{dz^=$8B(##$No9+~|}rWYMgQD^uQQEBI-(H%7lEVZLs zs_`oGa3DK~F+`m9Lz*J#%uP*VW;>tR;X)(1uss94?&nmZnR|#d@8tLAs@9wJ z+?HG5@1?&fsvu}jrT{9L}w(4jCDbALmF zoNZD-areZrzewBIxCQxx%@=9R5<|r%*?8|CZT;?{(|QgD zKDXUZnIj1jk)`*Bl|AH7x4B}rd9*jlg}{)Q)ECiRxJ^Ddo6QY-Qab4nf}CtEJ2^m~LU zXLEi;9`v}J6&X8|^bd2;HeTo3y&ZjP<9^BIwydjnA6UmLJa#-ukBKQaCia#mIfaiR zWFnC;sE3UTsc_@%Ap#{Xpf9dT+ufXDGP@sBn~#UwzEy?H4^s{Ev{TPuRjXi8wX0+=0d08Y=DP@^9&6h^k8JUs+7AktxF4M`^+yIrq#*#Z!_y3VeB$qPbqh@-u>*JP=IDqN-+>nGo=$b$#t4VI9FzBW#Fve-Ay#Z= z%eDGjO+I$rdhPSU5A$;TzhM&Ejt7NPL<(C58x&qKeZ)}=F+^K7zwEpTGKEZW*EZWZ zKewFgiHc+~xx8LN>-vRm0%_x7ZtLV?|0hT*Kvdu_nA#SrILG=6R%Cd$vH4(~=v_FH z8b8TCfnBCPcwW>};!<^_!8+XQv8J2Kr%F^*?V9PidOlqr71{Z1U#n%3wM)IT+#tSc)!Ii2o-!?4_QNoZkBKNA8XUBAxcS zSk9DMC+@C^Ff#Dt{m_H8TkBOZ_za3wbkD-p_V=AjhxizCHEjuTt*7fWZgeFfwQAeM0b|@`|Bj z9D4ap1wy#${;Vx13$o-ry8t8ZvlsPD>yQ3Yc;>(c1&(SEO1&!+tzo|Y>Z|;u5{vRu zZ6)s2b{I=HwYOVD*VoIJJQnR`7Bl$nsIbkhs7z*SEFhakoV)$c>g9X1L@R#SCQcW@ z|Jdp=nrxKSD#_EqC;u(Jr74>lX=gbp5}9A;u;92-eLCsYg)&}t2eynO}N2yyXmROnG zX43J{s)=r3lJ0O~+}5YH!+ZMam`5x#I#LpDvBD8`8Ck&LY^AOp6R#y{fpQmRE#BM=r_k-OxK)Z(+lZ@~HXIGE{jnn<~f!eNF^}TJ3+}^0CV4&mZ+Ik7#wy&M zs7@^7&e(*`oX*%5f+m<_19ijq%p$NuY>BuSMIBaZNJL--b_voT;tR3h;rHZjXWDDA zkKu!4pmS0Z^2cIr36vsomPS=c5+mgb1M(A6G;qkQJHZqcF{2FKSmHVPUQ(}?2 zIc4Au4C+rae2#e8=2@YcWa8K^(iZ86micbz4^uUQsp*POU`NFKZmR|p+vnD+vMe9c z<^~K`3ao`PQ4^vIrWQ*2yj62t60_>^hk7b5j$U?l)XNlxYutQi4Qm-cjM+AGz00Czk;`%GMWqyqz7cy!YI-gyvv-%U&+6OmbN7 zv7Pm2CVQXO6q6DZNbO2pSt}t7zN-k)q1S!A9DKCb~d{<5}$dKZf`Gg9}{{|Gz$? zfIPy#eF2oW3I8T90241>8UHt=9`OI!@Citc^imQW){f-kcCZPvt!W@5E;>C937egb z{CkB=i@J(;fx~axVm0#_YlMIAZWT4&k5pUb;egRp&T(dgMkZ1%mZ%bc97aW+fp9Pn zOI2D46vQoigXC(tu+>}~VP&aYlBUR?Iwq-@&<80RR8a3<+k>I;)FVf76={vpmN$-@ zY|n9`oXUy8oHt&4HCYUh##@T3t5Ku4SWye4x$$E5w8Nn{)6{7y9i$~=_1J@Gsa?RM z4cp?#r|>HH5{Kd$wQuY;TUX(QjiDEhPG}&sfbon_oDyI;RkMl`{~j6Wr$PQp5OUnu zp_kO^4RzD#Rg>aH%etgcb8(0|Y9J?Qd*o@b>`jS`=Hw+}gO$Ux5Q;Huv2=uVsu^XE z>a5eV3pROCp0?tV%#y4fw1xgnVJT;h!5go;5RV?PXOhG!1cm*U=1J>EYkIsU-1C?O zdo_|YvBBvi{uc}2NqV4h-dO5YgZ!k5_bJ{T(=yo|f!{f)>#@mi=57UNuE2~29cx{^ z@~&mz>k=7+s`koW>Ie_tyCho=FB>#RU!pd*%C!fy-+X3TTi%PydNP8RUDS`MnKCsW zGv4U#7iy$noyR@2Ql9AOd9p>{;jBm|E}9+I-!aKQRb_h!)-xeDvUWIQoJ2w&Jyw(Y zp`WNaaxq(+Qkh_(aYWQ@loG{sWmIW$TurAe+-kKH>qI}9?m0Gy9|pWL5Bt_CI!F3G zjJ$7txVGyPb{-qOv(jQ>YPzo#v&{~OZs{0*3gHMHN#7bq&`(ya&lSE4S#E6qMc*3^ z5@=!UIV3=tCI+qY#kAt%zc5@@r#OAOFR=puNKPV41|GU?aRsM&Xmp0fC#k8KWGv;H=ijyzbgZp0tQ_jVKLb5k0W zuv(gD98W85e_*=OUFWfnc#yetH3Si>O%K%5H0o1RCp;Zyt$JUZE~2OzqD))B2OE!3 zGauldN~{zY@u>4_pt29QEA+c%jbD6?i!-v^9brXZXZKFoto;h2_z*d*u(#UWZ10f7 z=ec>W3cU>JcK;mT^)Tb|Hu1?q@uy83L)T(4WteIC?~e(~W{OfYCq*syz~(sA;_gps zjUQP^Bocl8C>DYa%8-!>2&3PQi7rwq+f>}%qSzsX#1wPzW;76{mEo=KH6^*7fBkyD z)QZq6NHF{0o?yT7Vv32Lujej!=5a~-65h!HC$pAA5&C<-cu43jRxZQHEv zSrS)4ZNEQf zn<-q~H)Hg=3NjW`o=P`0SbuDWsnIAf=8Sg3%e?`uMi6fS2@cBP6DCsTW?;>2|}o9T32RR+q)5^z40Z1~Z>JP*BIkkI|E zd_SXoZN=9XB|QwU09o8-SjcklPv|pFP_gUMv|VgW)8d>6l_d@;^GorgNY~#k%3`V5xN`CE?PwmDXt* zblkpR1buUcU+VDNvH#9T4?8=&{?WB|_i)HnsQGcMJ& zEw;XYT`Ghs{vp#%YQ0(5v+{7Anh*XR(7&L{78r+@+ujf7%(vc~Pvz#xOQi(4SK0n7 z>q*YF$rHQ6f?N4ZtzLPkk=5=zv55!rK@97r%{Q%d9bZ*nCjCm|M`c?0)8=*@>hb=n zSiQp@@2{5IKDljBC zfze$D756WC4@7$T6j@8Fg)BX1v|FKQJ=Y;+GVPg+@0i*x-l<_A<3_k>znvQwwF~Af z!-S86aPy1LPOgeH9xB)}74HJnRC`0w6;1S0Y857-=cQ?qwll~$y6rqTk0fQa(VzoG zn=bk)R8T34!kr9?j~-{RFH|n^{T}i^a=!@A5oZsp3Ay;tr#d_|E3(bO#4Y27U7GXd z+kxo^ayu3of}&IZTG<8Ph`o@ z&s&Ic&@qfwC?)>QnZ)>@b--wI5>X`b>RS}8NA|2lC(D)kdnN^jo8|n|>8w!llGpqv z$u4rz&>4-hxf6~P1A5zqA*bOlRSN6tp$-MB6vxrnV|prapVgc-=~{eX{cvJ8*@{K! zV@X3vk`QMcF)UC4HIHibZJ4R}dRbc`v@=#W4#g3z9h@F8pP=Wu9ahiM7C|BanxH-& z3rGv~YZ%Dmb01+_iZT0=`RdNaL0h`$MtPp+d$mV3%;UbdvATgAW1TX&NgUa^@>yIz zwadqJcw%KKkd)tx#xBF!g8){l%Bow=#R#gAVcdsWo|JqOy{W%++9ukEKhp=IRB^$p z1a!m4GZlQ29VsC7&`}P3A;HLBbDFfmt+6#ktOI+fxB#f-h8em9&kUQKO}?W!YSbtr6)Qx8Zn?bb(X`htqnV?aQY9jPxwzhP)BJwzpa#3az zYFeLqI&EANW)RBDDvE*p<}bfF+A(*q>GC@L#UX0Li!xh2?VBa19}`^c^G=IW@ncqi ziJZYD;O>yiuf3G-pM^yyGUvx~QE~Y33`q}uGgFYR9~6Scbd<_hHz?1=HC?_CIeb^Z zq~lCSFgd0vL(OD=S0}%5Z(e$#@}U5_m^%4uuoTLwe2^A4c%YF_m2FsR#JHKYtvUHq ztT47$TZQ1XtOngg#4BpP(dd?q!s=5TJD!dlE5mDVM#&!V%%8!mS_0k)*-F z)x8@L(}31>Sc$fXI1L{Yoz$_Nato@i|FB+Sg(hoo*Bcd!m$w@HoziV7p*zEBDMS2Q z1H4m-M%DvW*eXb1`%;An`nT<|T@wSFl);Xa#cDT~r1ciUT~8(o#$mQBk`Fu9FtnsO$ACP{7`jR>u_Ce)*R zbrBR{P3S5ttxSufEU8uqW3UF;J?Yuh)95fWlydLF%)1!%&bcsFV7a!nz?EUovUZmo+b50Q z4Na+|N7C))Px;Qwp0<3DIf=K^s_#uMc73pE>&tm)nQQ%Z4lJ~TFKKDCt=b#Kg-TFQ zzgEZawCc82$Qzp)K!1z8g0H@WV;9uL(7NWnz8;wuKG-}FBk3eVW^)5<6NP*rOx346 zPre`!$Sw-=(-=~X+{5^)7t@>_)yyb360}4a->&KEMlPsgdd*h&>Ux|yq z<4Wb=jE8}#=(b|To%fPWrAvOwkU-NN?anslWBReDAN&X{fyyu+r#PK!I^pEeBM>0=*}>vD~b3ePo%w9pA;O5vaG| zkJxV@m7bJ*VBt0NZB~Ql0(STXDtBgLS1sII2bf3qcn>@FhlD5l5yBM?+$p5jBUhzFNoQFHu=1uo?#u6kx+wN0lGM?DyT} z)nW450oqjA8J)%epPzJ+$`T$4)J;27dmACQWL@@z?RhVr^}<1W>WIL$UxJH6#rg)@ zRwWR=Bo0Mxp46nJQeA!YE`nSOL3+e6xQP*$5hU60JyMCTYhnnLJ@BAx@<#&_W12WZ z@AY2Y80(%f7gs-mUL@#Cmu&N5YDGy_g|BlCm%S~g0_J*D=lRfT`tVrTpL@kE#C1vB z)Tzx0ZszTlVaIBEm)>fwUv7+riPG!EZDnN?Ur(1`zZdPbt{jR^%fO8K_FQc(;Ix~V zn26yt;HkS??TX9(&G3;%YC7l@K$#2%Qmn|av40exnj7+lvp6daH3}YC{bFKPcmG=L z*)dmZP3j^ox?8?(ld?X_uSZL8Yrq;46uw#)R&J|-1LWvOXX83+)MOKg>vC0cR>A;a z$*VLV9GJ?{!t0#rShE99+(Wq8lz)z^!F5J>3M(XOIQpwG@7VE7#Tai@(;s*}f`5v_ z7C_;n$pA-!b78_W`X&YHWXqe@Nm$)Y=~Yvy)*rk+BU-fGzfLMRxff8}0e@$ya{e6h zJWn9~IfgOuu~iAg$f|H=2~-25q$`_uk^P;CBxv-17TtxoKJ zv4B79_BLOwFg}mY1!1EE$g9mHMtP(6Q{Gkb&w`5|Ays^7SGj~_f8Q_UV1s)H zTppIkJa1w(goLu6?lz(iXkP*LOSv!-S`y+o&wt;17_%Pt#F!{GC;uC%$x*Q$B@Fbr zQTWkznf>I=bgN4R=gU=ciD*o`wIw%?bv(IXF)9!IJD3!YAN}NLcFO+#_%yf$_!V`E$5o875!cmG?}F7UB9JR#HYE)~piy&WC?j6;?taY9Q<9EQWm*^uwm za}D>~znuDRzBKWz_cw8@#NHgS2^OH((85jLL~VqKdYGzXVZCMUpJAl(C)^8=bdCk@ zJ)CF!3O^wlOX_wZJzY&wFK7IH(ctSF>FAKhr2AYt{#;z04BpNex%maoo0?Mwr~X~) zwYsg?;{E#60Eq1cz)8XyZD>i_XF=fU=+mh5-xm$OzMLAL+}d9~AcggBWKsZ;jl=pHbczk<+X{NdFHG{u((D@NZv^u# z&olx8d~d-sBjPk4#h=srzECpU>qh&cr(0riN0*}SV_Db;1!E=j^3*Jhc@2@ z|jqD z?xwqZd$5>|P$0Uwp=&uqoE=*tNLLl;^^91!8zrW^3!G$KPR`6hv=XV?a(e8RGp8L3 zd$aPcp8Hak>D=+nbBSm+J|dNIrVvMh~XwW=8Fzet>(i%)A|bEvsjipVKI_C}{G z!iHN1U}I0_0DPw*`7Yd5pa=SO>Yx>q4LdvYrvfEgMzl5()Gu`0LROkB_n81Mi)wCmF^SOW_71<1w?MVJ%EO*#B*aZqSX1KC<6W5&kSglvWY+&45Y`_qGI%v6^rc z9T^F1kj;}PBv#vf0PMB&U4HxEiyEW7`Iqvb%t)lR>qHvHK93KuNq^*NBeEnrxvs*e zLaXrvZbiw7^3gNtM)|wRG8@IZ$R&@jj)b>P$NUaHZ-zhon&|DxB)XdAm)@k@l8&GO ziC`L}j5<*>eRtbi7!UlTKfd4oc}vwDQ(s3-l!bTLcL|jWvHsD$MU22=7;1pk6;(UL zi#1XZ;5w@L`Bxb6bamo*9<$X)(6`m^W^|4i_-Qn}QQs?8ySK7HQ^!HpUpEB%R?_EC z9c+g@b*9I#Om2#cBIC51x|_d7CO+-&W$vlR<)+?UD7=KoZA4|}@L4aEWWPLzu61zt z1{n2a<*r!m?OI89sNi8GONy)BGg3)!>_Mt#c@6&fr2<8{a?c5gw|Fnf@1nA%I|DaW^GDYk*dQ;HLpVJtc+2(;2m(ai6dogSTHGc0Y)*u}bO-9T7 zv>8#;+!GML)S-wQ=^mv#aXTON-}~&$uCZQpbD}viS3B+fx`@3WPxY4ucBxh#tNyboy@LOfshLnn zdMWm~R>;(EHp5L4Q6cPvIK3^=yiJ&a>Xjqaud(Ij5Tc!5(UxR;c9O@3Xqsshtn?Cx zCVH-t%XUI|~PF3ao`?F*d z;;fc*-H(bHg!uJ)ukK2UY`Y2%V=Va_lQU9w$>9S&yEZGGG&#WXR)@H;gv3hTEb}h> zwR{{lz2_57$N0F(O6}sh1|yJy!y-)Y-3gZ0PE`^-iw4D5yb1anJ!i7yFiYVGn9>D} zRoizsYromapWz9sCR~qHE*WFPK7_dZ20KJwbcHL94(gIT7WJ22IqNTZElH4N7R9;I z{WwUFtf--*x|!l;K{45GvCNGwW={UzmP{ThNdX_&s~z^1w<4LE6H~;V<@_}>!McC2 z{+}x0*J%IjzApG457j3I6Y^DPGR;g@s1b)cw^b`0gv~}TPxNhku4l=My-~;GDaY{GF}|E zL~~UIdFEWbgby zYO1QD;1B0+xjpMT;2)7U`3y3Im~hvQ{pJ=A&XEtk&*(q5*NZ8cMF)F(NBGpu2g&{8 zC~f8Qa;OwQqB(86Id?zLF1VC}R#7nRA zKB^-If~)E#8_Sgt-%3G7IC=Ks2S*|c{$lfDN`Qdb+l0yD=SyHxK;JL^A$`5ZkF3nB zrE&l;P0(%`ron;WeRMS?5Vr-H%Sni}&SJ-Hinz4xI|B{gWcL7{wwt3nw>a>uw~i7;ovo-Z}c8#a;je1;x73z8#7pJ2jq}yA+}3uk=F@%+#e= z<45z`#acNZR&NfdZC9vC#h>X1%WB^}+|ES`)wm$N#(io|fPou~nt>?b`o#>PfldzODjy%LDbv*<#&wS9U=EGF<({_eZL@ApW6 z`qzS~X|VcgnrqRaWUtJ0{0*cRImW7y5hk6hTV~o*=W3L%UOls1K*rFr>u9oRRbEYp z6tDO};PbbrR{+g`9O+T6^wZtvd*}mqJr&@^$7&_Q{j|-cW#t!s-!)$dyxd(lk%259 zn*M=8@*4RMrdp>L1!+$78=;9Mv020zDHr6d+vQzyhg47X9@VIm&Heo*KDCrWx4Z{? zi*s*kDCF~t^UTaG9O;iFo-fU>0tAa2UL8qJg#4g6qmRy+0Jg;vBw@3dbf1(El)v3n z7Rz!NL)_ntci$|RI*{{zn(5*!;fXv6NfRY9K*Nc&1#N%U`1FDFO?UA_Gb@>7b^#(z zOI+)hJ!U4I8_Od{g`Pi@E%-^NrX@bCCkC1eDdH-aa+2R(kMhM8tY70FP)F=yIVSEB z?Efl=UJWUz5mr-AVH==z6%qKDqL~MWL9p++EVmEJh}r#-MzcSH3rod-ro0vf%V}y8 zv#Zfw`8)~BFRGSByt&v=a(MkZh;%Hrwvq^!*M7${;5ah?47k0QX@yn^{lnq!&m4Bw zxYc`pax6~ti+flhq&1Mx{Ws48M-lp+ELe`~-SSuM2cq>v*iZnG1Da~J{)|v*Mi0LY` z=alR27b^-MU~Ue!moK!MS&D%{O{=BJH+jCkUth1i4=wZ4)LK+{64g<(eJG4Khz>aq z8mpr=U*tEJ+DHDQ!m7_M_NkIOe+%nc(fNf|o}(%!N$7K&3{&q0IPHC0N0Wx1Jm#aJ zXZP5?m^Z=sD>{qcDcd5pjL^13on@~opn6sb-3P^>+697tf7Wx4c0B7n+?Y6I7Ta8RPe;2b#IWja?l7Y#Xh*ELbcv= z@6x|IOf-B?muD}7ndadNvChRI-H;_{AuCn)8<6|1z~V$ywqy^bM|O8BA8v|>soNYX zeZC?{aoD0`Vl%oi7I|-&Fs;}v)hc8&QLKuey!LiLuwkC>U|67q(&?CmUTEB6ont&CZ2~sc>>N2spdp-7TUn$v!3Zu1gWRJr z5nbSe>%3K^RDW@Wnf}<)5Q>KEZdM$!2E~h3>BoTLfhnKKuV*L_tZFFCA4Hebq&vm` zs=baIA`@2n60rZ)Xvh~2TL9x}!lYcUZ`^gqM_9*d(jxf3SO8=33XO!54M{DBlM?Ih zNr89zjVYn@w_vHfTtaSf98u$Yl^iX7@iC`gdz>sqpIOYGz*1*>-4Cl>oJmY4TA)f! zI7jdXIC$iVm;_q&#Mp*r5NE82#Y&f=yT6oXjw;+FT|P7^n7J$E6!)dy2BhSa=S}Cr zASJ>~cQ7Q&BvRZ_u}M#XODI`Nb%q%>m#JyTKS(!Bg@2ZuktXTAF}hzabsI}`NSUf8 zCnW+SR68pa)i&I3y$~D0b6(Lg?%rh~l9p#CcdxM4njD1Jri!BJ4<2O~_YfE#tV3if z6PRc&*DbBAGZ#!Y;bcr11Jb#?8&|q`eSk_jp&KTCW?-Xg3HQl zP3iN3PkrG`sl{zH(yB*_Ky1^Y$`{GwwlzvOW;i&DG zQXQyLXqRZ#q`XxtiJ{TLK8@Nck1%qVUpkd+0Aq433At={>u9=qcT0^Lnu;ussi?9` zL^D{#4K`9}?*D9ey%wj-7*#P?!pZrha6?z1dC;wUqb{d60IH!WUfs1Kn5^D?k=WwC zx#9e(3GeX5G;X$bM${M`rnR#TJES=)GoKyq?MH=0%GSz_s6Z%V%6xv1%R2{sO9?D1 zt|I^RjFy@U@pmmIU*@f5=4u0t1B>wUqF*dGbAB@%;*QRft#7v7L6cB0OH?_I2nvZ#CTR1-!8C}p8EXg1P#S z&zT)^U7LFRA>)%GHw^6Iy#6&=d{Ne+Us;WK?g-_2Lxnl)snOTGt92e!4xqkTfjEaE zFFX!XTJf3O-coazZf}2b;_X>MF<|@MP0^3*K zE;!WDNYz;Tl+tPV{jL1E4T=&>Vk;W$QuW(`Tw!(WYpExED@{axmEhstx5l0mlZuvw zcP|T1PU&DNQi#IWa(Z#E%o3Dk$flf7lI^=q5(eHH$A*+@yOp*vtrxAZaD0)KjcjMdQTXlu5W+@sFF}$ASS(2R%+^x?9ps1GFrlyH&}N(k;>; z-O|z_At~LRLnGZNh%j^vUD6HG&3qSl-_QHLYdwFzYc1Ad_FS_&kMlT>O|M~)Bz>yx zJPs1;F~yy5^)x2INT4OJI{YgC`$*os(2Cw*^j?&tFp<0m6RMkC?%;44>)p(yX znnU1QvHbyB!00cymgy%bTXtD$DxIjd)C`%+M*w*4B86yRF{r+b^HXscRk+=9H7P_$ ztiZ8?W!P=TjjqDTCLc}T-rlgi}g1}(3tY(bmEnVO2oT9XqI$HRb zq4>r+wZ`jj!u=4Fd`7w}TEkp3gWO0Ot3fJW6~*{qddqod>bJLyYCduBAShmMJY2%t z1?d%SoRK2@&BOl14=Zf=X2pG3?LP(VgJ8j1Dy+_VerMsi+duVWJ_X77?(-brw9s8+ zvHAOq&fm=4b@QG}+Q0MPlDpzOO0NM26gNhad8o^N6*j&8jUUI&EGo#H~iX%s7#gEft$ zN%NS=p-Sndkw{-jVPD*Az4x!0gnZ|O>b8kW4Fmkqv9T%P`|x7e zpxCKL@$8}pUrSNr9>_zRCn!wI=SU9pV`Y{F;t5ab-C)^k7^IfC8fKMWDm|7*q`1{xyVU4;^jwgkAdY>SzP%xcRI#S!qHU}9uF>CLL zs`s28&{*#MzFv1Ph7|W>s!%Yx9KPLYN#?|xVwDuU91Hy+ss*V0nx=!QjoqWT^sTrQ1(mx}D=Y}s97AjUQatIw}=^`fS}Ba$7aKK3<= zAr4lV2}RB?t%;vo)tH9ggthD?(70Ux6t0Y;(YJ{al9Oz!>KBB|+kIA?1!_)m3!)Xa z3l}q$B2leisG0mQNBy`%{x8NiHPQ4_ujW;*I-izbJ|eZ|%SUo8#@IuH_l2W2r@InU|;Wa?dQ+YpxA^W z7YRgftwLsNSyF#a`Vg0;VgtcGWONTFXsA|PLM?oiA9>4rJi$@yyW zb?+yQ>!tgCWFKN3UMs)zG5Fk*Is|p>T!z%1Ngq-d%Q>(=IouDC{b!y2PE?=~3g=Uu zRkJgfLa{^9qDm}K`8D}z-{<+;H$oNv9Ev|!cNBbP7{3#+K(G>ij`Yw%FD5p!BVv~4 zSi=o)YvZ3I!?i2Iwc;HV7R{&3Eku`7D$Z5@3N8 zAYS^7r)%rJ9GS{fRDgj>P9*vPXUJ$gPn+3X_>S3({hSKwGlB2M8hJfc5c!8hg7p); zfOH?rr-_*t8R$=-%yn!b&9N66Sn1z`_kBqsTvO@N=65B7zQ@+vA@(~s7Yjd_T+?&Lq`4#Yb74J3q6!t}y zkgtk*fINX;^Mkbs&=IlBhMj-grSG(?9tHdVpy)U-xNs7@8ot{tezQG{f)XZ+tiC3y zt!?%WgWJ&5ayn4;%a?+B(#D>M-Ty&V_}o0w|lMcyN>Psr|(6szt-eSVnOhAM%bF>e&-EXt)10 zWp-*oNEdI=c#_Cxz6LEWXb1@E3Z@Q31oHDnagDzwAJg;CaBD)E9>>#`+^)syhD9P14rx(+dj$aTn%onb*bm1 z1&rL#dlKk7v;#{Mu&|Mm1fak52n~x|KnBA_t0Pz#7`z3IG#gV??ns}u0a`}~#do3v zHNn9xJw6|4SGT96j~EdjkU_pmuT(Tt2uHKUPz<6KV1TVXxrh|Jpu_*^7}9`Xf91tW(=UTEfg^BWO;hxf4$ZCW8L_!EgMxZ6!%7w5r#3`SE{ObSF)gyBTe)L7r3w(VE?e$${ zmj{6YLjKC{`Hg6hpCCdw(8wyP|HT48pw554&DP*i2o5p$GD*R>&LhJOeOm^g!Jr0CxldRg~a)ActV??Tct&)jPA7(ib`_vUr8rlL+j9XO)48`J+0mLU1=t zKN-st5ShG1J3;`PTQ+SmPu&2}#ksLTNFmyML88Nebu=JIp5Qsc&H=iMW)mN802tzc z3YeGSAK%d@*dA3h+F1CUK7Tro5A};ov1D}O3~~aj*g&jEeSXTyZVAGQ^Wr5ayajL~ zkq;@0&>d!2L6-lZn|}bq`8HsLhg<{Z^bu|#4`M$SQ)x5;(#8P!AfSmKs{RSUL4hki zZZn;ae+sG-k9-_}+~`30qDvN1e<@gkz{r1J%`OBa5<69c_y-#4(Lvk@Kn8&aN{3)7 z@}9xdnh?l9_C~hU!^rm|49Z4BQY<*&TrK1RUID5Pc$A%hw0@}EO>)`EJ`qv)H?3zt zS`>JPJP)s0fUKw6)z9)FMh&+n0e-+!cUI*E8FtIB-+n{bqqu1<+jFa{`$HAi>A{ zgU-Z&l*(_*Egx#dR`hSJ>^u}IV?fX1A&^<@Z=lsFkN*zDTgyv`{cNen{eS2sA`56} zr@-_&<3HiMkb92=@=JV(eECy!>Mx8}EisOQ4ap)4;{d||Sdy6KBW86{+72z|8vV(O zB}vvd4qH+`3bF578mTPyuk76>A&C6!%r}Y)4pxZVxp1kz$uBiV<;2MaC`8509fzi6 zwexXOAogssDb`zPg<5Z{pr|kQHlhLi1@D#Qf~R#c3zvJlG!#tJiHk^Yzs$3Q!1a+83-+OS>o5p5fxL`k>yC zqVoCr+8zre7A9*TOz)MT`93~X^W*hUS2PiQ5~)DpePWOvpdpzQdgx-2UCH?kqph0b z>?f4-|GJtLw^a!ArZ&>9NU9vYyl@k`@R)j8%ua~qbU}Vr>DdAdT9LVj?%>C^j-PP* z!rjg}p%PTlzmp8wuPwMuT@zpMLdtu%l2 zG(nQO2!@O0#zUS!2$+(#pbqUDLh{F0?%wuaT6e^`S3iCog+{x zZs`RiJIjeSxI62Ji+RuyOb^V2^C&p9SoK}Mabu!vU1hf6!GQ%BxHu1|mw3lA-}7?t zaXVFc*GNhH0n8J!uYGwF!g^n8^B~{bJpYI8LncC8miE+_C%fEzwGu;8ilzYPM@vS^ zZrcGJUJSwZ#wUjZWeJn)K7qN}TS2#JHvEu?%^>e7lD3oANk}9C93`^u$6kGlHNd35 z_i}u=Q{rOQ{BcbeAvT)mwU}ze93Y&`YUN!KJwT-}RA(~#ZQtW$@PhSpzusNl0AAU4 zuIF;#=KH>k5tc@+BZ@wjk+`yy1}8>3tM`dn&r4D#36sYf5Sa!OL3E@%@e}TOZ$9E; zm8`(@Z|ntPGGMV`y$I*KvLL95k1y}eSClLCnOCxSoD_ygec&~qD|uNBLG?36t^Sg8 zoW;!3%;G#9_LdKC}V62QUN|ayZWh4K^3wZ z3-kt}jWI=95^c2RQg5dkKbCXe@vOjkdGY|jq}6Sgzo`ssuC02UH-W8CL5(fv&eHC- z(wFGHMrUcIuU=D2ksxT5JLy=r%}oKnL^LpV-z)wol|n}YR3H@|n*rn;rqy)~?2P*$ z_#>bF4H9qN*s1e8OE=!e!J%6$%@5QCb$73wcE+kw2mpDGDCq!%1F`=8lo(o4ygeR3 zLGjZ3-utq!Y+(FHFvvpU(6yera!FzlZ^gLxA;lx#N(D6Zf9O~k9Od@O2D)MhltKvy zWS#wRZLR^qrw;k3L^^*0pDH;1jakGAFfb~^+Q;W3fCxPcs|ti!$vQtjYy(7W-=!1O znXMmuo~qgA&$sOfv?mQPaRAo2ic|9-Xa_I{BKb*i8KdZ2bCN$b9eGftb=JRLw%iwd zrsC@pGM+EgE3nejy0m!Wi~nVVPM2iR^V3?~a_L1C7O+tO8D&>Q67jM@0p-h76(yzN zX=P#YXi0DxG*ahjrsqC`PJE}^zo)bVoBRG93<%Ev8tFezE$D$b zrQFck=8NOZgaWeJA;2o^74i=8(f*=GYT9WP-*7fFqQ0jeJi z*G`v{(l(mYPNWR;D+z@x@W}6AZns4T14LJ_SGShC*NhigxXR(C(B`?2@Xd(*$@nX} zlP^nu7B04P^wY$i>1DtcCU*;=O`pt$Q7IB|qn}$43;C6Tczc>`9$O%+gr(A$Isyc;*XQ0oBF*w*-SL zFVYh+SDuS0-@B9Io`U@2v5P^{s$&j72dE>PZTT`xiD$9YFg2t{8ezb|Q4yDvp1#%h zXsgkW)lE2_pz_bO(vlh$focq6+Pkvr6BhiK9Nc^Z!RpT4aJ4tNSX<>>Ufm2wyd10Z zhCRbyuk0?@Qlt+S%{Bewg_@k~?zbRLEU4c$1s)0t>{6w4Qqi>PJ$fc5_!H0EYklNi z6>~7Ot8KHPd~@8DspvT>3~PyGhm9~!S8x-ho8Qq6ghkSm#z6QXugHr?z|066#( zN@##YoT{U2o1=%(&eCfbg?278{^&_>wjG*?bf|e5ok4}OM#XJ~g$NaWPy27ApryA? zIt?^3!!%c&#U`rU)YMV^m?TX&E^Axr6|2b+PUbM`{HcqcLkaJxETaC6xzA8J9)*p? z)XIj%eDSwZcS+;@uR8XPX+6?!N_iw@F5cn0?daxQ1`Up&yXE@a)H7dH!(dwk!5DP~ z1|ycNFjUNf7oW8UU23KsDq!s)dV>S2tL^Q70!cU=)0zZgXK_`eDSS!B;}nDFk#(9@ zUgC5)rZz|EC;x7`8w6_x75VUKtJpj)04C&gdA90a`R=Y}rg;n#rpsfWbtCXM>wg-w z?@TYG0Q*$eSf*Ga{Gr89+G1#Ft>ds#h%;g`zH4%1G;b!C{T_dC;5GPgL|?v<=4KCX zhE|6^QkO(rn#+eoA7V~bN5W12JuJ2K2mj%P+lM8O+r7|OWII^X4Lj$E(^#0k!q&Ru zORi?rlc8=@{DL0t?JLvQIO1BPl8{$2l}$wNPTgwR+3ALc`ytG>lez3C=QTtScc?Ec z_>~dqvZ|YTd9x#^<4`AgB}y~q4?{wabG$PB zmYUQYN2NZMUc2G&_G&|fLDpMLuEUy@XTpqps2e?$drggVU}CiyaOm2g-FSzkLp*{0 z>x(ZY2$`46CmeU{8&&V$xRLKxnq6~PvL8)VG`eK`lCLrvt2@x4jZ<*)ZIbPsXwsaf zh5+O1kHASr6la5?YYdaX#l2fx1*R6y($3|4zpFRNokOPADf(G~!=5}EC z-`$e5ZD22Lo`E*1gHUSFpHN0X!^w>1RI`j$+*22}{#Agc?Tf-1mVF?NxKN2zFa`)J zS?_(*pnrzC04H~!u&@g6iesF1uhyZxcW5Ddpw~7Obz-yoQ&gvbwk1mf(bR^zvN-yu zdB#?h85%m83^l=*r)5mqII+s3Tib5fO~LQ*eO=FQwXVBror{y(`&YWXZIfSy<&1TE zINV&u8u$pi6e(}q(IP_msWmGFj1A8BCuCgv)^u#z6iKo!r%wV0Vri5o9OWLjJ0%Ze z@i+;53Ssy zYgGlwZ_d8bfL{o(l+!IF6;x{8A2YcRVNuf@rgCC+(-9m@sWOUTt*_X4;i%S1b#1)f z;7E`oZ2Hc>x!Y?UTkA)JOwYSsFtJX?%*2&l95?^Os&Hp-^#p&}7P#-*rw+@ZI5&Co zcj;I0sbSY+#A=eIdjeVdcRib!arUk@b=jLv0*m$~oOM`H(~+}BpE-##$X)dSgwI76 zyn4US#Q}Xcw8_jg{Y#8T+&9Xm#Tug2Gc5R_+6-k(>F)HbdoDb$ospS_fzvMcM~_?* zSfDT_lz}`K7@vi<>j9#_BGc+D#+#^)bgN&Bs3k-_EZEtJvP&bShhEJS^@qy?+}_Aw z(g2RxP8#o@_Q|-vq@*}FYakQetCa_4WU zj(q=!31d5>7Q(`Z&ue6h=2#Ro1S)$7zLL`V@NNrSR5!WMIK0D!-#KT$VV+4Tj^gzF z{ef4O;T8}v{d>Ar6zFncHq(Dd>wY{);M)9178H{L7k_$cQ zzr^!im(XLF0i7>4so6*rZno;B-Ha=#H8^o-iEUff8RHV&qka~-| z-SyuqxB$8_(&d_Ltme(^#wKP0R(9pGiV^+lW;El(P44$(v~j_9Kb*I%%x>$XNv~Sy zvNGoAAOUyE+xh8z&(C}ZZWZE|1{JRJdVnKQm^e(v#=h<=EWL)0CPdML9ug|d;nt(K zp%>16Z}X9S|M$giKwS0KFgkO3bA#P4MLQc4S*DwF^WpSNTfL_BJ~bZU0xH_=J+~!N zCE8RYlTJZRzPpt(frB=dw(A;u{dvAkXdAw~kdniF8!AzUj(du_s1MB)AH5)NWwOud z*$j6`!DM9;EQDr21agGO*=Wzy^U#+EVDEEO!lO6+XDuyeH-IUBHX}9CLO+JRKW{F3_MqMXM@&8d>m?e zfrev7H;;*@Z*IJ~U0~(38rM;7H#JU_7rfq*w$C<~g$<~sRE?c4Z!c2HL!rDF1b@!5 zc((>cY&Y3$U_X&>HaX|ql|8c{q?H0m+20;P3*~S!x;z)>C+Up44L{!~=^v?_v&L;i zncr`*MONB3l-?vKO?^qTSR<9HyIhq%dTqm)Pl>wO=UY;HVp5<~Qds0I-`v8get~ae)~?bA-f~sYRtVPC{VZ1M;tM2TBf}PYTB*dJ?!du&{sxtXJ~(~zDi5) zrP|o9ek3(5=J6WXC(HJWJ`-!W<;d5w=JY-wUj0?x;&s{oV+r%EM+&>uLJ)5N+}ZJ6 zH&H#wA9Lv{;u`O%L#Jl`BPol?_L~MyPfMn&%Y~+09|Gv?Pmvvk@dE>C;&lGz@W@N= zC!9nLS#q2B&)u4wr$kf)uG&(f;BasIB)AW*Xz z^jvtUqzGTY1ymgSe;BRAQy#W24uE3?y<86#aiL=tM{!HDn@$Q9NzQ)jF8;52!;k_Y zM{S?=X)gYh6dQsDbB;6S`T6((*_BR-Qpk^6-rI&XyS;7{({twM=jed8?l$d%73Lg>;(CBtlwCcB1FShVN8W z4;NzHX-%qU%<)YYQAy~ddf5yF9j7RdR6{8^s!>7QPyVB)bH?u(jsEd9b4P4+n#^5> z-lD%drOT8ujd8aTL~G+Nuc<3~x3%%S-*l1i_1XVpF4ZJLjh7LSb} z2g3bZY(GF8zNd~P79~$Wwz&tx+1ZeE(SmayzLEedXO;do${7A!E|rkow-v*!-#hPE zi+zljDb{M;IYTZGZW%xk|29Il<28o)jZVyIEaL%vaBW6IBjz1!X85VSp%{-huk5vX zQ$qY=uCLz{&Q}7C_4y4+CpGm^h zcRs%Ba!dPvJkmf3O~bJAP?bA(IB$U^ONE^>A#04AkkO2Ks>;0&b+NEeaLI4Y>f1_&GpnjAo4eb&s8l^5>c#h|0Q5& zJF-v-{LyR5gN5y@z!qs;@PsXcx&$UHbVw0#MzS2y#ns*y>?gwo7%MRpa zFwA*{YBStZ!QBk!<}IZb{x?#1tTvV5I0S$x&lN7C|JK-6s>&S9t29&p4-_t6mPD9! zulj>9cXX?>uG5D0u=l-vgDPpb^wv7GL{*FPe})uD0n4UXybwSL9v*dKz^ASR?ccTp zvjhSk|DzD4j0t>XcyvQ}cv+bi_^2NE?;Ss2QZY^t-hW!q3I+Hm+xib&06t90#Zy^A^{frKm~xGiWxcr=26{S%nNQ24R=)w0@bre9&ahs;yGr*9dEEM| zvEoCT=Y9|JTN$`Fbe`*3n!q(dMh3GfDO{GySt5F|SGj+5#TfYKyC-j3?ibSc2STfI z<|p6LMUuUui_9jVvsCs@WtDX0d1rumOrddn>AqYz{S5j&$)_zfjVJ%9;!hfNX1Xrd zQ+_*N6Blja3CF)Pp~Tzo_kp%sI87KL%2F3)Ut7N&*a&d5<)*2b6P>uF@^XLZoKT*^ z#Z90w5p2s$_1@B6sbbDCxN*)HE2YF@h@YE9FTe_XQh>$vgc`j>sXH<}Oc}4NV(*R8U>B%B||5*%C0^*Yn&Tna~_v4EtGgG9E00d2= z96~TPbvRr7=4`4`e0RdU;+FMRThsw!V~a$-#|tam%udzNBBbD=Tjn zDnfnafwvXnXjL{W-{Y24W^_}qXm{?ImKJ2 zuh$C12OC^RZHfpY`I0zsFrr1b{-89XTsr{%D)a4mC-GLN$pk<^0PtrP9Jk zAy+%OK!k#Clg~f3xp4^*P%B&_=!cc$orf3$bFGh&4XV}eTbr5~BPdKJ+?S8vRKY+M z-PM@YJc?S`{P(sTALWVrvReno&Uc9x``O=e?`VIdc7PHL`iU5B{9o`(SfR9xL zM{~c!D`jDn0sdp}Z;)yh^TGr*E>*^ygV^L$5>%FxtK3(Wym@mf1df`OL_lz06a-V} zUx2$qdx3QORTUAOvSGzn9W8<@dWuA(r+u~=)V*V$%1LC6Gb9pz0q8yq@HUWOlPDOC z6k+CQ$kLfftDabfFS+m$a%;=^F$cbIM!MOh2dky ztHvAedHOae>!M>#3&jLfSH%Z`p~HKdkbM45P!PE12@6zk?PejK5hzJhgas$kRKBWA zDEfM9?6Q#Q%_aesU&ldVTY+@KuS5i^48ZiFcD86B69B$AFk6q|fO_hFScX-+1;*Ky>;0e9@mr6bL)qJ29e+W#4WQ=c{XaaoM^*z?@G z*8SRx&t$dsRu-PKlv$p?xatsm8I^JI8YspMNybU6$6=yHNq(c}X|2p@N4~9JR>s)X z+atjNra$#UxzCm6+^16994iM(2fscWCjt6Z*Zx4!phD#7DUzR~I{7y?%TKhc^jM(y zIX(nez3^Qhy*nhVS_2eDTtgW+%2q<(yINL2o;d&Qq!GBnF z*(A65xydUo8e-E$s?rB(YbhQ$Nl3M6k&&KM#Se@vvBwA0~ceKuVoxgYccZLbkx~Mk!1=oEyoigFWLNQo4Ml_rfnxY zt8jaRvLoz1#lrgL5oCt4v7J_f-ylj#fRyb-l?%S5umeM3azq4Xrj3CGf=1S*ecWjy zB2XgS+Qk_W?WC9NBY!1sT>W;M=_TJM@@*Qam_TcYMEY`UE$vDSco2(~Fqe>qzc5Ig zXah0Bxhv2xy&1!Nj!R`U>$Ig|J0}W*2X9C^g136G_U`1B1Z#k_tc(5=t3c@wyTM5X zFoGRpbAQ>0o!t&P1{$EFqXsZUo}nuf?$!d{6c?{MqLDy5zh)I)pXr~6C)u}2ir^y` z;nHK(+&aNi0NW^o_9E7W*K{VwKbMi`J|VNVK2`8?4(`A0Ms^OCV=$V1^=+Y@ZrSs- z+G1S&c9BpJ;oYwl3R${v--+7jb z+~*cF8aMxo1^l|pGH9Bhogr6~1TGSz@HhL&9|J2Y#MW7mRMlxJ`9r5D%o9#xw1a#a zSE;bW5VlkP2^q!2w#!!kC<}_It5JM6rF?)^(8qJ3Be40N2QGt(uRHdxbjFF{%^9Yj znM|xKxC(K>rD6+tMkc(-2~&*Xv?iJ+jPD~>ogvDN9o53dm;H0egY<6V5w!J!<@7uG zc~SeynEL_zedPA;I`wMHDSw~MNfNW6dpK{6R3&$O8pp5ZJ9W&Q%T>ZRGz+&VP=IOx zj?)zoNP!&|$W@?Mvq-ZGmbiavc@9pSMt$OvIreR{a2De!+(%_HYv!FW=YzzX{3vB# zJ#-gDq85o>OsimEpQk$=5b1HMJy35Md%C=0GHJWc47tpGArD!xGgz}`H^m@Y!g5oh zJkU&D|J})xDf zy68HM$EZ_^FMNahlOMAYJN@ujQsflbw7K1>Nn*cT~6`Xog zq@10&-geiLp`R?GjMsMa*v~t;PK=1y72YJy`D$L+F~(8lT~7q^yXSTWWgi4H$EJIR zD)Fu}rLDq4LUfOZU}b8o4P6KFJTj#;EfH zr{>BL5+l99>kbPe2w{Aa%Rzaj>eJm{`bC*+7e0vWjcm^`n?+#!9#L(+c(G25TxNl*!#(B34j{!U08x#@+Pm zTi3+3d7INNSNtrB`pgXOoW}j}O7&l4xjWv<=_u#8$1X)4B=j z_+SS$t?K|)F3XcKlER~&*2ZAZtW=3(HT<@qyU_$nL5-44xE<+J*ati&9jNN4v;&M+ zRG{nTSv_5I)~`ClzPBd$>@2B*n9)Kxxih zKi$@p`gIVC6`t+5!M-E-V4|`Z*ktwYHQGuS!{fB1y$L;h;~Cdb9Q%d|I{{pF8gi>d zB`wjECxYmVeMaGPmCuq;kT&1Xx}2StAD(OKrlfPdu!U5Q(Gg5>hY}Mx|6XOWE+)b_wE!eyWjY}GM;!EJ z9W}o532Zo>(V2m1H3B@Jr|wx&yKHEJDVqJ3S?4G)H@dsjuZsllrZX)TKh&r}BpVE| zE3uL$hU@iWvE1U{gk&GYebHoFg|0gvP+ban-%(y&DGm3oe2|%psz)hG7)W10dK))N z*6&X_U0v&b2D2?x>J~^;ZTP(v=w4B)%-bhca2U#dIN~)Jn#ISZWRl(T8pn+KxV_quB|jSP zD8k%63O;k_2(8D_9KW$ULAo{%50f@ApU)G<+qWwe_$X|WYj84hTntCZO6B-MMp2LX zq=l7FZ-&yPe5mx7^dCjqX)3MV`-PZDgA)cm$`5k%nhFlxHm+ABp#FsK;4Q~)c^_uk ziO4!M^+KnbJhDUYZ z|8N;VnhS>lUi%CiR^*5J=@!^?7cCrd(`Y37-z0w!Bg=?Wxi^~TIqB!-O{e3BNBq2L z{rY9DF?3P|W#c_IBP&1krlBFa{02*nT|% zMUt*PhpTRR7e~fLlx&e5fqF{JBjWpT>q0(Yc8$JKke}n3Qi}DvXG{3R)?pw|iJ=mQ zE|-=FmE^z~e?!*L7R-kldb&&&S5EeBnSy=V1(#T8B@SgHF|>SZmujLBtoCMSmOq+J zt771g8_E@AuC_2E%EBd8L~bzfd>_}kXOc>`5dY=T{YqGh0K;Uiu0FXN*~c-?-kTC! zz{FZ7S}30(2^+%NPQ%nyYCxg8jOSmDqrIDEa+otbxPRgs%fmAtNuR=`hO8n^wXqQz zn~qO1x*#mAvmQS9TboT%I^B@pCk{4GZHEy{L^D9T(3&z>m!rtH&0XycGqxvkwHx4I z!zT|bLd=P7hZW9GWYfn|v5qbXsJ6c?6}wt4OqC_=CZo%>X#sD&E*$$^!G(**iAP`Z ziO~;UOKv2GfJGy4D0;;Qr%3>7bV=zm^E|wJLAbTEyb|MmaY048X_j(cP@C2$#i+zC znD6=kD~B^UAQ_E&o_G}PEDMW%{;Xs&H}^zIC6Vz)ItW#R{u3Z#|2bPe!_>G(SJ-hs z0?Av`h_kXthh1e>U<#9EE(YZ*7%Q4i={=Myw7L2DLhG#jlF{#s0SDTHT$!=FVTkZB z&O&_JpmT4@tX2|YMw{5u=`?fQG|ER*-@{iYY&K1{Nk2;m`bVPl2J7O?g_|2feD)As zL=}q`zij-nlcF9EIM+r0a6sjGo`0b^ThCpRY2y$yVodM-IcaG*W{WP`*#=j2Szu;N zjD_U)FWfp*u>6a*5@~iM+^GQiRVYdG*zee9crQ5Vbr(9%!hck5;cB$viTFb;ft_IQ zHpk~?-tTub$Fs*KAqb(@FC8=bt(^z(@c7rL&dl5>*7bVegvoa}9 zf>J}obXzoHO-$TfYj`G=G(nhC&q<-;Sy0wL03oupq>bCk*JhS&RA&<7HGe^jt3`XS zbb6wv@-Tmrav7Ag0GEN}AHOY7^CB2<;*$_4jQ2!B;w5E)#C6TPeNg(EA17v+oA13X zgBz>uhvHxfVNmR@HWy+)d-d^Aa^olEJ{9d805o^de^>EkRvkr zq1Au@<0kJ{-SRq~@LZ@~Kkq?ypH$O%r~D8-EZ`k+P`>IKt7=nos&ysRSqrP(AQkD9 zPYPY+f}eKrVZ_@K1crlrE&VeKBWBA+>8UPV90C0spclxntG@CDhqJYd>GrZ^F~q zy>$~5>G(~eWX$<#5y^B@@5TK}wH3cZo%(33AW9>VTPufd=HTx-4pU>70-Pod{xH!h z3CzLev)JtXAb`A}pg zLh*}r_4bd02KZP6&(#ofD0B|Y2`zOA^9wmC?%gTV*$WnB zbY0CD8<=ml_=`YYE{;#uCv^6?wzG5%WK4fCHnLv@V!hkQ`sm7IR&OH@xO0j_Rk^Hg zvH$S>oy)#x1H^n_<~g=ze1#XD6kqm0EtlKTUx=K za0B4X1kYtKhA#E=+C4}TK?G4%&~sWcFsZVQ!D5bwkdsVX4Q~v)$T6uV zL^x4Pgyn2k?~U*zIK9145L>|!fo$Hpk>T_b$iaDv$6<=dj-YfnjfxCGU19zxF%>$M zi+{F{Ae_jPX!<-NzW2P|cN>FU^Y{oN#WzZsYLW$Q34m|PzaRiZWD{cLE9SK4aE7wR z1hU5EKcSwc@WmIiT09VlZ)ofz3V;kE^aBp%TU`R-tkgFIaLNqSAuLpf4K|P-?Se7g$+XV5%6ADvAf4V zbR&NN>C7Lpiojmr;BUZ7@lxHg6}S#Q^@}xvRZO;UW&XyxFLl0${WcjUfaC;~SGMD- z(!=|u24_bMZ5b|{_FrTS-`{-HJhg>a9vYHsV4*1*K67xXvXDo3vR-c|_RvnUt4omW zaRGi_EU8qqaOTKOY?2(VNkV5Cxt!g$da;rV@CRYwf@&cCfbz<~^Ia`*z?taCnQf%1 zb43+L+?zt+({(IMj$K(B!x368kneT~F!$zSLf?sqGD~e`Dvm@;ZvdaW}ohB^> z8&F>;Kz)TYa`|k`Sk}7@3fwgkDid0x`%ihE2sxK(D%tOL>(E(>(fm17+%7+?id09HWr@-p@IBt(AfL5(Eqs1v?zX89^$>^-AQf|Ogl z6qx=2M(5KEgN8E%LOWpj!j_h1VjwFLjcKH~T*<&^qA6hn1Y5#BH`Q+aHsj5erpvCdWsA@2dScFsV{h6C=DF9WV`>%kSs5uN#tuLe0GDo@1^r;!S&hF=_gLWoSH;c!7XLEw(k^xB^&$>D+!y@R?O;ZOwBT>uAY3UdYNd%paj)l1H zPN^ih{M9W976XE*`f-!yrg>#pgI@DLHYDetiQx@k)RbWXeyruID)IgpyC^r3&8Q_} zML?CZEFxm|$Xx8*;+Km3MYmc?F#ti7ZGB~P!#8Ui(dy3sM6mxcAAT5(DfcEPLB7{@ z_S-X)!AN7*Sl+ri@6&HL?@Pv~ko>goKJN6*1ga;!ctIrOo`v)hAa`GHuj-?tpV1zo z4iELjB|kTgm_ZO6TQ4YRbi!2zqX&?=ja{=ZfbG4YrTbPz-QCQiLpM*Xfhk0 zzTCnUE=k6EAW~}#5)teZj_^Q1AX1zk9Ma*3mRif9QheG^OOy2FoG%FK2AsI`H19~f1cN~8 zkmW|I5~PgVO`nT*gT-QJiYGJ@e*h|SI#AL4J(mKWS^~~;TVKs9@Nie%0S6=CAwz<$ z%ZzN7cGagd(Xr&P=m>4kMs0m5lsd~Z!z7p(-2O3+pXZi&P;;4uAehq6Q&OOLE^GZ%R&gjkz6gU|3h233b;kq z9BB5Bo9y+~&(l?2)nk1;el4CW%P&RNsKmW7F~FVVH>57#3_b~5?{t;jcaYSuS&npw zm-=VSPIK{aaF*DQPbY)e057tnn`*ksD!MzZ(;)s^q}<)CL8%Pr0gU7ELy2Ehv&bRxok1Wx@@HQ%eo#=&H7>97Ikv8=P5wW(rm zmD2yiW}uw%xT&Ce6`{bFlq?HUH@rO8X`7vm*qXHqJTM6fC08rlc^E`@>+MYUyLPh~ z1M_>gowSIO55^>*0vJ4_Qxj7X#L?etr8ZR80P=Gn25Y+y&>tnm1;r{i8)n|GYylEB zRb408P7zA#Vk@aYdGh(4f`SgOEwoT8?_#f9fY=7W z&aFN^5lz|Q0CYB0@I80F0+FjP#&#_e4ULGV{5YItCD{=N!u&+_%q|W`!mcSnb@+VO zkM4v(@c)lHg%%$gT{%wZddZGDOQ;vZ%*dGcRIFmL{fJBrlI-gx7z}(55@CK+bQBq6 z7^fT!#Y~9EE-I3~ON&WTx}zuT>H0?fF!lM(B0h4(ttRLFGxKMm9FH^GUqL{S)g<6x zSCX-zXzX%`#EPOdf))s2`O{t#n;tO`~ZZoEMaJ9{Ca3$QnbgqNxuC zbP-#?;R#3!u+wTiwS450)B&>x@C~7AP&$4-1o#gG{qSh~ws>nA_iscke*OJ7K-3-WZytC4-s{9w-pTaxpTrRV zCYE|^Lhw%r*545SSlxf$`8Oo@KkpF#eJ5l-;@@}v36Z|~&pY&g--(ph|2Osc24r=m zgZ>r3I<^2A7#w2tQdK@JITZ$Yq1hZyp4`R8s=6AU88m<&Ls9{`GG5LKdrK>z`&u5l z@^h87NurBKv0gn8CYma zTD)LKm_-%3-pSqVwOqDu@lcJQct509U0<%f*pxPa5zX=Nu{k|Jvw<_qtnGI$o?ST; z{*|knwWW;n{JC7ig|6ar11q?AX~ga6*9!%nDgB)l&h`^t>C!i&3mIQ30DpA`@Z0@d zoKfl3xZ_emNhm;pxWM)0s+SDF=q{ zGiuANWEKm~((Z_ef%ykw;C`^3CdQxO5|mxv(09SAGS-TM4lx$jc0*h|k}J|mDqdX

Jx{g% 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 MPFA#3_@np#0LzP#DF6Tf literal 0 HcmV?d00001 diff --git a/docs/images/summarization-tool-global-architecture.svg b/docs/images/summarization-tool-global-architecture.svg new file mode 100644 index 0000000..ed15fad --- /dev/null +++ b/docs/images/summarization-tool-global-architecture.svg @@ -0,0 +1,188 @@ + + AI Document Summarization Tool global architecture + Clear global architecture diagram for the AI Document Summarization Tool showing user entry points, frontend, API boundary, workflow services, data stores, deployment runtime, and external integrations. + + + + + + + + + + + + + + AI Document Summarization Tool + Global architecture for document ingestion, extraction, evaluation, collaboration, persistence, authentication, and operations. + + + + Product Flow + + Input Documents + + Questions, Prompts, Review + + Answers and Scores + + + + User Interface + React + Vite frontend + + Upload and Viewer + + Extraction UI + + Evaluation UI + + Session History + + Templates + + + + API and Security Boundary + FastAPI backend + + API Routers + + Auth Guard + + Middleware + + Better Auth Proxy + + + + Workflow Services + + + File + Management + + + Document Processing + Azure DI or Docling + + + Parsed Artifacts + Markdown, figures, tables, boxes + + + Entity Extraction + structured answers + + + Figure Summaries + vision model context + + + Paragraph Output + generated summaries + + + Evaluation + LLM-as-judge scoring + + + + Data Layer + + Azure Blob Storage + + PostgreSQL Database + + Telemetry and Logs + + + + Runtime Layer + + Docker Containers + + Blob Backend + + Azure Deployment + + + + Users and + Reviewers + + + Auth and + OAuth Providers + GitHub, Entra + + + AI Model + Endpoints + Azure OpenAI + Vertex/Gemini, vLLM + + + Operations + Prometheus, OTLP + Loki/Grafana + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Science-GPT Summarization Architecture + From ea1d75c8d62429ab2d96dcf94f92567752078796 Mon Sep 17 00:00:00 2001 From: zhong Date: Fri, 15 May 2026 01:10:27 -0400 Subject: [PATCH 03/12] Update documentation to include backend class reference and enhance existing documentation with additional links for better navigation. Added a new class reference appendix for detailed field-level insights on ORM models, Pydantic schemas, and service classes. --- README.md | 2 + docs/backend/README.md | 4 +- docs/backend/appendices/class-index.md | 2 +- docs/backend/appendices/class-reference.md | 2155 ++++++++++++++++++++ 4 files changed, 2161 insertions(+), 2 deletions(-) create mode 100644 docs/backend/appendices/class-reference.md diff --git a/README.md b/README.md index 661bde9..741ad5b 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,8 @@ SummarizationTool-dev/ ## 📚 Additional documentation - [Backend README](backend/README.md) — backend setup and processing details +- [Backend technical design docs](docs/backend/README.md) — backend architecture, workflows, diagrams, data models, schemas, and appendices +- [Backend class reference](docs/backend/appendices/class-reference.md) — field-level reference for backend ORM models, schemas, dataclasses, service attributes, and provider classes - [Migration guide](docs/migration-guide.md) — architecture migration and platform transition notes - [GitHub auth setup](docs/setup-github-auth.md) — Better Auth GitHub OAuth configuration - [Dockerize & deploy to Azure](docs/superpowers/plans/dockerize-and-deploy.md) — deployment architecture and implementation notes diff --git a/docs/backend/README.md b/docs/backend/README.md index 03c3f9c..20afbb6 100644 --- a/docs/backend/README.md +++ b/docs/backend/README.md @@ -127,6 +127,7 @@ The current architecture replaced an earlier Supabase-based implementation with - [API endpoint index](appendices/api-endpoint-index.md) — compact endpoint list by router. - [Class index](appendices/class-index.md) — backend classes, dataclasses, and schema classes by package. +- [Class reference](appendices/class-reference.md) — field-level reference for ORM models, Pydantic schemas, dataclasses, service attributes, and provider classes. - [Data-flow diagrams](appendices/data-flow-diagrams.md) — text diagrams for upload, processing, extraction, evaluation, and restore flows. - [Risks, assumptions, and testing](appendices/risks-assumptions-testing.md) — risks, assumptions, and recommended test coverage. @@ -171,7 +172,8 @@ When backend code changes, update the smallest relevant module document first, t Good update examples: -- Adding a new SQLAlchemy table: update [03-data-models.md](03-data-models.md) and [appendices/class-index.md](appendices/class-index.md). +- Adding a new SQLAlchemy table or class field: update [03-data-models.md](03-data-models.md), [appendices/class-index.md](appendices/class-index.md), and [appendices/class-reference.md](appendices/class-reference.md). - Adding a new route: update [02-api-surface.md](02-api-surface.md) and [appendices/api-endpoint-index.md](appendices/api-endpoint-index.md). +- Adding or changing a Pydantic schema field: update [04-schemas.md](04-schemas.md) and [appendices/class-reference.md](appendices/class-reference.md). - Adding a new model provider: update [06-llm-layer.md](06-llm-layer.md), [11-auth-security-observability.md](11-auth-security-observability.md) if new secrets are needed, and the risk/testing appendix. - Changing extraction result shape: update [04-schemas.md](04-schemas.md), [07-extraction-flow.md](07-extraction-flow.md), and data-flow diagrams. diff --git a/docs/backend/appendices/class-index.md b/docs/backend/appendices/class-index.md index 50236ae..c7634bb 100644 --- a/docs/backend/appendices/class-index.md +++ b/docs/backend/appendices/class-index.md @@ -1,6 +1,6 @@ # Backend Class Index -Compact index of backend classes, dataclasses, and schema classes. For detailed behavior, follow the linked module docs from [../README.md](../README.md). +Compact index of backend classes, dataclasses, and schema classes. For field-level details, see the generated [class reference](class-reference.md). For detailed behavior, follow the linked module docs from [../README.md](../README.md). ## Core app/config/auth diff --git a/docs/backend/appendices/class-reference.md b/docs/backend/appendices/class-reference.md new file mode 100644 index 0000000..a81fa63 --- /dev/null +++ b/docs/backend/appendices/class-reference.md @@ -0,0 +1,2155 @@ +# Backend Class Reference + +This appendix is a field-oriented reference for classes defined under `backend/`. It complements the compact [class index](class-index.md) and the design documents in the parent folder. + +How to read this document: + +- **ORM models** list SQLAlchemy columns as they are defined in code. The database-focused explanation lives in [../03-data-models.md](../03-data-models.md). +- **Pydantic and API schemas** list request/response fields, types, and defaults. These are the HTTP-facing contracts. +- **Dataclasses and runtime state** list in-memory job, telemetry, and guard fields. +- **Service/provider classes** usually do not expose schema fields, so this reference lists constructor-created instance attributes and public methods. +- **Scripts and utilities** are included because they are defined under `backend/`, but most are developer tooling rather than production request-path classes. +- Fields are direct fields declared on that class. If a class inherits from another schema, inherited fields are documented on the base class entry. + +This file is generated from source-level class definitions and should be updated when fields, schemas, dataclasses, or service constructors change. + +## Summary + +| Category | Class count | +| --- | --- | +| ORM models | 18 | +| Pydantic and API schemas | 66 | +| Dataclasses and runtime state | 12 | +| Enums | 1 | +| Service and provider classes | 34 | +| Utilities | 1 | +| Scripts and developer tools | 7 | +| Other backend classes | 1 | + +## ORM models + +### `AppSession` + +**File:** `backend/models/app_session.py` +**Base classes:** `Base` +**Purpose:** An extraction workflow session. Users create sessions to track + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `name` | `Text` | `default="Untitled Session"` | SQLAlchemy column | +| `status` | `Text` | `default="in_progress"` | SQLAlchemy column | +| `last_step` | `Text` | `default="upload"` | SQLAlchemy column | +| `configuration` | `JSONB` | `default=dict` | SQLAlchemy column | +| `evaluation_config` | `JSONB` | `default=dict` | SQLAlchemy column | +| `files_config` | `JSONB` | `default=dict` | SQLAlchemy column | +| `total_cost` | `Float` | `default=0.0` | SQLAlchemy column | +| `total_latency` | `Float` | `default=0.0` | SQLAlchemy column | +| `total_calls` | `Integer` | `default=0` | SQLAlchemy column | +| `shared_with_group_id` | `UUID(as_uuid=True)` | `ForeignKey("groups.id", ondelete="SET NULL"), nullable=True` | SQLAlchemy column | +| `shared_by` | `String(36)` | `ForeignKey("user.id"), nullable=True` | SQLAlchemy column | +| `shared_at` | `DateTime` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `Document` + +**File:** `backend/models/document.py` +**Base classes:** `Base` +**Purpose:** A document within an extraction session. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `session_id` | `UUID(as_uuid=True)` | `ForeignKey("app_sessions.id", ondelete="CASCADE"), nullable=True` | SQLAlchemy column | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `file_hash` | `Text` | `nullable=False` | SQLAlchemy column | +| `filename` | `Text` | `nullable=False` | SQLAlchemy column | +| `file_path` | `Text` | `nullable=True` | SQLAlchemy column | +| `study_type` | `Text` | `nullable=True` | SQLAlchemy column | +| `processor_used` | `Text` | `nullable=True` | SQLAlchemy column | +| `processing_status` | `Text` | `default="pending"` | SQLAlchemy column | +| `processing_error` | `Text` | `nullable=True` | SQLAlchemy column | +| `extracted_text_path` | `Text` | `nullable=True` | SQLAlchemy column | +| `processed_at` | `DateTime` | `nullable=True` | SQLAlchemy column | +| `parse_cost` | `Float` | `nullable=True` | SQLAlchemy column | +| `page_count` | `Integer` | `nullable=True` | SQLAlchemy column | +| `parse_duration_seconds` | `Float` | `nullable=True` | SQLAlchemy column | +| `figure_count` | `Integer` | `nullable=True` | SQLAlchemy column | +| `table_count` | `Integer` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `EvalJobRecord` + +**File:** `backend/models/eval_job.py` +**Base classes:** `Base` +**Purpose:** Persisted snapshot of an EvalJob's status and results. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `job_id` | `Text` | `primary_key=True` | SQLAlchemy column | +| `session_id` | `Text` | `nullable=True` | SQLAlchemy column | +| `user_id` | `Text` | `nullable=True` | SQLAlchemy column | +| `status` | `Text` | `nullable=False, default="pending"` | SQLAlchemy column | +| `progress` | `Integer` | `nullable=False, default=0` | SQLAlchemy column | +| `total` | `Integer` | `nullable=False, default=0` | SQLAlchemy column | +| `results` | `JSONB` | `nullable=True, default=list` | SQLAlchemy column | +| `errors` | `JSONB` | `nullable=True, default=list` | SQLAlchemy column | +| `error` | `Text` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime(timezone=True)` | `nullable=True` | SQLAlchemy column | +| `completed_at` | `DateTime(timezone=True)` | `nullable=True` | SQLAlchemy column | + +### `EvaluationResult` + +**File:** `backend/models/evaluation.py` +**Base classes:** `Base` +**Purpose:** An evaluation score for an extraction result. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `extraction_result_id` | `UUID(as_uuid=True)` | `ForeignKey("extraction_results.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `metric` | `Text` | `nullable=False` | SQLAlchemy column | +| `score` | `Float` | `nullable=True` | SQLAlchemy column | +| `reasoning` | `Text` | `nullable=True` | SQLAlchemy column | +| `judge_model` | `Text` | `nullable=True` | SQLAlchemy column | +| `human_score` | `Float` | `nullable=True` | SQLAlchemy column | +| `ground_truth` | `Text` | `nullable=True` | SQLAlchemy column | +| `evaluation_cost` | `Float` | `nullable=True` | SQLAlchemy column | +| `evaluation_time` | `Float` | `nullable=True` | SQLAlchemy column | +| `evaluated_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `ExtractionResult` + +**File:** `backend/models/extraction.py` +**Base classes:** `Base` +**Purpose:** An extraction result for a specific entity from a specific model. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `session_id` | `UUID(as_uuid=True)` | `ForeignKey("app_sessions.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `document_id` | `UUID(as_uuid=True)` | `ForeignKey("documents.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `entity_name` | `Text` | `nullable=False` | SQLAlchemy column | +| `model_id` | `Text` | `nullable=False` | SQLAlchemy column | +| `extracted_text` | `Text` | `nullable=True` | SQLAlchemy column | +| `bbox_references` | `JSONB` | `nullable=True` | SQLAlchemy column | +| `status` | `Text` | `default="pending"` | SQLAlchemy column | +| `error_message` | `Text` | `nullable=True` | SQLAlchemy column | +| `extracted_at` | `DateTime` | `nullable=True` | SQLAlchemy column | +| `prompt_tokens` | `Integer` | `nullable=True` | SQLAlchemy column | +| `completion_tokens` | `Integer` | `nullable=True` | SQLAlchemy column | +| `duration_ms` | `Integer` | `nullable=True` | SQLAlchemy column | +| `cost` | `Float` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `Group` + +**File:** `backend/models/group.py` +**Base classes:** `Base` +**Purpose:** A user group for sharing sessions and templates. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `name` | `Text` | `nullable=False` | SQLAlchemy column | +| `description` | `Text` | `nullable=True` | SQLAlchemy column | +| `created_by` | `String(36)` | `ForeignKey("user.id"), nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `UserGroup` + +**File:** `backend/models/group.py` +**Base classes:** `Base` +**Purpose:** User-group membership with roles. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), primary_key=True` | SQLAlchemy column | +| `group_id` | `UUID(as_uuid=True)` | `ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True` | SQLAlchemy column | +| `role` | `Text` | `default="member"` | SQLAlchemy column | +| `joined_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | + +### `UserPreferences` + +**File:** `backend/models/preferences.py` +**Base classes:** `Base` +**Purpose:** User preferences for default models, temperature, etc. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=False, unique=True` | SQLAlchemy column | +| `default_models` | `JSONB` | `default=list` | SQLAlchemy column | +| `default_temperature` | `Float` | `default=0.0` | SQLAlchemy column | +| `settings` | `JSONB` | `default=dict` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `LoginHistory` + +**File:** `backend/models/preferences.py` +**Base classes:** `Base` +**Purpose:** Login history for audit trail. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `ip_address` | `Text` | `nullable=True` | SQLAlchemy column | +| `user_agent` | `Text` | `nullable=True` | SQLAlchemy column | +| `login_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | + +### `UserPromptTemplate` + +**File:** `backend/models/preferences.py` +**Base classes:** `Base` +**Purpose:** Legacy user-scoped prompt templates (simple key-value). + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `name` | `Text` | `nullable=False` | SQLAlchemy column | +| `entity_name` | `Text` | `nullable=False` | SQLAlchemy column | +| `prompt_content` | `Text` | `nullable=False` | SQLAlchemy column | +| `study_type` | `Text` | `nullable=True` | SQLAlchemy column | +| `system_prompt` | `Text` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `TemplateFolder` + +**File:** `backend/models/template.py` +**Base classes:** `Base` +**Purpose:** Folder for organising prompt templates hierarchically. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `name` | `Text` | `nullable=False` | SQLAlchemy column | +| `scope` | `Text` | `nullable=False, default="user"` | SQLAlchemy column | +| `owner_user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=True` | SQLAlchemy column | +| `owner_group_id` | `UUID(as_uuid=True)` | `ForeignKey("groups.id", ondelete="CASCADE"), nullable=True` | SQLAlchemy column | +| `parent_id` | `UUID(as_uuid=True)` | `ForeignKey("template_folders.id", ondelete="CASCADE"), nullable=True` | SQLAlchemy column | +| `created_by` | `String(36)` | `ForeignKey("user.id"), nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `PromptTemplate` + +**File:** `backend/models/template.py` +**Base classes:** `Base` +**Purpose:** A prompt template for entity extraction. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `name` | `Text` | `nullable=False` | SQLAlchemy column | +| `description` | `Text` | `nullable=True` | SQLAlchemy column | +| `study_type` | `Text` | `nullable=True` | SQLAlchemy column | +| `scope` | `Text` | `nullable=False, default="user"` | SQLAlchemy column | +| `owner_user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=True` | SQLAlchemy column | +| `owner_group_id` | `UUID(as_uuid=True)` | `ForeignKey("groups.id", ondelete="CASCADE"), nullable=True` | SQLAlchemy column | +| `system_prompt` | `Text` | `nullable=True` | SQLAlchemy column | +| `entities` | `JSONB` | `nullable=False, default=list` | SQLAlchemy column | +| `summary_prompt` | `Text` | `nullable=True` | SQLAlchemy column | +| `variables` | `JSONB` | `default=list` | SQLAlchemy column | +| `is_immutable` | `Boolean` | `default=False` | SQLAlchemy column | +| `tags` | `ARRAY(Text)` | `default=list` | SQLAlchemy column | +| `is_default` | `Boolean` | `default=False` | SQLAlchemy column | +| `version` | `Integer` | `default=1` | SQLAlchemy column | +| `folder_id` | `UUID(as_uuid=True)` | `nullable=True` | SQLAlchemy column | +| `created_by` | `String(36)` | `ForeignKey("user.id"), nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow` | SQLAlchemy column | + +### `TemplateVersion` + +**File:** `backend/models/template.py` +**Base classes:** `Base` +**Purpose:** Version history for prompt templates. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `template_id` | `UUID(as_uuid=True)` | `ForeignKey("prompt_templates.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `version` | `Integer` | `nullable=False` | SQLAlchemy column | +| `system_prompt` | `Text` | `nullable=True` | SQLAlchemy column | +| `entities` | `JSONB` | `nullable=False` | SQLAlchemy column | +| `summary_prompt` | `Text` | `nullable=True` | SQLAlchemy column | +| `variables` | `JSONB` | `nullable=True` | SQLAlchemy column | +| `changed_by` | `String(36)` | `ForeignKey("user.id"), nullable=True` | SQLAlchemy column | +| `change_summary` | `Text` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | + +### `TemplatePermission` + +**File:** `backend/models/template.py` +**Base classes:** `Base` +**Purpose:** Per-user permission overrides for templates. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `UUID(as_uuid=True)` | `primary_key=True, default=uuid.uuid4` | SQLAlchemy column | +| `template_id` | `UUID(as_uuid=True)` | `ForeignKey("prompt_templates.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `user_id` | `String(36)` | `ForeignKey("user.id", ondelete="CASCADE"), nullable=False` | SQLAlchemy column | +| `can_read` | `Boolean` | `default=True` | SQLAlchemy column | +| `can_write` | `Boolean` | `default=False` | SQLAlchemy column | +| `granted_by` | `String(36)` | `ForeignKey("user.id"), nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow` | SQLAlchemy column | + +### `User` + +**File:** `backend/models/user.py` +**Base classes:** `Base` +**Purpose:** Better Auth 'user' table. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `String(36)` | `primary_key=True` | SQLAlchemy column | +| `name` | `Text` | `nullable=False` | SQLAlchemy column | +| `email` | `Text` | `nullable=False, unique=True` | SQLAlchemy column | +| `email_verified` | `Boolean` | `default=False, name="emailVerified"` | SQLAlchemy column | +| `image` | `Text` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow, name="createdAt"` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow, name="updatedAt"` | SQLAlchemy column | +| `role` | `Text` | `default="user"` | SQLAlchemy column | +| `is_admin` | `Boolean` | `default=False` | SQLAlchemy column | + +### `AuthSession` + +**File:** `backend/models/user.py` +**Base classes:** `Base` +**Purpose:** Better Auth 'session' table. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `String(36)` | `primary_key=True` | SQLAlchemy column | +| `expires_at` | `DateTime` | `nullable=False, name="expiresAt"` | SQLAlchemy column | +| `token` | `Text` | `nullable=False, unique=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow, name="createdAt"` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow, name="updatedAt"` | SQLAlchemy column | +| `ip_address` | `Text` | `nullable=True, name="ipAddress"` | SQLAlchemy column | +| `user_agent` | `Text` | `nullable=True, name="userAgent"` | SQLAlchemy column | +| `user_id` | `String(36)` | `nullable=False, name="userId"` | SQLAlchemy column | + +### `Account` + +**File:** `backend/models/user.py` +**Base classes:** `Base` +**Purpose:** Better Auth 'account' table. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `String(36)` | `primary_key=True` | SQLAlchemy column | +| `account_id` | `Text` | `nullable=False, name="accountId"` | SQLAlchemy column | +| `provider_id` | `Text` | `nullable=False, name="providerId"` | SQLAlchemy column | +| `user_id` | `String(36)` | `nullable=False, name="userId"` | SQLAlchemy column | +| `access_token` | `Text` | `nullable=True, name="accessToken"` | SQLAlchemy column | +| `refresh_token` | `Text` | `nullable=True, name="refreshToken"` | SQLAlchemy column | +| `id_token` | `Text` | `nullable=True, name="idToken"` | SQLAlchemy column | +| `access_token_expires_at` | `DateTime` | `nullable=True, name="accessTokenExpiresAt"` | SQLAlchemy column | +| `refresh_token_expires_at` | `DateTime` | `nullable=True, name="refreshTokenExpiresAt"` | SQLAlchemy column | +| `scope` | `Text` | `nullable=True` | SQLAlchemy column | +| `password` | `Text` | `nullable=True` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow, name="createdAt"` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow, name="updatedAt"` | SQLAlchemy column | + +### `Verification` + +**File:** `backend/models/user.py` +**Base classes:** `Base` +**Purpose:** Better Auth 'verification' table. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `String(36)` | `primary_key=True` | SQLAlchemy column | +| `identifier` | `Text` | `nullable=False` | SQLAlchemy column | +| `value` | `Text` | `nullable=False` | SQLAlchemy column | +| `expires_at` | `DateTime` | `nullable=False, name="expiresAt"` | SQLAlchemy column | +| `created_at` | `DateTime` | `default=datetime.utcnow, name="createdAt"` | SQLAlchemy column | +| `updated_at` | `DateTime` | `default=datetime.utcnow, onupdate=datetime.utcnow, name="updatedAt"` | SQLAlchemy column | + +## Pydantic and API schemas + +### `ChatQueryRequest` + +**File:** `backend/api/chat/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `query` | `str` | | declared field | +| `document_markdown` | `Optional[str]` | `None` | declared field | +| `model_type` | `str` | | declared field | +| `model_id` | `Optional[str]` | `None` | declared field | +| `deployment` | `Optional[str]` | `None` | declared field | +| `api_version` | `Optional[str]` | `None` | declared field | + +### `EvalTaskRequest` + +**File:** `backend/api/evaluations/jobs.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | | declared field | +| `source_model` | `str` | | declared field | +| `actual_output` | `str` | | declared field | +| `extraction_prompt` | `str` | | declared field | +| `expected_output` | `Optional[str]` | `None` | declared field | +| `file_hash` | `Optional[str]` | `None` | declared field | +| `file_id` | `Optional[str]` | `None` | declared field | + +### `ProviderConfigRequest` + +**File:** `backend/api/evaluations/jobs.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `provider_id` | `str` | `Field(..., description="e.g. 'azure-gpt4o'")` | Pydantic Field | +| `provider` | `str` | `Field(..., description="'azure_openai' \| 'vertex_ai' \| 'anthropic'")` | Pydantic Field | +| `model_name` | `Optional[str]` | `None` | declared field | +| `deployment` | `Optional[str]` | `None` | declared field | +| `endpoint` | `Optional[str]` | `None` | declared field | +| `api_key` | `Optional[str]` | `None` | declared field | + +### `SubmitJobRequest` + +**File:** `backend/api/evaluations/jobs.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `session_id` | `str` | | declared field | +| `tasks` | `List[EvalTaskRequest]` | | declared field | +| `providers` | `List[ProviderConfigRequest]` | | declared field | +| `metrics` | `List[str]` | `Field( default=["correctness", "completeness", "relevance", "safety"] )` | Pydantic Field | +| `custom_evaluation_steps` | `Optional[Dict[str, List[str]]]` | `None` | declared field | +| `threshold` | `float` | `Field(default=0.7, ge=0.0, le=1.0)` | Pydantic Field | + +### `FileUploadResponse` + +**File:** `backend/api/files/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `success` | `bool` | | declared field | +| `file_hash` | `str` | | declared field | +| `original_filename` | `str` | | declared field | +| `file_size` | `int` | | declared field | +| `is_new` | `bool` | | declared field | +| `deduplicated` | `bool` | | declared field | +| `processed` | `dict` | `{}` | declared field | + +### `UserFileInfo` + +**File:** `backend/api/files/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `file_hash` | `str` | | declared field | +| `original_filename` | `str` | | declared field | +| `file_size` | `int` | | declared field | +| `mime_type` | `str` | | declared field | +| `created_at` | `str` | | declared field | +| `processed` | `dict` | `{}` | declared field | + +### `CreateGroupRequest` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `description` | `Optional[str]` | `None` | declared field | + +### `UpdateGroupRequest` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `Optional[str]` | `None` | declared field | +| `description` | `Optional[str]` | `None` | declared field | + +### `AddMemberRequest` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `str` | | declared field | +| `role` | `str` | `"member"` | declared field | + +### `UpdateMemberRoleRequest` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `role` | `str` | | declared field | + +### `GroupResponse` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `str` | | declared field | +| `name` | `str` | | declared field | +| `description` | `Optional[str]` | | declared field | +| `created_by` | `Optional[str]` | | declared field | +| `created_at` | `str` | | declared field | +| `updated_at` | `str` | | declared field | +| `user_role` | `Optional[str]` | `None` | declared field | +| `member_count` | `Optional[int]` | `None` | declared field | + +### `MemberResponse` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `str` | | declared field | +| `role` | `str` | | declared field | +| `joined_at` | `str` | | declared field | +| `display_name` | `Optional[str]` | `None` | declared field | +| `email` | `Optional[str]` | `None` | declared field | +| `avatar_url` | `Optional[str]` | `None` | declared field | + +### `GroupDetailResponse` + +**File:** `backend/api/groups/router.py` +**Base classes:** `GroupResponse` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `members` | `List[MemberResponse]` | `[]` | declared field | + +### `UserSearchResult` + +**File:** `backend/api/groups/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `str` | | declared field | +| `display_name` | `Optional[str]` | `None` | declared field | +| `email` | `Optional[str]` | `None` | declared field | +| `avatar_url` | `Optional[str]` | `None` | declared field | + +### `ParagraphEvalGenerateRequest` + +**File:** `backend/api/paragraph_evaluation.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `session_id` | `str` | | declared field | +| `file_hash` | `str` | | declared field | +| `user_id` | `Optional[str]` | `None` | declared field | +| `entity_order` | `Optional[List[str]]` | `None` | declared field | + +### `ParagraphGenerationRequest` + +**File:** `backend/api/paragraphgenerator.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entities` | `List[Dict]` | | declared field | +| `summary_prompt` | `str` | | declared field | +| `session_id` | `Optional[str]` | `None` | declared field | +| `file_hash` | `Optional[str]` | `None` | declared field | +| `system_prompt` | `Optional[str]` | `None` | declared field | +| `model_type` | `Optional[str]` | `"azure"` | declared field | +| `model_id` | `Optional[str]` | `None` | declared field | +| `deployment` | `Optional[str]` | `None` | declared field | +| `api_version` | `Optional[str]` | `None` | declared field | +| `azure_endpoint` | `Optional[str]` | `None` | declared field | +| `azure_api_key` | `Optional[str]` | `None` | declared field | +| `gemini_api_key` | `Optional[str]` | `None` | declared field | +| `gemini_project_id` | `Optional[str]` | `None` | declared field | +| `gemini_location` | `Optional[str]` | `None` | declared field | +| `max_tokens` | `int` | `8048` | declared field | +| `temperature` | `Optional[float]` | `None` | declared field | + +### `BatchMetricsRequest` + +**File:** `backend/api/server/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `session_id` | `str` | | declared field | +| `batch_number` | `int` | | declared field | +| `batch_latency` | `float` | | declared field | +| `document_count` | `int` | | declared field | + +### `ShareSessionRequest` + +**File:** `backend/api/sessions/router.py` +**Base classes:** `BaseModel` +**Purpose:** Request to share a session with a group + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `group_id` | `str` | | declared field | + +### `EntityModel` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `prompt` | `str` | | declared field | + +### `VariableModel` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `description` | `Optional[str]` | `None` | declared field | +| `default` | `Optional[str]` | `None` | declared field | + +### `CreateTemplateRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `entities` | `List[EntityModel]` | | declared field | +| `scope` | `str` | `"user"` | declared field | +| `owner_group_id` | `Optional[str]` | `None` | declared field | +| `description` | `Optional[str]` | `None` | declared field | +| `study_type` | `Optional[str]` | `None` | declared field | +| `system_prompt` | `Optional[str]` | `None` | declared field | +| `summary_prompt` | `Optional[str]` | `None` | declared field | +| `variables` | `Optional[List[VariableModel]]` | `None` | declared field | +| `tags` | `Optional[List[str]]` | `None` | declared field | +| `is_immutable` | `bool` | `False` | declared field | +| `folder_id` | `Optional[str]` | `None` | declared field | + +### `UpdateTemplateRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `Optional[str]` | `None` | declared field | +| `description` | `Optional[str]` | `None` | declared field | +| `study_type` | `Optional[str]` | `None` | declared field | +| `system_prompt` | `Optional[str]` | `None` | declared field | +| `entities` | `Optional[List[EntityModel]]` | `None` | declared field | +| `summary_prompt` | `Optional[str]` | `None` | declared field | +| `variables` | `Optional[List[VariableModel]]` | `None` | declared field | +| `tags` | `Optional[List[str]]` | `None` | declared field | +| `is_immutable` | `Optional[bool]` | `None` | declared field | +| `change_summary` | `Optional[str]` | `None` | declared field | +| `folder_id` | `Optional[str]` | `None` | declared field | + +### `SetImmutableRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `is_immutable` | `bool` | | declared field | + +### `SetPermissionRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `str` | | declared field | +| `can_read` | `bool` | `True` | declared field | +| `can_write` | `bool` | `False` | declared field | + +### `ForkTemplateRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `new_name` | `Optional[str]` | `None` | declared field | + +### `ChangeScopeRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `new_scope` | `str` | | declared field | +| `owner_group_id` | `Optional[str]` | `None` | declared field | + +### `CreateFolderRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `scope` | `str` | | declared field | +| `parent_id` | `Optional[str]` | `None` | declared field | +| `owner_group_id` | `Optional[str]` | `None` | declared field | + +### `RenameFolderRequest` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | + +### `FolderResponse` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `str` | | declared field | +| `name` | `str` | | declared field | +| `scope` | `str` | | declared field | +| `owner_user_id` | `Optional[str]` | | declared field | +| `owner_group_id` | `Optional[str]` | | declared field | +| `parent_id` | `Optional[str]` | | declared field | +| `created_by` | `Optional[str]` | | declared field | +| `created_at` | `str` | | declared field | + +### `TemplateResponse` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `str` | | declared field | +| `name` | `str` | | declared field | +| `description` | `Optional[str]` | | declared field | +| `study_type` | `Optional[str]` | | declared field | +| `scope` | `str` | | declared field | +| `owner_user_id` | `Optional[str]` | | declared field | +| `owner_group_id` | `Optional[str]` | | declared field | +| `system_prompt` | `Optional[str]` | | declared field | +| `entities` | `List[Any]` | | declared field | +| `summary_prompt` | `Optional[str]` | | declared field | +| `variables` | `Optional[List[Any]]` | | declared field | +| `tags` | `Optional[List[str]]` | | declared field | +| `is_immutable` | `bool` | | declared field | +| `version` | `int` | | declared field | +| `created_by` | `Optional[str]` | | declared field | +| `created_at` | `str` | | declared field | +| `updated_at` | `str` | | declared field | +| `can_edit` | `Optional[bool]` | `None` | declared field | +| `is_owner` | `Optional[bool]` | `None` | declared field | +| `group_name` | `Optional[str]` | `None` | declared field | +| `folder_id` | `Optional[str]` | `None` | declared field | + +### `VersionResponse` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `str` | | declared field | +| `template_id` | `str` | | declared field | +| `version` | `int` | | declared field | +| `system_prompt` | `Optional[str]` | | declared field | +| `entities` | `List[Any]` | | declared field | +| `summary_prompt` | `Optional[str]` | | declared field | +| `variables` | `Optional[List[Any]]` | | declared field | +| `changed_by` | `Optional[str]` | | declared field | +| `change_summary` | `Optional[str]` | | declared field | +| `created_at` | `str` | | declared field | + +### `PermissionResponse` + +**File:** `backend/api/templates/router.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `str` | | declared field | +| `template_id` | `str` | | declared field | +| `user_id` | `str` | | declared field | +| `can_read` | `bool` | | declared field | +| `can_write` | `bool` | | declared field | +| `granted_by` | `Optional[str]` | | declared field | +| `created_at` | `str` | | declared field | + +### `ProcessFileRequest` + +**File:** `backend/schemas/documents.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `processor` | `Optional[ProcessorType]` | `ProcessorType.AUTO` | declared field | +| `extract_figures` | `bool` | `Field( default=True, description="Extract figures/charts from document (Azure Document Intelligence only)", )` | Pydantic Field | +| `batch_number` | `Optional[int]` | `Field( default=None, description="Logical batch identifier (1–99) assigned by the frontend for grouped uploads", )` | Pydantic Field | + +### `ExtractFigureContentRequest` + +**File:** `backend/schemas/documents.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `model_type` | `str` | `Field( default="gemini", description="LLM model type for OCR extraction (gemini, azure)", )` | Pydantic Field | +| `model_id` | `Optional[str]` | `Field( default=None, description="Specific model ID to use" )` | Pydantic Field | +| `extraction_prompt` | `str` | `Field( default="Extract all textual content, data points, axis labels, legends, and any other readable information from this scientific figure or chart. Include numerical values...` | Pydantic Field | +| `max_tokens` | `int` | `Field(default=2048, description="Maximum tokens in the response")` | Pydantic Field | +| `temperature` | `float` | `Field( default=0.0, description="Sampling temperature for extraction" )` | Pydantic Field | +| `system_message` | `Optional[str]` | `Field( default=None, description="Custom system message for the model" )` | Pydantic Field | + +### `FigureExtractionResult` + +**File:** `backend/schemas/documents.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `content` | `str` | `Field(description="Extracted textual content from the figure")` | Pydantic Field | +| `model_used` | `str` | `Field(description="Model that was used for extraction")` | Pydantic Field | +| `timestamp` | `str` | `Field(description="ISO timestamp of extraction")` | Pydantic Field | +| `duration` | `float` | `Field(description="Processing time in seconds")` | Pydantic Field | + +### `FigureMetadata` + +**File:** `backend/schemas/documents.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `id` | `str` | `Field(description="Figure identifier")` | Pydantic Field | +| `page` | `Optional[int]` | `Field( default=None, description="Page number where figure appears" )` | Pydantic Field | +| `caption` | `Optional[str]` | `Field( default=None, description="Figure caption if available" )` | Pydantic Field | +| `image_path` | `Optional[str]` | `Field( default=None, description="Path to figure image file" )` | Pydantic Field | +| `bounding_regions` | `Optional[list]` | `Field( default=None, description="Figure bounding regions" )` | Pydantic Field | +| `extracted_content` | `Optional[FigureExtractionResult]` | `Field( default=None, description="OCR extraction results if available" )` | Pydantic Field | + +### `EvaluationRequest` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Request schema for evaluating a single entity extraction + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | `Field(..., description="Name of the entity being extracted")` | Pydantic Field | +| `extraction_prompt` | `str` | `Field(..., description="Prompt used for extraction")` | Pydantic Field | +| `actual_output` | `str` | `Field(..., description="The actual extracted output")` | Pydantic Field | +| `expected_output` | `Optional[str]` | `Field( None, description="Expected/ground truth output (required for correctness/completeness)", )` | Pydantic Field | +| `metrics` | `Optional[List[str]]` | `Field( default=["all"], description="List of metrics to use: 'correctness', 'completeness', 'relevance', 'safety', or 'all'", )` | Pydantic Field | +| `provider` | `str` | `Field( default="azure_openai", description="LLM provider for evaluation: 'azure_openai' or 'vertex_ai'", )` | Pydantic Field | +| `threshold` | `float` | `Field( default=0.5, ge=0.0, le=1.0, description="Score threshold for passing" )` | Pydantic Field | +| `strict_mode` | `bool` | `Field( default=False, description="If True, only perfect scores pass" )` | Pydantic Field | +| `custom_evaluation_steps` | `Optional[Dict[str, List[str]]]` | `Field( None, description="Custom evaluation steps for each metric (e.g., {'correctness': ['step1', 'step2']})", )` | Pydantic Field | +| `azure_deployment` | `Optional[str]` | `Field( None, description="Azure OpenAI deployment name" )` | Pydantic Field | +| `azure_endpoint` | `Optional[str]` | `Field(None, description="Azure OpenAI endpoint")` | Pydantic Field | +| `azure_api_key` | `Optional[str]` | `Field(None, description="Azure OpenAI API key")` | Pydantic Field | +| `azure_model_name` | `Optional[str]` | `Field(None, description="Azure OpenAI model name")` | Pydantic Field | +| `vertex_model_name` | `Optional[str]` | `Field( default="gemini-2.5-flash", description="Vertex AI model name" )` | Pydantic Field | +| `vertex_project` | `Optional[str]` | `Field(None, description="GCP project ID")` | Pydantic Field | +| `vertex_location` | `Optional[str]` | `Field( default="us-central1", description="GCP location" )` | Pydantic Field | +| `model_name` | `Optional[str]` | `Field( None, description="Model name for Anthropic providers" )` | Pydantic Field | + +### `SingleExtractionEval` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Schema for a single extraction in batch evaluation + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | | declared field | +| `extraction_prompt` | `str` | | declared field | +| `actual_output` | `str` | | declared field | +| `expected_output` | `Optional[str]` | `None` | declared field | + +### `BatchEvaluationRequest` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Request schema for batch evaluation of multiple extractions + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `extractions` | `List[SingleExtractionEval]` | `Field( ..., description="List of extractions to evaluate" )` | Pydantic Field | +| `metrics` | `Optional[List[str]]` | `Field( default=["all"], description="List of metrics to use" )` | Pydantic Field | +| `custom_evaluation_steps` | `Optional[Dict[str, List[str]]]` | `Field( None, description="Custom evaluation steps for each metric (e.g., {'correctness': ['step1', 'step2']})", )` | Pydantic Field | +| `provider` | `str` | `Field( default="azure_openai", description="LLM provider for evaluation" )` | Pydantic Field | +| `threshold` | `float` | `Field(default=0.5, ge=0.0, le=1.0)` | Pydantic Field | +| `strict_mode` | `bool` | `Field(default=False)` | Pydantic Field | +| `azure_deployment` | `Optional[str]` | `None` | declared field | +| `azure_endpoint` | `Optional[str]` | `None` | declared field | +| `azure_api_key` | `Optional[str]` | `None` | declared field | +| `azure_model_name` | `Optional[str]` | `None` | declared field | +| `vertex_model_name` | `Optional[str]` | `"gemini-2.5-flash"` | declared field | +| `vertex_project` | `Optional[str]` | `None` | declared field | +| `vertex_location` | `Optional[str]` | `"us-central1"` | declared field | +| `model_name` | `Optional[str]` | `None` | declared field | + +### `CustomMetricRequest` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Request schema for creating and running a custom G-Eval metric + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `metric_name` | `str` | `Field(..., description="Name of the custom metric")` | Pydantic Field | +| `evaluation_steps` | `List[str]` | `Field( ..., description="List of evaluation steps for the metric" )` | Pydantic Field | +| `entity_name` | `str` | `Field(..., description="Name of the entity being extracted")` | Pydantic Field | +| `extraction_prompt` | `str` | `Field(..., description="Prompt used for extraction")` | Pydantic Field | +| `actual_output` | `str` | `Field(..., description="The actual extracted output")` | Pydantic Field | +| `expected_output` | `Optional[str]` | `None` | declared field | +| `provider` | `str` | `Field(default="azure_openai")` | Pydantic Field | +| `threshold` | `float` | `Field(default=0.5, ge=0.0, le=1.0)` | Pydantic Field | +| `strict_mode` | `bool` | `Field(default=False)` | Pydantic Field | +| `azure_deployment` | `Optional[str]` | `None` | declared field | +| `azure_endpoint` | `Optional[str]` | `None` | declared field | +| `azure_api_key` | `Optional[str]` | `None` | declared field | +| `azure_model_name` | `Optional[str]` | `None` | declared field | +| `vertex_model_name` | `Optional[str]` | `"gemini-2.5-flash"` | declared field | +| `vertex_project` | `Optional[str]` | `None` | declared field | +| `vertex_location` | `Optional[str]` | `"us-central1"` | declared field | + +### `MetricResult` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Schema for a single metric evaluation result + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `metric_name` | `str` | | declared field | +| `score` | `float` | | declared field | +| `threshold` | `float` | | declared field | +| `success` | `bool` | | declared field | +| `reason` | `str` | | declared field | + +### `EvaluationResponse` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Response schema for evaluation results + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `evaluation_id` | `str` | | declared field | +| `entity_name` | `str` | | declared field | +| `provider` | `str` | | declared field | +| `model` | `str` | | declared field | +| `timestamp` | `str` | | declared field | +| `evaluation_time` | `float` | | declared field | +| `test_case` | `Dict[str, Any]` | | declared field | +| `metrics` | `List[MetricResult]` | | declared field | +| `aggregate_score` | `float` | | declared field | +| `all_passed` | `bool` | | declared field | +| `threshold` | `float` | | declared field | +| `strict_mode` | `bool` | | declared field | +| `status` | `str` | | declared field | +| `error` | `Optional[str]` | `None` | declared field | + +### `BatchEvaluationResponse` + +**File:** `backend/schemas/evaluations.py` +**Base classes:** `BaseModel` +**Purpose:** Response schema for batch evaluation results + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `batch_id` | `str` | | declared field | +| `timestamp` | `str` | | declared field | +| `batch_time` | `float` | | declared field | +| `total_evaluations` | `int` | | declared field | +| `successful_evaluations` | `int` | | declared field | +| `failed_evaluations` | `int` | | declared field | +| `avg_aggregate_score` | `float` | | declared field | +| `all_passed` | `bool` | | declared field | +| `threshold` | `float` | | declared field | +| `provider` | `str` | | declared field | +| `results` | `List[Dict[str, Any]]` | | declared field | + +### `Entity` + +**File:** `backend/schemas/extractions.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `prompt` | `str` | | declared field | +| `extracted` | `Optional[str]` | `None` | declared field | +| `system_prompt` | `Optional[str]` | `None` | declared field | + +### `ExtractRequest` + +**File:** `backend/schemas/extractions.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `conversion_id` | `str` | | declared field | +| `session_id` | `Optional[str]` | `None` | declared field | +| `deployment` | `Optional[str]` | `None` | declared field | +| `entities` | `List[Entity]` | | declared field | +| `api_version` | `Optional[str]` | `None` | declared field | +| `azure_endpoint` | `Optional[str]` | `None` | declared field | +| `azure_api_key` | `Optional[str]` | `None` | declared field | +| `gemini_api_key` | `Optional[str]` | `None` | declared field | +| `gemini_project_id` | `Optional[str]` | `None` | declared field | +| `gemini_location` | `Optional[str]` | `None` | declared field | +| `max_tokens` | `int` | `8024` | declared field | +| `temperature` | `float` | `0.0` | declared field | +| `model_type` | `Optional[str]` | `"azure"` | declared field | +| `model_id` | `Optional[str]` | `None` | declared field | +| `processor_used` | `Optional[str]` | `None` | declared field | + +### `ServerConfig` + +**File:** `backend/schemas/server.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `is_azure_openai_configured` | `bool` | | declared field | +| `is_gemini_configured` | `bool` | `False` | declared field | +| `is_azure_document_intelligence_configured` | `bool` | `False` | declared field | +| `is_llama_configured` | `bool` | `False` | declared field | +| `is_macbook_configured` | `bool` | `False` | declared field | +| `is_macbook_healthy` | `bool` | `False` | declared field | + +### `SessionEntity` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Entity configuration within a session + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `name` | `str` | | declared field | +| `prompt` | `str` | | declared field | +| `system_prompt` | `Optional[str]` | `None` | declared field | + +### `SessionConfiguration` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Configuration snapshot for a session + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `study_type` | `Optional[str]` | `None` | declared field | +| `selected_models` | `List[str]` | `Field(default_factory=list)` | Pydantic Field | +| `entities` | `List[SessionEntity]` | `Field(default_factory=list)` | Pydantic Field | +| `summary_prompt` | `Optional[str]` | `None` | declared field | +| `paragraph_system_prompt` | `Optional[str]` | `None` | declared field | +| `temperature` | `float` | `0.0` | declared field | +| `model_temperatures` | `Optional[Dict[str, float]]` | `Field( default_factory=dict )` | Pydantic Field | +| `files_config` | `Optional[Dict[str, Any]]` | `Field( default_factory=dict )` | Pydantic Field | +| `evaluation_config` | `Optional[Dict[str, Any]]` | `Field( default_factory=dict )` | Pydantic Field | + +### `SessionDocument` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Document reference within a session + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `file_hash` | `str` | | declared field | +| `filename` | `str` | | declared field | +| `id` | `Optional[str]` | `None` | declared field | +| `processor_used` | `Optional[str]` | `None` | declared field | +| `parse_cost` | `Optional[float]` | `None` | declared field | +| `page_count` | `Optional[int]` | `None` | declared field | +| `parse_duration_seconds` | `Optional[float]` | `None` | declared field | +| `figure_count` | `Optional[int]` | `None` | declared field | +| `table_count` | `Optional[int]` | `None` | declared field | + +### `ExtractionResult` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Extraction result for a single entity + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | | declared field | +| `model_id` | `str` | | declared field | +| `document_id` | `Optional[str]` | `None` | declared field | +| `extracted_text` | `Optional[str]` | `None` | declared field | +| `references` | `Optional[List[Dict[str, Any]]]` | `None` | declared field | +| `status` | `Literal["pending", "completed", "error"]` | `"pending"` | declared field | +| `error_message` | `Optional[str]` | `None` | declared field | +| `extracted_at` | `Optional[datetime]` | `None` | declared field | +| `file_hash` | `Optional[str]` | `None` | declared field | +| `prompt_tokens` | `Optional[int]` | `None` | declared field | +| `completion_tokens` | `Optional[int]` | `None` | declared field | +| `duration_ms` | `Optional[int]` | `None` | declared field | +| `cost` | `Optional[float]` | `None` | declared field | + +### `SessionMetrics` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Aggregated session metrics (stored in sessions table) + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `total_cost` | `float` | `0.0` | declared field | +| `total_latency` | `float` | `0.0` | declared field | +| `total_calls` | `int` | `0` | declared field | + +### `EvaluationScore` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Evaluation score from a judge + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `metric` | `str` | | declared field | +| `score` | `Optional[float]` | `None` | declared field | +| `reasoning` | `Optional[str]` | `None` | declared field | +| `judge_model` | `Optional[str]` | `None` | declared field | +| `human_score` | `Optional[float]` | `None` | declared field | +| `evaluation_cost` | `Optional[float]` | `None` | declared field | +| `evaluation_time` | `Optional[float]` | `None` | declared field | + +### `EvaluationResult` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Evaluation result for an extraction + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `document_id` | `Optional[str]` | `None` | declared field | +| `file_hash` | `Optional[str]` | `None` | declared field | +| `entity_name` | `str` | | declared field | +| `model_id` | `str` | | declared field | +| `ground_truth` | `Optional[str]` | `None` | declared field | +| `scores` | `List[EvaluationScore]` | `Field(default_factory=list)` | Pydantic Field | +| `human_score` | `Optional[float]` | `None` | declared field | +| `evaluated_at` | `Optional[datetime]` | `None` | declared field | +| `evaluation_cost` | `Optional[float]` | `None` | declared field | +| `evaluation_time` | `Optional[float]` | `None` | declared field | + +### `Session` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Full session model with configuration, results, and evaluations + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `session_id` | `str` | `Field(default_factory=lambda: str(uuid.uuid4()))` | Pydantic Field | +| `user_id` | `str` | | declared field | +| `name` | `str` | `"Untitled Session"` | declared field | +| `status` | `Literal["in_progress", "completed"]` | `"in_progress"` | declared field | +| `last_step` | `Optional[str]` | `"upload"` | declared field | +| `evaluation_config` | `Optional[Dict[str, Any]]` | `Field(default_factory=dict)` | Pydantic Field | +| `files_config` | `Optional[Dict[str, Any]]` | `Field(default_factory=dict)` | Pydantic Field | +| `created_at` | `datetime` | `Field(default_factory=datetime.utcnow)` | Pydantic Field | +| `updated_at` | `datetime` | `Field(default_factory=datetime.utcnow)` | Pydantic Field | +| `configuration` | `SessionConfiguration` | `Field(default_factory=SessionConfiguration)` | Pydantic Field | +| `documents` | `List[SessionDocument]` | `Field(default_factory=list)` | Pydantic Field | +| `extraction_results` | `List[ExtractionResult]` | `Field(default_factory=list)` | Pydantic Field | +| `evaluation_results` | `List[EvaluationResult]` | `Field(default_factory=list)` | Pydantic Field | +| `session_metrics` | `Optional[SessionMetrics]` | `None` | declared field | + +### `CreateSessionRequest` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Request to create a new session + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `str` | | declared field | +| `name` | `Optional[str]` | `"Untitled Session"` | declared field | +| `last_step` | `Optional[str]` | `"upload"` | declared field | +| `configuration` | `Optional[SessionConfiguration]` | `None` | declared field | +| `evaluation_config` | `Optional[Dict[str, Any]]` | `None` | declared field | +| `files_config` | `Optional[Dict[str, Any]]` | `None` | declared field | +| `documents` | `Optional[List[SessionDocument]]` | `None` | declared field | + +### `UpdateSessionRequest` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Request to update an existing session + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `user_id` | `Optional[str]` | `None` | declared field | +| `name` | `Optional[str]` | `None` | declared field | +| `status` | `Optional[Literal["in_progress", "completed"]]` | `None` | declared field | +| `last_step` | `Optional[str]` | `None` | declared field | +| `configuration` | `Optional[SessionConfiguration]` | `None` | declared field | +| `evaluation_config` | `Optional[Dict[str, Any]]` | `None` | declared field | +| `files_config` | `Optional[Dict[str, Any]]` | `None` | declared field | +| `documents` | `Optional[List[SessionDocument]]` | `None` | declared field | +| `extraction_results` | `Optional[List[ExtractionResult]]` | `None` | declared field | +| `evaluation_results` | `Optional[List[EvaluationResult]]` | `None` | declared field | + +### `SessionSummary` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Lightweight session summary for list views + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `session_id` | `str` | | declared field | +| `name` | `str` | | declared field | +| `status` | `Literal["in_progress", "completed"]` | | declared field | +| `created_at` | `datetime` | | declared field | +| `updated_at` | `datetime` | | declared field | +| `last_step` | `Optional[str]` | `None` | declared field | +| `study_type` | `Optional[str]` | `None` | declared field | +| `document_count` | `int` | | declared field | +| `document_names` | `List[str]` | `Field(default_factory=list)` | Pydantic Field | +| `extraction_count` | `int` | | declared field | +| `evaluation_count` | `int` | | declared field | +| `shared_by_name` | `Optional[str]` | `None` | declared field | +| `shared_group_name` | `Optional[str]` | `None` | declared field | +| `shared_at` | `Optional[datetime]` | `None` | declared field | +| `owner_user_id` | `Optional[str]` | `None` | declared field | + +### `SessionListResponse` + +**File:** `backend/schemas/sessions.py` +**Base classes:** `BaseModel` +**Purpose:** Response for listing sessions + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `sessions` | `List[SessionSummary]` | | declared field | +| `total` | `int` | | declared field | + +### `MarkdownReference` + +**File:** `backend/services/llm/azure.py` +**Base classes:** `BaseModel` +**Purpose:** A reference to a specific section of the markdown that was used + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `text` | `str` | `Field( description="The exact text excerpt from the markdown that was referenced" )` | Pydantic Field | + +### `ExtractionResult` + +**File:** `backend/services/llm/azure.py` +**Base classes:** `BaseModel` +**Purpose:** Structured result containing both the extracted answer and its references + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `answer` | `str` | `Field( description="The extracted information or answer based on the prompt" )` | Pydantic Field | +| `references` | `List[MarkdownReference]` | `Field( description="List of specific text excerpts from the markdown that were used to generate this answer" )` | Pydantic Field | + +### `MarkdownReference` + +**File:** `backend/services/llm/gemini.py` +**Base classes:** `BaseModel` +**Purpose:** A reference to a specific section of the markdown that was used + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `text` | `str` | `Field( description="The exact text excerpt from the markdown that was referenced" )` | Pydantic Field | + +### `ExtractionResult` + +**File:** `backend/services/llm/gemini.py` +**Base classes:** `BaseModel` +**Purpose:** Structured result containing both the extracted answer and its references + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `answer` | `str` | `Field( description="The extracted information or answer based on the prompt" )` | Pydantic Field | +| `references` | `List[MarkdownReference]` | `Field( description="List of specific text excerpts from the markdown that were used to generate this answer" )` | Pydantic Field | + +### `MarkdownReference` + +**File:** `backend/services/llm/llama.py` +**Base classes:** `BaseModel` +**Purpose:** A reference to a specific section of the markdown that was used + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `text` | `str` | `Field( description="The exact text excerpt from the markdown that was referenced" )` | Pydantic Field | + +### `ExtractionResult` + +**File:** `backend/services/llm/llama.py` +**Base classes:** `BaseModel` +**Purpose:** Structured result containing both the extracted answer and its references + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `answer` | `Union[str, Dict[str, Any], List[Any]]` | `Field( description="The extracted information or answer based on the prompt (string, structured data, or list)" )` | Pydantic Field | +| `references` | `List[MarkdownReference]` | `Field( description="List of specific text excerpts from the markdown that were used to generate this answer" )` | Pydantic Field | + +### `MarkdownReference` + +**File:** `backend/services/llm/vllm.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `text` | `str` | `Field(description="Exact text excerpt from the markdown")` | Pydantic Field | + +### `ExtractionResult` + +**File:** `backend/services/llm/vllm.py` +**Base classes:** `BaseModel` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `answer` | `str` | `Field(description="Extracted information or answer")` | Pydantic Field | +| `references` | `List[MarkdownReference]` | `Field( description="Text excerpts used to generate the answer" )` | Pydantic Field | + +## Dataclasses and runtime state + +### `Config` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Decorators:** `dataclass` +**Purpose:** Configuration settings for the evaluation script. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `base_url` | `str` | `"http://macbook1.sciencegpt.ca"` | declared field | +| `api_endpoint` | `str` | `"/api/generate"` | declared field | +| `delay_between_requests` | `int` | `60` | declared field | +| `max_tokens` | `int` | `4096` | declared field | +| `temperature` | `float` | `0.0` | declared field | +| `request_timeout` | `int` | `600` | declared field | +| `project_root` | `Path` | `Path(__file__).resolve().parents[2]` | declared field | +| `model_list_file` | `Path` | `field(default_factory=lambda: Path("macbookmodelnames.csv"))` | dataclass field | +| `prompt_file` | `Path` | `field(default_factory=lambda: Path("prompt.md"))` | dataclass field | +| `test_document_file` | `Path` | `field( default_factory=lambda: Path( "64596011f75ffd2916b1ce50131f3d7cb36c10141e914e435fd5dc0e007b2b52_base.md" ) )` | dataclass field | +| `output_file` | `Path` | `field( default_factory=lambda: Path("model_evaluation_results.xlsx") )` | dataclass field | + +**Public methods:** `from_env()` + +### `TestResult` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Decorators:** `dataclass` +**Purpose:** Result of testing a single model. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `model_name` | `str` | | declared field | +| `entity_responses` | `Dict[str, str]` | | declared field | +| `ground_truth` | `Dict[str, str]` | | declared field | +| `score` | `float` | | declared field | +| `total_latency_seconds` | `float` | | declared field | +| `error` | `Optional[str]` | `None` | declared field | + +**Public methods:** `to_dict()` + +### `EntityPrompt` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Decorators:** `dataclass` +**Purpose:** Represents a single entity extraction prompt. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | | declared field | +| `prompt_with_examples` | `str` | | declared field | + +### `VRAMStatus` + +**File:** `backend/services/document/processors/docling/vram_guard.py` +**Decorators:** `dataclass` +**Purpose:** Snapshot of current GPU VRAM and worker state. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `total_mb` | `float` | | declared field | +| `used_mb` | `float` | | declared field | +| `free_mb` | `float` | | declared field | +| `active_workers` | `int` | | declared field | +| `max_workers` | `int` | | declared field | +| `can_accept_worker` | `bool` | | declared field | +| `estimated_per_worker_mb` | `float` | | declared field | +| `safety_margin_mb` | `float` | | declared field | +| `jobs_completed` | `int` | `0` | declared field | +| `jobs_queued` | `int` | `0` | declared field | +| `is_cold_start` | `bool` | `False` | declared field | + +**Public methods:** `utilization_pct()` + +### `EvalTask` + +**File:** `backend/services/evaluation/job_queue.py` +**Decorators:** `dataclass` +**Purpose:** One atomic unit of work: evaluate a single entity extraction. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | | declared field | +| `source_model` | `str` | | declared field | +| `actual_output` | `str` | | declared field | +| `extraction_prompt` | `str` | | declared field | +| `expected_output` | `Optional[str]` | `None` | declared field | +| `file_hash` | `Optional[str]` | `None` | declared field | +| `file_id` | `Optional[str]` | `None` | declared field | + +### `ProviderConfig` + +**File:** `backend/services/evaluation/job_queue.py` +**Decorators:** `dataclass` +**Purpose:** Configuration for a single judge LLM. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `provider_id` | `str` | | declared field | +| `provider` | `str` | | declared field | +| `model_name` | `Optional[str]` | `None` | declared field | +| `deployment` | `Optional[str]` | `None` | declared field | +| `endpoint` | `Optional[str]` | `None` | declared field | +| `api_key` | `Optional[str]` | `None` | declared field | + +### `TaskResult` + +**File:** `backend/services/evaluation/job_queue.py` +**Decorators:** `dataclass` +**Purpose:** Result of evaluating one task with one provider. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `entity_name` | `str` | | declared field | +| `source_model` | `str` | | declared field | +| `file_id` | `Optional[str]` | | declared field | +| `file_hash` | `Optional[str]` | | declared field | +| `provider_id` | `str` | | declared field | +| `provider` | `str` | | declared field | +| `model` | `str` | | declared field | +| `aggregate_score` | `float` | | declared field | +| `all_passed` | `bool` | | declared field | +| `evaluation_time` | `float` | | declared field | +| `evaluation_cost` | `float` | | declared field | +| `metrics` | `List[Dict[str, Any]]` | | declared field | +| `ground_truth` | `Optional[str]` | `None` | declared field | + +### `EvalJob` + +**File:** `backend/services/evaluation/job_queue.py` +**Decorators:** `dataclass` +**Purpose:** A batch evaluation job submitted by a user. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `job_id` | `str` | | declared field | +| `session_id` | `str` | | declared field | +| `user_id` | `str` | | declared field | +| `tasks` | `List[EvalTask]` | | declared field | +| `providers` | `List[ProviderConfig]` | | declared field | +| `metrics` | `List[str]` | | declared field | +| `custom_evaluation_steps` | `Dict[str, List[str]]` | | declared field | +| `threshold` | `float` | `0.7` | declared field | +| `status` | `str` | `"pending"` | declared field | +| `progress` | `int` | `0` | declared field | +| `total` | `int` | `0` | declared field | +| `results` | `List[TaskResult]` | `field(default_factory=list)` | dataclass field | +| `errors` | `List[Dict[str, str]]` | `field( default_factory=list )` | dataclass field | +| `cancelled` | `bool` | `False` | declared field | +| `created_at` | `datetime` | `field(default_factory=lambda: datetime.now(timezone.utc))` | dataclass field | +| `completed_at` | `Optional[datetime]` | `None` | declared field | +| `error` | `Optional[str]` | `None` | declared field | +| `_asyncio_tasks` | `List[Any]` | `field(default_factory=list, repr=False)` | dataclass field | + +**Public methods:** `to_status_dict()` + +### `_JobStatusProxy` + +**File:** `backend/services/evaluation/job_queue.py` +**Decorators:** `dataclass` +**Purpose:** Read-only snapshot of a job loaded from DB (cross-worker lookup). + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `_data` | `Dict[str, Any]` | | declared field | + +**Public methods:** `to_status_dict()` + +### `CallMetric` + +**File:** `backend/services/telemetry/cost_tracker.py` +**Decorators:** `dataclass` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `provider` | `str` | | declared field | +| `model` | `str` | | declared field | +| `prompt_tokens` | `int` | | declared field | +| `completion_tokens` | `int` | | declared field | +| `duration` | `float` | | declared field | +| `cost` | `float` | | declared field | +| `timestamp` | `str` | | declared field | +| `document_name` | `Optional[str]` | `None` | declared field | +| `page_count` | `int` | `0` | declared field | +| `figure_count` | `int` | `0` | declared field | +| `table_count` | `int` | `0` | declared field | +| `batch_number` | `Optional[int]` | `None` | declared field | + +### `BatchMetric` + +**File:** `backend/services/telemetry/cost_tracker.py` +**Decorators:** `dataclass` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `batch_number` | `int` | | declared field | +| `batch_latency` | `float` | | declared field | +| `document_count` | `int` | | declared field | + +### `SessionMetrics` + +**File:** `backend/services/telemetry/cost_tracker.py` +**Decorators:** `dataclass` + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `session_id` | `str` | | declared field | +| `total_cost` | `float` | `0.0` | declared field | +| `total_latency` | `float` | `0.0` | declared field | +| `total_calls` | `int` | `0` | declared field | +| `calls` | `List[CallMetric]` | `field(default_factory=list)` | dataclass field | +| `batches` | `Dict[int, BatchMetric]` | `field(default_factory=dict)` | dataclass field | + +## Enums + +### `ProcessorType` + +**File:** `backend/schemas/enums.py` +**Base classes:** `str, Enum` +**Purpose:** Document processor types + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `AUTO` | | `"auto"` | declared field | +| `DOCLING` | | `"docling"` | declared field | +| `AZURE_DOC_INTELLIGENCE` | | `"azure_doc_intelligence"` | declared field | + +## Service and provider classes + +### `SQLAlchemyDBService` + +**File:** `backend/services/database/sqlalchemy_db_service.py` +**Purpose:** Database service using SQLAlchemy / Azure Postgres directly. + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create_session()`, `get_session()`, `list_sessions()`, `get_session_basic()`, `update_session()`, `delete_session()`, `get_session_for_shared_view()`, `record_login()`, `create_document()`, `get_document()`, `get_documents_by_session()`, `get_parse_cost_by_file_hash()`, `list_user_documents()`, `update_document()`, `update_document_processing()`, `upsert_extraction_result()`, `update_extraction_cost()`, `get_extraction_results_by_session()`, `get_extraction_results_by_document()`, `upsert_evaluation_result()`, `get_evaluation_results_by_extraction()`, `get_or_create_preferences()`, `update_preferences()`, `save_prompt_template()`, `get_prompt_templates()`, `delete_prompt_template()`, `share_session()`, `unshare_session()`, `list_shared_sessions()`, `get_user_group_ids()`, `get_group_name()`, `get_user_display_name()`, `increment_session_metrics()`, `get_session_metrics()`, `reset_session_metrics()`, `create_eval_job_record()`, `upsert_eval_job_status()`, `get_eval_job_status()`, `mark_eval_job_cancelled()` + +### `DocumentService` + +**File:** `backend/services/document/document_service.py` +**Purpose:** Main service for document processing with multiple processor support + +| Instance attribute | Initialized from | +| --- | --- | +| `available_processors` | `self._check_processor_availability()` | +| `azure_doc_intelligence_service` | `AzureDocIntelligenceService()` | +| `docling_service` | `DoclingRemoteClient()` | +| `file_service` | `get_organized_file_service()` | + +**Public methods:** `async convert_document_to_markdown()`, `async get_processor_capabilities()`, `async get_conversion_by_id()`, `async get_markdown_content()`, `async resolve_processor_used()`, `async get_figures_for_conversion()`, `async get_raw_analysis_result()`, `async get_processing_file_bytes()` + +### `FileService` + +**File:** `backend/services/document/file_service.py` +**Purpose:** Service for handling file upload, storage, and management operations + +| Instance attribute | Initialized from | +| --- | --- | +| `metadata_dir` | `self.upload_dir / "metadata"` | +| `upload_dir` | `Path(upload_dir)` | + +**Public methods:** `async save_uploaded_file()`, `async get_file_by_hash()`, `async get_file_info()`, `async get_file_by_id()`, `async delete_file()`, `async get_file_content()`, `async list_files()`, `async _save_metadata()`, `async _load_metadata()` + +### `OrganizedFileService` + +**File:** `backend/services/document/organized_file_service.py` +**Purpose:** Service for file upload, storage, and retrieval using Azure Blob Storage. + +| Instance attribute | Initialized from | +| --- | --- | +| `_blob` | `BlobStorageClient(conn_str, container)` | +| `_db` | `None` | + +**Public methods:** `db()`, `compute_file_hash()`, `async save_uploaded_file()`, `get_processing_output_path()`, `async sync_processing_output_to_blob()`, `async get_processed_metadata()`, `async update_processed_metadata()`, `async get_processing_file_bytes()`, `async processing_file_exists()`, `async resolve_processed_processor()`, `async build_document_view()`, `async is_file_processed()`, `async get_processed_content()`, `async get_file_content()`, `async get_file_metadata()`, `async get_original_file_path()`, `async list_user_files()` + +### `OrganizedDocumentProcessor` + +**File:** `backend/services/document/organized_processor.py` +**Purpose:** Document processor that uses the organized file structure. + +| Instance attribute | Initialized from | +| --- | --- | +| `azure_service` | `AzureDocIntelligenceService()` | +| `docling_service` | `DoclingRemoteClient(base_url=docling_url)` | +| `file_service` | `get_organized_file_service()` | + +**Public methods:** `async process_document()`, `async _process_with_azure()`, `async _process_with_docling()`, `async _load_metadata()`, `async get_processed_markdown()`, `async is_processed()` + +### `AzureDocIntelligenceService` + +**File:** `backend/services/document/processors/azure_doc_intelligence/azure_doc_intelligence_service.py` +**Purpose:** Service for processing documents using Azure Document Intelligence + +| Instance attribute | Initialized from | +| --- | --- | +| `base_path` | `Path(__file__).parent.parent.parent.parent.parent` | +| `client` | `self._init_client()` | +| `output_base_dir` | `self.base_path / "output" / "azure_doc_intelligence"` | + +**Public methods:** `async convert_document_to_markdown()`, `async _log()`, `async get_conversion_by_id()`, `async get_markdown_content()`, `is_available()`, `async get_figures_for_conversion()`, `async get_raw_analysis_result()` + +### `DoclingRemoteClient` + +**File:** `backend/services/document/processors/docling/docling_remote_client.py` +**Purpose:** Drop-in replacement for DoclingService.convert_document_to_markdown(). + +| Instance attribute | Initialized from | +| --- | --- | +| `base_url` | `(base_url or os.environ.get("DOCLING_SERVICE_URL", "")).rstrip( "/" )` | +| `poll_interval` | `poll_interval` | +| `timeout` | `timeout` | + +**Public methods:** `async convert_document_to_markdown()`, `async _call_sync_convert()`, `async _download_artifact_bundle()`, `async convert_async()`, `async check_health()` + +### `_VRAMPeakTracker` + +**File:** `backend/services/document/processors/docling/docling_service.py` +**Purpose:** Poll nvidia-smi in a background thread to capture the true peak VRAM. + +| Instance attribute | Initialized from | +| --- | --- | +| `_peak` | `-1.0` | +| `_poll_sec` | `poll_sec` | +| `_stop` | `_threading.Event()` | +| `_thread` | `None` | + +**Public methods:** `start()`, `stop()` + +### `DoclingService` + +**File:** `backend/services/document/processors/docling/docling_service.py` +**Purpose:** Service for handling document ingestion and conversion using Docling + +| Instance attribute | Initialized from | +| --- | --- | +| `_pool_size` | `0` | +| `_process_pool` | `None` | +| `_vram_guard` | `None` | +| `base_path` | `Path(__file__).resolve().parents[4]` | +| `image_resolution_scale` | `image_resolution_scale` | +| `output_base_dir` | `Path(markdown_dir)` | +| `output_base_dir` | `self.base_path / "output" / "docling"` | + +**Public methods:** `process_pool()`, `vram_guard()`, `max_workers()`, `async convert_document_to_markdown()`, `async start_conversion()`, `async get_conversion_by_id()`, `async get_markdown_content()`, `async list_conversions()`, `async delete_conversion()`, `async get_figures_for_conversion()`, `async get_raw_analysis_result()`, `async _save_conversion_metadata()`, `async _load_conversion_metadata()` + +### `VRAMGuard` + +**File:** `backend/services/document/processors/docling/vram_guard.py` +**Purpose:** VRAM-aware concurrency controller for GPU-accelerated document processing. + +| Instance attribute | Initialized from | +| --- | --- | +| `_active_workers` | `0` | +| `_cold_start_max` | `cold_start_workers if cold_start_workers is not None else int( os.environ.get( "VRAM_COLD_START_WORKERS", str(_COLD_START_WORKERS_DEFAULT) ) )` | +| `_cold_start_min_jobs` | `cold_start_min_jobs if cold_start_min_jobs is not None else _COLD_START_MIN_JOBS_DEFAULT` | +| `_is_cold_start` | `not loaded_state` | +| `_jobs_completed` | `0` | +| `_last_smi_free` | `self.vram_total_mb` | +| `_last_smi_time` | `0.0` | +| `_last_smi_used` | `0.0` | +| `_lock` | `asyncio.Lock()` | +| `_max_workers_cap` | `max_workers_cap if max_workers_cap is not None else ( int(os.environ["VRAM_MAX_WORKERS"]) if "VRAM_MAX_WORKERS" in os.environ else None )` | +| `_observation_window` | `observation_window if observation_window is not None else int( os.environ.get( "VRAM_OBSERVATION_WINDOW", str(_OBSERVATION_WINDOW_DEFAULT) ) )` | +| `_peak_observations` | `loaded_state.get("observations", []) if loaded_state else []` | +| `_per_worker_mb` | `loaded_state["per_worker_mb"]` | +| `_per_worker_mb` | `per_worker_init_mb if per_worker_init_mb is not None else float(os.environ.get("VRAM_PER_WORKER_INIT_MB", "2800"))` | +| `_persistence_path` | `Path(persistence_path) if persistence_path else _DEFAULT_STATE_PATH` | +| `_pool_needs_resize` | `False` | +| `_queued_workers` | `0` | +| `_slot_available` | `asyncio.Event()` | +| `_smi_lock` | `threading.Lock()` | +| `_usable_vram_mb` | `self.vram_total_mb - self.safety_margin_mb` | +| `check_interval_sec` | `check_interval_sec if check_interval_sec is not None else float(os.environ.get("VRAM_CHECK_INTERVAL_SEC", "1.0"))` | +| `safety_margin_mb` | `safety_margin_mb if safety_margin_mb is not None else float(os.environ.get("VRAM_SAFETY_MARGIN_MB", "1536"))` | +| `vram_total_mb` | `vram_total_mb or _get_gpu_vram_total_mb() or 16384.0` | + +**Public methods:** `async acquire_slot()`, `report_worker_result()`, `report_oom()`, `get_status()`, `max_workers()`, `active_workers()`, `per_worker_mb()`, `is_cuda_oom()` + +### `AnthropicVertexDeepEvalModel` + +**File:** `backend/services/evaluation/adapters/anthropic_adapter.py` +**Base classes:** `DeepEvalBaseLLM` +**Purpose:** Custom DeepEval model adapter for Anthropic via Vertex AI + +| Instance attribute | Initialized from | +| --- | --- | +| `_model_name` | `model_name` | +| `async_client` | `AsyncAnthropicVertex( region=self.location, project_id=self.project )` | +| `call_history` | `[]` | +| `client` | `AnthropicVertex(region=self.location, project_id=self.project)` | +| `location` | `location` | +| `max_tokens` | `max_tokens` | +| `project` | `project or "hcsx-scigpt2-innocentrhino-acm"` | +| `temperature` | `temperature` | + +**Public methods:** `load_model()`, `generate()`, `async a_generate()`, `get_model_name()` + +### `AzureOpenAIDeepEvalModel` + +**File:** `backend/services/evaluation/adapters/azure_adapter.py` +**Base classes:** `DeepEvalBaseLLM` +**Purpose:** Custom DeepEval model adapter for Azure OpenAI using LangChain + +| Instance attribute | Initialized from | +| --- | --- | +| `_model_name` | `model_name or (secrets_config.get("model_name") if secrets_config else None) or os.getenv("AZURE_OPENAI_MODEL_NAME", "gpt-5-mini")` | +| `api_key` | `api_key or (secrets_config.get("api_key") if secrets_config else None) or os.getenv("AZURE_OPENAI_KEY")` | +| `api_version` | `api_version or (secrets_config.get("api_version") if secrets_config else None) or os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview")` | +| `call_history` | `[]` | +| `deployment` | `deployment or (secrets_config.get("deployment") if secrets_config else None) or os.getenv("AZURE_OPENAI_DEPLOYMENT") or os.getenv("AZURE_OPENAI_MODEL_NAME")` | +| `endpoint` | `endpoint or (secrets_config.get("endpoint") if secrets_config else None) or os.getenv("AZURE_OPENAI_ENDPOINT")` | +| `max_tokens` | `max_tokens` | +| `model` | `AzureChatOpenAI(**model_kwargs)` | +| `temperature` | `temperature` | + +**Public methods:** `load_model()`, `generate()`, `async a_generate()`, `get_model_name()` + +### `VertexAIDeepEvalModel` + +**File:** `backend/services/evaluation/adapters/vertex_adapter.py` +**Base classes:** `DeepEvalBaseLLM` +**Purpose:** Custom DeepEval model adapter for Vertex AI using LangChain + +| Instance attribute | Initialized from | +| --- | --- | +| `_model_name` | `model_name` | +| `call_history` | `[]` | +| `location` | `location or os.getenv("GEMINI_LOCATION", "us-central1")` | +| `model` | `ChatVertexAI(**model_kwargs)` | +| `project` | `project or os.getenv("GEMINI_PROJECT")` | +| `temperature` | `temperature` | + +**Public methods:** `load_model()`, `generate()`, `async a_generate()`, `get_model_name()` + +### `EvaluationService` + +**File:** `backend/services/evaluation/evaluation_service.py` +**Purpose:** Main evaluation service orchestrator + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `METRIC_FACTORIES` | | `{ "correctness": CorrectnessMetricFactory, "completeness": CompletenessMetricFactory, "relevance": RelevanceMetricFactory, "safety": SafetyMetricFactory, }` | declared field | + +**Public methods:** `create_evaluation_model()`, `create_metric()`, `create_custom_metric()`, `async _evaluate_combined()`, `async evaluate_extraction()`, `async evaluate_multiple_extractions()`, `async get_evaluation_result()`, `async list_evaluations()` + +### `CompletenessMetricFactory` + +**File:** `backend/services/evaluation/metrics/completeness.py` +**Purpose:** Factory for creating Completeness evaluation metrics + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create()`, `get_required_params()`, `get_description()` + +### `CorrectnessMetricFactory` + +**File:** `backend/services/evaluation/metrics/correctness.py` +**Purpose:** Factory for creating Correctness evaluation metrics + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create()`, `get_required_params()`, `get_description()` + +### `CustomMetricFactory` + +**File:** `backend/services/evaluation/metrics/custom.py` +**Purpose:** Factory for creating custom G-Eval metrics + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create()`, `get_description()` + +### `RelevanceMetricFactory` + +**File:** `backend/services/evaluation/metrics/relevance.py` +**Purpose:** Factory for creating Relevance evaluation metrics + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create()`, `get_required_params()`, `get_description()` + +### `SafetyMetricFactory` + +**File:** `backend/services/evaluation/metrics/safety.py` +**Purpose:** Factory for creating Safety evaluation metrics + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create()`, `get_required_params()`, `get_description()` + +### `EvaluationResultStorage` + +**File:** `backend/services/evaluation/storage/result_storage.py` +**Purpose:** Handles storage and retrieval of evaluation results + +| Instance attribute | Initialized from | +| --- | --- | +| `output_dir` | `Path(output_dir) if output_dir else base_path / "output" / "evaluations"` | + +**Public methods:** `async save()`, `async get()`, `async list_all()`, `async delete()`, `get_storage_path()` + +### `GroupService` + +**File:** `backend/services/groups/group_service.py` +**Purpose:** Service for managing groups and memberships + +_No class-level data fields or constructor instance attributes are defined._ + +**Public methods:** `create_group()`, `get_group()`, `list_user_groups()`, `update_group()`, `delete_group()`, `get_group_members()`, `add_member()`, `update_member_role()`, `remove_member()` + +### `AnthropicLLMClient` + +**File:** `backend/services/llm/anthropic.py` + +| Instance attribute | Initialized from | +| --- | --- | +| `disabled` | `not self.service_account_path` | +| `location` | `os.environ.get("ANTHROPIC_LOCATION", "global")` | +| `project_id` | `os.environ.get("ANTHROPIC_PROJECT_ID") or "hcsx-scigpt2-innocentrhino-acm"` | +| `service_account_path` | `self._find_service_account_file()` | + +**Public methods:** `async _call_anthropic_api()`, `async extract_entities_with_anthropic()`, `async generate_paragraph_with_anthropic()` + +### `AzureLLMClient` + +**File:** `backend/services/llm/azure.py` + +| Instance attribute | Initialized from | +| --- | --- | +| `api_key` | `os.environ.get("AZURE_OPENAI_KEY")` | +| `api_version` | `os.environ.get( "AZURE_OPENAI_API_VERSION", "2024-08-01-preview" )` | +| `default_deployment` | `os.environ.get("AZURE_OPENAI_DEPLOYMENT")` | +| `default_model_name` | `os.environ.get("AZURE_OPENAI_MODEL_NAME")` | +| `disabled` | `not has_global_creds and not has_configured_models` | +| `endpoint` | `os.environ.get("AZURE_OPENAI_ENDPOINT")` | + +**Public methods:** `async generate_paragraph_with_azure()`, `async extract_entities_with_azure()`, `async extract_content_from_image()` + +### `GeminiLLMClient` + +**File:** `backend/services/llm/gemini.py` + +| Instance attribute | Initialized from | +| --- | --- | +| `disabled` | `not self.project_id or not self.location or not self.service_account_path` | +| `location` | `os.environ.get("GEMINI_LOCATION") or os.environ.get("VERTEX_AI_LOCATION") or "us-central1"` | +| `project_id` | `os.environ.get("GEMINI_PROJECT_ID") or os.environ.get("GEMINI_PROJECT") or os.environ.get("VERTEX_AI_PROJECT")` | +| `service_account_path` | `self._find_service_account_file()` | + +**Public methods:** `async _call_gemini_api()`, `async extract_entities_with_gemini()`, `async generate_paragraph_with_gemini()`, `async extract_content_from_image()` + +### `LlamaLLMClient` + +**File:** `backend/services/llm/llama.py` + +| Instance attribute | Initialized from | +| --- | --- | +| `disabled` | `not self.project_id or not self.location or not self.region or not self.service_account_path` | +| `location` | `os.environ.get("LLAMA_LOCATION", "us-east5")` | +| `project_id` | `os.environ.get("LLAMA_PROJECT_ID") or os.environ.get( "GEMINI_PROJECT_ID" )` | +| `region` | `os.environ.get("LLAMA_REGION", "us-east5")` | +| `service_account_path` | `self._find_service_account_file()` | + +**Public methods:** `async _call_llama_api()`, `async extract_entities_with_llama()`, `async _try_llama_extraction_strategy()`, `async _try_llama_fallback_strategy()`, `async _handle_llama_parsing_error()`, `async generate_paragraph_with_llama()`, `async warm_up()` + +### `LLMService` + +**File:** `backend/services/llm/llm_service.py` +**Purpose:** LLM Service for entity extraction and paragraph generation. + +| Instance attribute | Initialized from | +| --- | --- | +| `anthropic_client` | `AnthropicLLMClient()` | +| `azure_client` | `AzureLLMClient()` | +| `gemini_client` | `GeminiLLMClient()` | +| `llama_client` | `LlamaLLMClient()` | +| `macbook_client` | `MacbookLLMClient()` | +| `timeout_log_dir` | `Path(__file__).resolve().parents[2] / "output" / "timeout_logs"` | +| `timeout_log_file` | `self.timeout_log_dir / "timeout_log.txt"` | +| `vllm_client` | `VLLMClient()` | + +**Public methods:** `async _call_with_timeout_logging()`, `async extract_entities_from_markdown()`, `async extract_content_from_image()`, `async generate_paragraph()` + +### `MacbookLLMClient` + +**File:** `backend/services/llm/macbook.py` + +| Instance attribute | Initialized from | +| --- | --- | +| `_fail_count` | `0` | +| `_tags_cache` | `[]` | +| `_tags_cache_ts` | `0.0` | +| `_tags_cache_ttl_seconds` | `120` | +| `base_url` | `(self.base_url or "").rstrip("/")` | +| `base_url` | `os.environ.get("MACBOOK_LLM_BASE_URL", "").rstrip("/")` | +| `base_url` | `self._load_base_url_from_secrets()` | +| `disable_reasoning` | `disable_reasoning_env not in [ "false", "0", "no", "off", ]` | +| `disabled` | `not bool(self.base_url)` | +| `initial_backoff` | `float(os.environ.get("MACBOOK_INITIAL_BACKOFF", 1.0))` | +| `max_attempts` | `int(os.environ.get("MACBOOK_MAX_ATTEMPTS", 2))` | +| `max_backoff` | `float(os.environ.get("MACBOOK_MAX_BACKOFF", 8.0))` | +| `per_attempt_timeout` | `max( 1800.0, float(os.environ.get("MACBOOK_PER_ATTEMPT_TIMEOUT", 1800.0)) )` | +| `total_retry_cap` | `max( 1800.0, float(os.environ.get("MACBOOK_TOTAL_RETRY_CAP", 1800.0)) )` | + +**Public methods:** `async fetch_available_models()`, `async check_health()`, `async _call_macbook_api()`, `async extract_entities_with_macbook()`, `async generate_paragraph_with_macbook()` + +### `MacbookRequestQueue` + +**File:** `backend/services/llm/macbook_queue.py` +**Purpose:** FIFO queue with a single worker for serializing Macbook LLM requests. + +| Instance attribute | Initialized from | +| --- | --- | +| `_queue` | `asyncio.Queue()` | +| `_started` | `False` | +| `_total_enqueued` | `0` | +| `_total_processed` | `0` | +| `_worker_task` | `None` | + +**Public methods:** `async _worker()`, `async enqueue()`, `pending_count()`, `stats()` + +### `VLLMClient` + +**File:** `backend/services/llm/vllm.py` +**Purpose:** OpenAI-compatible client for VLLM inference servers. + +| Instance attribute | Initialized from | +| --- | --- | +| `_static_models` | `[]` | +| `_static_models` | `json.loads(models_json)` | +| `api_key` | `os.environ.get("VLLM_API_KEY", "EMPTY")` | +| `base_url` | `os.environ.get("VLLM_BASE_URL", "").rstrip("/")` | +| `disabled` | `not bool(self.base_url)` | + +**Public methods:** `async fetch_available_models()`, `async check_health()`, `async _call_vllm_api()`, `async extract_entities_with_vllm()`, `async generate_paragraph_with_vllm()` + +### `SessionService` + +**File:** `backend/services/session/session_service.py` +**Purpose:** Service for managing user sessions with database storage + +| Instance attribute | Initialized from | +| --- | --- | +| `_doc_cache` | `{}` | +| `_file_service` | `None` | +| `db` | `get_db_service()` | + +**Public methods:** `file_service()`, `create_session()`, `get_session()`, `list_sessions()`, `update_session()`, `delete_session()`, `add_extraction_result()`, `add_extraction_result_fast()`, `add_evaluation_result()`, `add_evaluation_result_fast()`, `clear_cache()`, `share_session()`, `unshare_session()`, `list_shared_sessions()`, `get_session_for_shared_view()`, `async build_restore_view()` + +### `BlobStorageClient` + +**File:** `backend/services/storage/blob_storage.py` +**Purpose:** Async Azure Blob Storage client. + +| Instance attribute | Initialized from | +| --- | --- | +| `_container` | `container_name` | +| `_service` | `BlobServiceClient.from_connection_string(connection_string)` | + +**Public methods:** `async _ensure_container()`, `async upload_bytes()`, `async download_bytes()`, `async exists()`, `async upload_directory()`, `async list_blobs_with_prefix()`, `from_env()` + +### `CostTracker` + +**File:** `backend/services/telemetry/cost_tracker.py` + +| Instance attribute | Initialized from | +| --- | --- | +| `_db_service` | `None` | +| `_pricing` | `self._load_pricing()` | +| `_sessions` | `{}` | + +**Public methods:** `estimate_call_cost()`, `record_call()`, `record_batch()`, `get_session_metrics()`, `load_session_metrics_from_db()`, `clear_session()` + +### `FolderService` + +**File:** `backend/services/templates/folder_service.py` +**Purpose:** Service for managing template folders. + +| Instance attribute | Initialized from | +| --- | --- | +| `group_service` | `get_group_service()` | + +**Public methods:** `list_folders()`, `create_folder()`, `rename_folder()`, `delete_folder()` + +### `TemplateService` + +**File:** `backend/services/templates/template_service.py` +**Purpose:** Service for managing prompt templates + +| Instance attribute | Initialized from | +| --- | --- | +| `group_service` | `get_group_service()` | + +**Public methods:** `create_template()`, `get_template()`, `list_templates()`, `update_template()`, `delete_template()`, `get_version_history()`, `revert_to_version()`, `fork_template()`, `change_scope()`, `set_immutable()`, `set_permission()`, `get_permissions()`, `remove_permission()` + +## Utilities + +### `PDFBBoxVisualizer` + +**File:** `backend/utils/pdf_bbox_visualizer.py` +**Purpose:** Visualizes bounding boxes from Azure Document Intelligence on PDFs + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `COLORS` | | `{ "word": (0.2, 0.6, 1.0), # Light blue "line": (0.0, 0.5, 0.8), # Blue "paragraph": (0.5, 0.0, 0.5), # Purple "table": (1.0, 0.5, 0.0), # Orange "table_cell": (1.0, 0.7, 0.3), ...` | declared field | + +**Public methods:** `visualize_words()`, `visualize_lines()`, `visualize_paragraphs()`, `visualize_tables()`, `visualize_figures()`, `visualize_selection_marks()`, `visualize_all()`, `save()`, `close()` + +## Scripts and developer tools + +### `DocumentAnalyzer` + +**File:** `backend/scripts/analyze_and_visualize_pdf.py` +**Purpose:** Analyzes documents using Azure Document Intelligence + +| Instance attribute | Initialized from | +| --- | --- | +| `client` | `DocumentIntelligenceClient( endpoint=self.endpoint, credential=AzureKeyCredential(self.key) )` | +| `endpoint` | `os.getenv("AZURE_DOC_INTELLIGENCE_ENDPOINT")` | +| `key` | `os.getenv("AZURE_DOC_INTELLIGENCE_KEY")` | + +**Public methods:** `analyze_pdf()` + +### `ModelListParser` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Purpose:** Parse model names from the CSV file. + +| Field | Type | Default / definition | Kind | +| --- | --- | --- | --- | +| `EXPECTED_MODELS` | | `[ # 3B - 4B "llama3.2:3b-instruct-fp16", "llama3.2:3b-instruct-q4_K_M", "MedAIBase/MedGemma1.5:4b", "phi4-mini:3.8b", "phi3.5:3.8b", "nemotron-mini:4b-instruct-q4_K_M", "nemotro...` | declared field | + +**Public methods:** `parse_model_list()` + +### `PromptLoader` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Purpose:** Load prompt template and test document. + +| Instance attribute | Initialized from | +| --- | --- | +| `config` | `config` | + +**Public methods:** `load()` + +### `MacbookClient` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Purpose:** Client for interacting with MacBook LLM API. + +| Instance attribute | Initialized from | +| --- | --- | +| `base_url` | `config.base_url.rstrip("/")` | +| `config` | `config` | +| `endpoint` | `config.api_endpoint` | +| `session` | `requests.Session()` | + +**Public methods:** `generate()`, `close()` + +### `ResponseEvaluator` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Purpose:** Evaluate model responses against ground truth. + +| Instance attribute | Initialized from | +| --- | --- | +| `ground_truth` | `ground_truth` | +| `weights` | `{ "study_author(s)": 0.12, "author_affiliations": 0.12, "study_title": 0.12, "publication_date": 0.10, "test_material": 0.12, "vehicle_or_solvent_used": 0.12, "dose_le...` | + +**Public methods:** `compute_score()` + +### `ExcelReporter` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Purpose:** Generate Excel report from test results. + +| Instance attribute | Initialized from | +| --- | --- | +| `config` | `config` | + +**Public methods:** `generate()` + +### `ModelTestRunner` + +**File:** `backend/scripts/evaluate_macbook_models.py` +**Purpose:** Orchestrates the model testing process. + +| Instance attribute | Initialized from | +| --- | --- | +| `client` | `MacbookClient(config)` | +| `config` | `config` | +| `reporter` | `ExcelReporter(config)` | + +**Public methods:** `run()` + +## Other backend classes + +### `Base` + +**File:** `backend/models/base.py` +**Base classes:** `DeclarativeBase` +**Purpose:** Base class for all SQLAlchemy models + +_No class-level data fields or constructor instance attributes are defined._ + From d6a0a3128c7e0c51abde0ff2ceca5dbfebd1e9ec Mon Sep 17 00:00:00 2001 From: Sam120204 Date: Wed, 20 May 2026 12:26:03 -0400 Subject: [PATCH 04/12] Add README.md for Science-GPT summarization tool Introduced a comprehensive README file detailing the Science-GPT web application, its purpose, target users, functionality, testing methodology, and evaluation results. This documentation aims to provide a clear overview of the tool's capabilities and the benefits it offers to scientific reviewers in processing large documents. --- docs/README.md | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..057f87d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,121 @@ +# Science-GPT Summarization Tool Overview + +Science-GPT is a web application that helps scientific reviewers work through large scientific documents more quickly. It can read uploaded PDF studies, extract key information, generate structured summaries, and let reviewers compare results from different AI models. + +This page is a high-level overview for readers who want to understand what the project does, who it is for, and what was learned during testing. + +## Where to go next + +- Read this page first for a plain-language project overview. +- See [`backend/`](backend/) for detailed backend architecture and implementation notes. +- See [`images/`](images/) for application screenshots and workflow diagrams. + + +## What problem does this solve? + +Scientific reviewers at PMRA review large volumes of technical documents, including toxicology studies, epidemiology studies, scientific articles, and grey literature. This work is important for pesticide and chemical safety decisions, but it is also time-consuming. + +A major part of the review process involves finding information in documents, extracting key details, organizing results, and writing summaries. Science-GPT was built to test whether AI can help with those tasks while still keeping scientific experts in control. + +The goal is not to replace reviewers. The goal is to help reviewers get to a strong first draft faster, so they can spend more time checking evidence, applying scientific judgement, and making decisions. + +## Who is this tool for? + +The main users are scientific reviewers and evaluators who review pesticide and chemical safety information. + +During Stream 2 testing, the project focused on two groups: + +- **Toxicology reviewers**, who review animal toxicity studies and summarize effects such as maternal and fetal outcomes. +- **Epidemiology reviewers**, who review human health studies and summarize study design, exposure, outcomes, measures of association, strengths, limitations, and risk-of-bias information. + +The tool may also be useful to project teams, managers, and technical partners who need to understand how AI could support scientific review workflows. + +## What does the tool do? + +At a high level, Science-GPT supports this workflow: + +1. A reviewer uploads one or more PDF studies. +2. The tool converts the PDF into structured text that AI models can use. +3. The reviewer chooses what information should be extracted. +4. One or more AI models extract the requested information. +5. The reviewer compares the outputs, checks the source evidence, and validates the result. +6. The output can be exported for reporting or further analysis. + +The application supports multiple models, including models from OpenAI, Google, Anthropic, Meta, and locally hosted open-source models. This makes it possible to compare model quality, speed, and cost on the same task. + +## How was it tested? + +Testing was led by Subject Matter Experts (SMEs). SMEs selected studies, created expected answers, reviewed AI outputs, and scored how well the models performed. + +The team tested two main use cases: + +### In vivo developmental toxicity studies + +Toxicology SMEs selected 10 published developmental toxicity studies. They manually created “ground truth” summaries, which acted as the reference answers for scoring AI outputs. + +The AI models were tested on two levels of summary: + +- **Level 0 summaries:** simple study information such as author, title, publication date, and journal. +- **Level 1 summaries:** more detailed toxicology information such as test material, animal species, dose levels, route of administration, maternal effects, and fetal or offspring effects. + +### Epidemiology studies + +Epidemiology SMEs tested the tool on studies with different designs, including cohort, case-control, and biomonitoring studies. + +The tool was used to extract information such as participant numbers, pesticide of interest, exposure measurement, health outcomes, measures of association, study conclusions, strengths, limitations, and risk-of-bias information. + +This use case was more difficult because epidemiology studies often contain complex tables, different exposure measures, different study designs, and more variation in how results are reported. + +## How was performance measured? + +The main performance questions were: + +- **Was the answer correct?** +- **Was the answer complete?** +- **How much did it cost?** +- **How long did it take?** +- **Was the output useful to the reviewer?** + +For the toxicology testing, SMEs scored model outputs for correctness and completeness against the ground truth summaries. For epidemiology testing, SMEs also looked closely at whether the outputs were readable, concise, and useful for real review work. + +## Evaluation results + +A major part of Stream 2 was evaluation: testing the tool with SMEs, comparing model outputs, and measuring whether the results were useful for real scientific review work. + +### Toxicology results + +For the toxicology use case, SMEs tested the tool on 10 in vivo developmental toxicity studies. The models were scored against SME-created reference summaries. + +The strongest overall model was **Gemini 2.5 Pro**. It performed best across both simple study information and more detailed toxicology summary fields. + +| Model result | What it means | +| --- | --- | +| **Gemini 2.5 Pro was the top performer** | It had the best overall correctness and completeness scores for the toxicology studies. | +| **Simple fields were easier** | Most models could extract information such as author, title, publication date, and journal. | +| **Detailed scientific fields were harder** | More complex fields, such as maternal effects and fetal or offspring effects, showed bigger differences between models. | +| **Claude Sonnet 4.5 and Claude Opus 4.1 also performed strongly** | They were good options for complex summaries, but were more expensive than Gemini 2.5 Pro. | + +The testing also showed a large time-saving opportunity. SMEs spent about **300 minutes** manually creating summaries for 10 studies. Using the tool with the best-performing model and fastest document processing setup, the same set of draft summaries could be generated in slightly over **5 minutes**, before SME verification. + +### Epidemiology results + +For the epidemiology use case, the results were more mixed because epidemiology studies are less standardized and often include complex tables, different study designs, and detailed numerical results. + +The main findings were: + +| Model result | What it means | +| --- | --- | +| **Gemini 2.5 Pro and GPT-5.2 were strong general choices** | They performed well across many extraction tasks. | +| **Claude Sonnet 4.5 was best for complex table extraction** | It was especially useful when the reviewer needed numerical results organized into tables. | +| **Lower-cost models worked for simple fields** | They could handle straightforward information, but were less reliable for complex summaries and tables. | +| **The best model depended on the task** | No single model was best for every epidemiology extraction task. | + +In one applied epidemiology risk-of-bias test, the report estimated that reviewing 20 studies manually would take about **40 hours**. With AI-generated summaries plus SME verification, the work was estimated at about **11 hours total**. + +### Overall evaluation conclusion + +The evaluation showed that Science-GPT can create useful first drafts much faster than a fully manual workflow. However, the outputs still need SME review. The tool is most valuable when it helps reviewers quickly collect and organize information while leaving the final scientific judgement with the reviewer. + +The best default model from the testing was **Gemini 2.5 Pro**, especially for the toxicology use case. For epidemiology, **Gemini 2.5 Pro**, **GPT-5.2**, and **Claude Sonnet 4.5** were all useful, depending on whether the task required general extraction, lower cost, or stronger table generation. + +Overall, Stream 2 showed that Science-GPT can help scientific reviewers work faster while keeping expert judgement at the centre of the process. From 7673bc109f05380b57828d27c0835de3b7324fcb Mon Sep 17 00:00:00 2001 From: Jordan Leis Date: Fri, 22 May 2026 13:57:01 +0000 Subject: [PATCH 05/12] Add documentation for Batch Results feature and enhance frontend README - Introduced `14-batch-results.md` detailing the Batch Results page, including UI sections, table structure, fuzzy search, row detail modal, Excel export, state management, and API calls. - Updated `README.md` to include an overview of the Batch Results feature and its integration within the Science-GPT frontend. - Created `component-index.md` to catalog shared components used across multiple pages. - Added `hooks-contexts.md` to document custom hooks and context providers utilized in the frontend. - Defined key TypeScript interfaces in `types-interfaces.md` for better understanding of the data structures used. - Established a glossary to clarify project-specific terms and Azure services relevant to the Science-GPT project. --- docs/INDEX.md | 84 ++++++ docs/backend/01-architecture.md | 2 + docs/backend/02-api-surface.md | 2 + docs/backend/03-data-models.md | 2 + docs/backend/04-schemas.md | 2 + docs/backend/05-document-processing.md | 2 + docs/backend/06-llm-layer.md | 2 + docs/backend/07-extraction-flow.md | 2 + docs/backend/08-evaluation-flow.md | 2 + docs/backend/09-session-sharing-groups.md | 2 + docs/backend/10-template-system.md | 2 + .../backend/11-auth-security-observability.md | 2 + docs/frontend/01-app-shell.md | 167 ++++++++++++ docs/frontend/02-auth.md | 188 +++++++++++++ docs/frontend/03-upload.md | 111 ++++++++ docs/frontend/04-processing.md | 117 ++++++++ docs/frontend/05-study-config.md | 116 ++++++++ docs/frontend/06-extraction.md | 177 +++++++++++++ docs/frontend/07-evaluation.md | 166 ++++++++++++ docs/frontend/08-simplified-flow.md | 111 ++++++++ docs/frontend/09-chat.md | 99 +++++++ docs/frontend/10-session-history.md | 120 +++++++++ docs/frontend/11-templates.md | 172 ++++++++++++ docs/frontend/12-groups.md | 129 +++++++++ docs/frontend/13-executive-mode.md | 70 +++++ docs/frontend/14-batch-results.md | 107 ++++++++ docs/frontend/README.md | 184 +++++++++++++ docs/frontend/appendices/component-index.md | 181 +++++++++++++ docs/frontend/appendices/hooks-contexts.md | 199 ++++++++++++++ docs/frontend/appendices/types-interfaces.md | 249 ++++++++++++++++++ docs/glossary.md | 123 +++++++++ 31 files changed, 2892 insertions(+) create mode 100644 docs/INDEX.md create mode 100644 docs/frontend/01-app-shell.md create mode 100644 docs/frontend/02-auth.md create mode 100644 docs/frontend/03-upload.md create mode 100644 docs/frontend/04-processing.md create mode 100644 docs/frontend/05-study-config.md create mode 100644 docs/frontend/06-extraction.md create mode 100644 docs/frontend/07-evaluation.md create mode 100644 docs/frontend/08-simplified-flow.md create mode 100644 docs/frontend/09-chat.md create mode 100644 docs/frontend/10-session-history.md create mode 100644 docs/frontend/11-templates.md create mode 100644 docs/frontend/12-groups.md create mode 100644 docs/frontend/13-executive-mode.md create mode 100644 docs/frontend/14-batch-results.md create mode 100644 docs/frontend/README.md create mode 100644 docs/frontend/appendices/component-index.md create mode 100644 docs/frontend/appendices/hooks-contexts.md create mode 100644 docs/frontend/appendices/types-interfaces.md create mode 100644 docs/glossary.md diff --git a/docs/INDEX.md b/docs/INDEX.md new file mode 100644 index 0000000..a362c83 --- /dev/null +++ b/docs/INDEX.md @@ -0,0 +1,84 @@ +# Science-GPT Documentation Index + +Start here. Every document in this repository is listed below with its audience and a short description of what it covers. + +**New to the project?** Read the [Product Overview](README.md) first, then the [Glossary](glossary.md), then the entry point for the area you're working in (backend or frontend). + +--- + +## Start here + +| Document | Audience | What it covers | +|---|---|---| +| [Product Overview](README.md) | Everyone | What the tool does, who uses it (PMRA reviewers), how it was tested, SME evaluation results | +| [Glossary](glossary.md) | Everyone | Plain-language definitions for all Azure services, tools, and project-specific terms | + +--- + +## Backend + +| Document | Audience | What it covers | +|---|---|---| +| [Backend TDD — Entry Point](backend/README.md) | Engineers | Master index for the full backend technical design; start here for anything backend | +| [01 — Architecture](backend/01-architecture.md) | Engineers | Service boundaries, package responsibilities, five major data flows, dependency direction | +| [02 — API Surface](backend/02-api-surface.md) | Engineers | All 14 routers documented with every endpoint, request/response shapes, auth, and exceptions | +| [03 — Data Models](backend/03-data-models.md) | Engineers | All 13 ORM models with field-level types, constraints, indexes, and migration notes | +| [04 — Schemas](backend/04-schemas.md) | Engineers | All Pydantic request/response schemas with design notes | +| [05 — Document Processing](backend/05-document-processing.md) | Engineers | Upload, SHA-256 deduplication, parser selection, Azure DI and Docling pipelines, blob storage, bounding boxes | +| [06 — LLM Layer](backend/06-llm-layer.md) | Engineers | All 7 provider clients, dispatch table, timeout budgets, structured output handling, cost tracking | +| [07 — Extraction Flow](backend/07-extraction-flow.md) | Engineers | Entity extraction end-to-end: concurrency model, provider dispatch, reference/bbox matching, session persistence | +| [08 — Evaluation Flow](backend/08-evaluation-flow.md) | Engineers | G-Eval scoring, combined JSON parsing, background job lifecycle, cancellation, cost tracking | +| [09 — Sessions, Sharing & Groups](backend/09-session-sharing-groups.md) | Engineers | Session lifecycle, restore-view construction, group membership rules, shared session read path | +| [10 — Template System](backend/10-template-system.md) | Engineers | Template CRUD, version snapshots, fork, scope change, access-control algorithms, folder operations | +| [11 — Auth, Security & Observability](backend/11-auth-security-observability.md) | Engineers | Better Auth session validation, auth proxy, CORS, secrets loading, structlog, Prometheus, OpenTelemetry, CostTracker | + +### Backend appendices + +| Document | What it covers | +|---|---| +| [API Endpoint Index](backend/appendices/api-endpoint-index.md) | Compact table of every route — method, path, purpose | +| [Class Index](backend/appendices/class-index.md) | All backend classes organised by package | +| [Class Reference](backend/appendices/class-reference.md) | Field-level reference for ORM models, Pydantic schemas, and service classes | +| [Data Flow Diagrams](backend/appendices/data-flow-diagrams.md) | 15 text-format diagrams covering every major request flow | +| [Risks, Assumptions & Testing](backend/appendices/risks-assumptions-testing.md) | Runtime/data/provider assumptions, risk table with mitigations, 13-category test strategy, 12-step smoke test | + +--- + +## Frontend + +| Document | Audience | What it covers | +|---|---|---| +| [Frontend TDD — Entry Point](frontend/README.md) | Engineers | Tech stack, page map, workflow diagram, architecture overview | +| [01 — App Shell](frontend/01-app-shell.md) | Engineers | `App.tsx` — `DocumentData` interface, step routing, `onComplete()` pattern, session persistence, navigation guards | +| [02 — Auth](frontend/02-auth.md) | Engineers | `LoginPage`, `AuthCallback`, `authUtils.ts` — OAuth flow, token lifecycle, `authenticatedFetch()`, visibility refresh | +| [03 — Upload](frontend/03-upload.md) | Engineers | Workflow step 1 — file upload, SHA-256 deduplication, auto-processing, parser selection | +| [04 — Processing](frontend/04-processing.md) | Engineers | Workflow step 2 — parsed content inspection, re-processing, PDF bounding box viewer | +| [05 — Study Config](frontend/05-study-config.md) | Engineers | Workflow step 3 — study type, entity editor, template loading, model selection | +| [06 — Extraction](frontend/06-extraction.md) | Engineers | Workflow step 4 — concurrent entity extraction, PDF reference highlighting, multi-model comparison, in-place editing | +| [07 — Evaluation](frontend/07-evaluation.md) | Engineers | Workflow step 5 — G-Eval metrics, background jobs, human score overrides, Excel export | +| [08 — Simplified Flow](frontend/08-simplified-flow.md) | Engineers | One-click pipeline, `useSimplifiedPipeline` hook, batched extraction, stage progression | +| [09 — Chat](frontend/09-chat.md) | Engineers | Freeform document Q&A, multi-document context, message ratings | +| [10 — Session History](frontend/10-session-history.md) | Engineers | Browse, restore, share, and delete sessions; shared sessions (read-only) | +| [11 — Templates](frontend/11-templates.md) | Engineers | Template CRUD, version history, fork, scope change, folder organisation | +| [12 — Groups](frontend/12-groups.md) | Engineers | Group lifecycle, member roles, add/remove members, user search | +| [13 — Executive Mode](frontend/13-executive-mode.md) | Engineers | Standalone summary generation without structured entity review | +| [14 — Batch Results](frontend/14-batch-results.md) | Engineers | Cross-file results table, fuzzy search, column visibility, Excel export | + +### Frontend appendices + +| Document | What it covers | +|---|---| +| [Component Index](frontend/appendices/component-index.md) | All shared components with props and usage | +| [Hooks & Contexts](frontend/appendices/hooks-contexts.md) | All custom hooks and `ThemeContext` with exported APIs | +| [Types & Interfaces](frontend/appendices/types-interfaces.md) | Key TypeScript interfaces: `DocumentData`, `Entity`, `Template`, `Group`, and more | + +--- + +## Deployment & operations + +| Document | Audience | What it covers | +|---|---|---| +| [GitHub Auth Setup](superpowers/setup-github-auth.md) | DevOps | 10-step guide to configuring GitHub Enterprise Cloud OAuth | +| [Migration Guide](superpowers/migration-guide.md) | Engineers | Supabase → Azure Postgres + Better Auth migration history | +| [Deployment Plan](superpowers/plans/dockerize-and-deploy.md) | DevOps | Azure Container Apps architecture, CI/CD pipeline, provisioning record | +| [Logging Stack](../logging/README.md) | DevOps | LGTM stack setup: Grafana, Loki, Tempo, Prometheus; NSG firewall rules | diff --git a/docs/backend/01-architecture.md b/docs/backend/01-architecture.md index 80236c4..34799b5 100644 --- a/docs/backend/01-architecture.md +++ b/docs/backend/01-architecture.md @@ -1,5 +1,7 @@ # Backend Architecture +> *Science-GPT's backend is a Python FastAPI service that sits between the frontend and everything else — the database, blob storage, document parsers, and AI providers. This document explains how those pieces connect: which packages own which responsibilities, how a request travels from the browser to the database and back, and which infrastructure assumptions the code relies on.* + This document describes the high-level backend architecture, module boundaries, runtime setup, and major control/data flows. Lower-level class, endpoint, and schema details are split into the linked module documents. ## 1. Application entry point diff --git a/docs/backend/02-api-surface.md b/docs/backend/02-api-surface.md index 9e35223..ce51cc7 100644 --- a/docs/backend/02-api-surface.md +++ b/docs/backend/02-api-surface.md @@ -1,5 +1,7 @@ # API Surface Technical Design +> *Every action the frontend can take — uploading a file, running an extraction, fetching a session — goes through one of the 14 API routers documented here. This is the contract between the frontend and the backend: what URLs exist, what they expect, what they return, and what can go wrong. If you're building a new frontend feature or debugging an unexpected response, start here.* + This document describes the backend FastAPI API surface by router. It focuses on interface communication: paths, methods, request/response structures, authentication, service dependencies, and important exceptions. For a compact route-only list, see [appendices/api-endpoint-index.md](appendices/api-endpoint-index.md). diff --git a/docs/backend/03-data-models.md b/docs/backend/03-data-models.md index a7ebd34..9177eb7 100644 --- a/docs/backend/03-data-models.md +++ b/docs/backend/03-data-models.md @@ -1,5 +1,7 @@ # Data Models Technical Design +> *Everything a reviewer does — uploading a file, running an extraction, saving a session, sharing with a group — gets written to PostgreSQL through one of these 13 ORM models. This document describes every table: what each field stores, its type and constraints, and any gotchas between what the ORM assumes and what the database actually enforces. Read this if you're adding a new feature that needs a new table, or if you're debugging unexpected data in the database.* + This document describes the backend physical data model implemented with SQLAlchemy models in `backend/models/` and Alembic migrations in `backend/alembic/`. ## Visual overview diff --git a/docs/backend/04-schemas.md b/docs/backend/04-schemas.md index a9fa9b4..da2fbe5 100644 --- a/docs/backend/04-schemas.md +++ b/docs/backend/04-schemas.md @@ -1,5 +1,7 @@ # Pydantic Schemas Technical Design +> *Pydantic schemas are the typed contracts that sit at the boundary between the frontend and the backend — they define exactly what JSON the API accepts and what it returns. This document lists every schema class used for request bodies and response payloads, with notes on design decisions like why certain fields use free strings instead of enums. If the frontend is sending data that the backend rejects, or you're not sure what shape a response will have, start here.* + This document describes request and response schemas in `backend/schemas/` and local router-level schemas in `backend/api/*`. These schemas define the API-facing data structures separate from SQLAlchemy ORM models. ## 1. Shared enum schemas diff --git a/docs/backend/05-document-processing.md b/docs/backend/05-document-processing.md index fd6e1d1..1975372 100644 --- a/docs/backend/05-document-processing.md +++ b/docs/backend/05-document-processing.md @@ -1,5 +1,7 @@ # Document Processing Technical Design +> *When a reviewer uploads a PDF, the backend needs to convert it into structured text, figures, and tables before an AI model can work with it. This document traces that entire journey — from the moment the file arrives, through SHA-256 deduplication and blob storage, into either the Azure Document Intelligence or Docling parser, and finally into the "document view" that the frontend uses to display results and restore sessions. It also covers bounding boxes: how the coordinates of text passages are preserved so the PDF viewer can highlight exactly where an extracted answer came from.* + This document describes upload, storage, parsing, artifact access, bounding-box normalization, figure/table handling, and document-view construction. ## 1. Scope diff --git a/docs/backend/06-llm-layer.md b/docs/backend/06-llm-layer.md index 9157c11..f68b0d1 100644 --- a/docs/backend/06-llm-layer.md +++ b/docs/backend/06-llm-layer.md @@ -1,5 +1,7 @@ # LLM Provider Layer Technical Design +> *Science-GPT is not locked to a single AI provider. This document describes how the backend routes extraction and evaluation requests across seven different providers — Azure OpenAI, Gemini, Anthropic, Llama, Macbook (local), and vLLM — through a common interface. It covers how each provider client works, how timeouts and retries are handled, how structured outputs are extracted from different response formats, and how every call's cost and token usage is recorded.* + This document describes the provider-routing layer implemented under `backend/services/llm/`. It covers classes, request/response contracts, timeout/retry behavior, concurrency controls, and cost-tracking integration. ## 1. Scope diff --git a/docs/backend/07-extraction-flow.md b/docs/backend/07-extraction-flow.md index 7c93e96..77164c0 100644 --- a/docs/backend/07-extraction-flow.md +++ b/docs/backend/07-extraction-flow.md @@ -1,5 +1,7 @@ # Entity Extraction Flow Technical Design +> *Entity extraction is the core operation of Science-GPT: given a document and a list of fields to extract, ask an LLM to find each value and point to where it found it in the document. This document explains how that works end-to-end — how the markdown is loaded, how up to 48 LLM calls run concurrently through a semaphore, how the answers are matched back to bounding boxes on the PDF pages, and how results are persisted to the reviewer's session so nothing is lost if the browser closes.* + This document describes how the backend extracts structured entity answers from processed documents, attaches reference/bounding-box data, records cost, and persists results into sessions. ## 1. Scope diff --git a/docs/backend/08-evaluation-flow.md b/docs/backend/08-evaluation-flow.md index a025e1e..bfc54b4 100644 --- a/docs/backend/08-evaluation-flow.md +++ b/docs/backend/08-evaluation-flow.md @@ -1,5 +1,7 @@ # Evaluation Flow Technical Design +> *After entities are extracted, reviewers can ask a second LLM to judge how good the answers are — scoring them for correctness, completeness, relevance, and safety. This document explains how that works: the G-Eval scoring algorithm, how the backend tries to parse scores from the judge's response (with several fallback strategies for malformed output), how long-running evaluation jobs are managed in the background so the frontend can poll for progress, and how individual evaluations are cancelled without losing work already done.* + This document describes LLM-as-a-judge evaluation, DeepEval metric construction, batch evaluation, background job execution, cancellation, persistence, and cost tracking. ## 1. Scope diff --git a/docs/backend/09-session-sharing-groups.md b/docs/backend/09-session-sharing-groups.md index 3659c68..488a6e6 100644 --- a/docs/backend/09-session-sharing-groups.md +++ b/docs/backend/09-session-sharing-groups.md @@ -1,5 +1,7 @@ # Sessions, Sharing, and Groups Technical Design +> *A session is a saved record of a reviewer's work — their uploaded files, extracted entities, and evaluation scores. This document covers the full session lifecycle: how sessions are created, updated, and restored; how the "restore view" reconstructs a complete frontend state from the database; and how sessions are shared with colleagues through groups. It also covers group membership rules — who can add members, who can change roles, and what a shared session recipient can and cannot do.* + This document describes workflow sessions, DB-to-API conversion, restore-view generation, shared sessions, group membership, and authorization rules. ## 1. Scope diff --git a/docs/backend/10-template-system.md b/docs/backend/10-template-system.md index 80166fe..372a26a 100644 --- a/docs/backend/10-template-system.md +++ b/docs/backend/10-template-system.md @@ -1,5 +1,7 @@ # Template System Technical Design +> *Templates are reusable sets of entity prompts that reviewers can save, version, and share. Instead of re-typing the same 20 extraction fields for every toxicology study, a reviewer saves them as a template once and loads it in two clicks. This document explains how templates are stored, how every edit is automatically versioned (with full revert capability), how templates are scoped to an individual, a group, or all users, and how the access-control rules determine who can read or edit a given template.* + This document describes the prompt template workspace: templates, folders, scopes, versions, immutability, forks, and permissions. ## 1. Scope diff --git a/docs/backend/11-auth-security-observability.md b/docs/backend/11-auth-security-observability.md index 09c3c59..e347e6b 100644 --- a/docs/backend/11-auth-security-observability.md +++ b/docs/backend/11-auth-security-observability.md @@ -1,5 +1,7 @@ # Auth, Security, and Observability Technical Design +> *Three cross-cutting concerns that touch every part of the backend: who is allowed in (authentication via Better Auth and GitHub OAuth), what they're allowed to do (authorization per endpoint and resource), and what the system records about its own behaviour (structured logs, Prometheus metrics, OpenTelemetry traces, and per-session cost tracking). This document covers all three — including the security risks table, CORS configuration, secrets loading, and how the CostTracker updates the database without blocking the request loop.* + This document describes authentication, authorization boundaries, security-sensitive behaviors, logging, metrics, tracing, and cost/session telemetry. ## 1. Scope diff --git a/docs/frontend/01-app-shell.md b/docs/frontend/01-app-shell.md new file mode 100644 index 0000000..688a42b --- /dev/null +++ b/docs/frontend/01-app-shell.md @@ -0,0 +1,167 @@ +# App Shell + +> *`App.tsx` is the spine of the entire frontend. It owns all workflow state, controls which page is visible, persists sessions across browser reloads, and guards against accidental navigation away from in-progress work. Every page component is a child of App.tsx — pages read their inputs from it and report their outputs back to it via callbacks.* + +## 1. Overview + +`App.tsx` (~2100 lines) does four things: + +1. **Owns `DocumentData`** — the single object that accumulates everything a reviewer has done in a session: uploaded files, processing results, entity configs, extraction results, evaluation results. +2. **Controls navigation** — a `currentStep` string determines which page renders. There is no URL router; the browser address bar does not change between steps. +3. **Persists state** — saves `currentStep` and `sessionId` to `localStorage` so a reload restores where the reviewer left off. +4. **Guards navigation** — prevents accidental navigation away from a step with in-flight API calls or unsaved results. + +--- + +## 2. The `DocumentData` interface + +`DocumentData` is the central data structure. It is passed as a prop to every page and updated via the `onComplete()` callback pattern. + +```typescript +interface DocumentData { + // --- Upload step --- + file: File | null; // Primary uploaded file (single-file path) + fileId?: string; // SHA-256 hash of the primary file + uploadResult?: any; // Raw response from POST /api/upload + parser: string; // Selected processor: "azure" | "docling" | "auto" + uploadedFiles?: UploadedFile[]; // All uploaded files (multi-file path) + + // --- Processing step --- + extractedText: string; // Processed markdown from primary file + annotatedOutput: string; // Enhanced markdown (with figure summaries injected) + + // --- Study config step --- + studyType: string; // "toxicology" | "epidemiology" | "custom" + summaryPrompt?: string; // System prompt for paragraph summary generation + selectedModel: string; // Primary model identifier + selectedModels?: string[]; // All selected models (multi-model extraction) + temperature?: number; // Model temperature setting + entities: Entity[]; // Entity definitions + their extraction results + + // --- Extraction step (populated per entity) --- + // Entity.extracted, Entity.answer, Entity.references, + // Entity.extractionsByModel, Entity.duration, etc. + // See Entity interface in appendices/types-interfaces.md + + // --- Session --- + sessionId?: string; // ID of the saved AppSession in the DB + + // --- Config --- + filesConfig?: FilesConfig; // Per-file parser and model settings + evaluationConfig?: EvaluationConfig; // Evaluation metric and model settings +} +``` + +`UploadedFile` extends the single-file fields to support multi-document sessions. Each entry carries its own `fileId`, `extractedText`, `entities`, and so on. + +--- + +## 3. Routing and navigation + +### Step identifiers + +Navigation is driven by a `currentStep` string, not a URL path. The full set: + +| Step ID | Page rendered | Role | +|---|---|---| +| `login` | `LoginPage` | Auth entry point | +| `auth_callback` | `AuthCallback` | OAuth redirect handler | +| `upload` | `UploadPage` | Workflow step 1 | +| `processing` | `ProcessingPage` | Workflow step 2 | +| `study_selection` | `BatchStudySelectionPage` | Workflow step 3 | +| `extraction` | `EntityExtractionPage` | Workflow step 4 | +| `evaluation` | `EvaluationPage` | Workflow step 5 | +| `simplified` | `SimplifiedFlowPage` | One-click pipeline | +| `chat` | `ChatPage` | Document Q&A | +| `executive` | `ExecutiveModePage` | Executive summary | +| `history` | `SessionHistoryPage` | Session browser | +| `templates` | `TemplateWorkspacePage` | Template manager | +| `groups` | `GroupManagementPage` | Group manager | + +### Tool overlay navigation + +Tool overlays (`chat`, `executive`, `history`, `templates`, `groups`) can be opened from any workflow step. When the reviewer enters an overlay, App.tsx saves the current step in `previousWorkflowStep`. The overlay's Back button calls `setCurrentStep(previousWorkflowStep)` to return exactly where they were. + +### Completed steps + +App.tsx derives a `completedSteps` set from the contents of `DocumentData`: + +- `upload` is complete when `documentData.uploadedFiles` or `documentData.fileId` is populated. +- `processing` is complete when `documentData.extractedText` is non-empty. +- `study_selection` is complete when `documentData.entities` has at least one entry. +- `extraction` is complete when any entity has an `extracted` or `answer` value. +- `evaluation` is complete when any entity has `evaluationResults`. + +Reviewers can only navigate forward to the next step or backward to a completed step. Skipping ahead is blocked. + +--- + +## 4. The `onComplete()` callback pattern + +Pages do not write to global state directly. Instead, each page receives an `onComplete` prop: + +```typescript +// Example: UploadPage reports its results back to App.tsx +onComplete: (updates: Partial) => void +``` + +When a page finishes its work, it calls `onComplete({ uploadedFiles: [...], parser: "azure" })`. App.tsx merges this into `documentData` with a shallow merge (top-level keys only) and advances `currentStep` to the next step. + +Pages also receive: + +| Prop | Type | Purpose | +|---|---|---| +| `documentData` | `DocumentData` | Read-only access to current workflow state | +| `onComplete` | `(updates) => void` | Report results and advance to next step | +| `onInvalidateDownstream` | `(fromStep) => void` | Mark later steps as stale | +| `onInFlightChange` | `(bool) => void` | Signal that an API call is in progress (blocks navigation) | +| `onNavigate` | `(step) => void` | Navigate to a specific step (used by overlays) | + +--- + +## 5. Downstream invalidation + +When a reviewer modifies an early step — for example, re-uploading a file on the Upload page — the work done in later steps may no longer be valid. App.tsx tracks this with a `staleDownstream` flag. + +When `onInvalidateDownstream("processing")` is called: +- `extractedText`, `annotatedOutput`, entities, extraction results, and evaluation results are cleared from `documentData`. +- The user sees a visual banner: "Results invalidated — re-run to update." +- Navigation to downstream steps is still permitted (for inspection), but the stale indicator makes clear the results are outdated. + +--- + +## 6. Session persistence + +On every meaningful state change, App.tsx writes two values to `localStorage`: + +``` +summarization_current_step → "extraction" +summarization_session_id → "abc123" +``` + +On app load, if both values are present: + +1. App.tsx calls `GET /api/sessions/{sessionId}/restore-view`. +2. The response re-hydrates `documentData` with the full session state. +3. `currentStep` is restored to the saved step. +4. The reviewer continues exactly where they left off. + +If the restore call fails (session deleted, token expired), App.tsx clears localStorage and starts a fresh session at `upload`. + +--- + +## 7. Navigation guards + +Two guards prevent accidental data loss: + +**In-flight guard** (`onInFlightChange`): When a page signals that an API call is in progress, App.tsx disables the step navigation bar. Clicking another step shows a confirmation dialog: "An extraction is in progress. Leave anyway?" + +**Unsaved changes guard** (`rerunConfirm`): When a reviewer tries to navigate backward from a step with results (e.g., going back from Extraction to Study Config), App.tsx shows: "Going back will clear your extraction results. Continue?" The reviewer must confirm before the state is cleared. + +--- + +## 8. Authentication flow in App.tsx + +On mount, App.tsx calls `getSession()` from `authUtils.ts`. If no session exists, it redirects to `login`. If a session exists, it fetches user info and proceeds to restore state from localStorage or start at `upload`. + +The session check is non-blocking — the app renders a loading spinner while `getSession()` is in flight and never flashes unauthenticated content to the user. diff --git a/docs/frontend/02-auth.md b/docs/frontend/02-auth.md new file mode 100644 index 0000000..bf4b5ff --- /dev/null +++ b/docs/frontend/02-auth.md @@ -0,0 +1,188 @@ +# Authentication + +> *Before a reviewer can use the tool they need to prove who they are. Science-GPT uses GitHub OAuth — the reviewer clicks "Sign in with GitHub", gets redirected to GitHub, approves access, and is sent back to the app with a session token. This document covers the login page, the OAuth callback handler, and `authUtils.ts` — the utility module that attaches auth tokens to every API call the app makes.* + +## 1. Components + +| File | Purpose | +|---|---| +| `components/LoginPage.tsx` | GitHub login UI | +| `components/AuthCallback.tsx` | Handles the redirect back from GitHub | +| `utils/authUtils.ts` | Session management, token refresh, authenticated fetch | + +--- + +## 2. LoginPage + +**File:** `components/LoginPage.tsx` (~192 lines) + +### What the user sees + +A split-screen layout: + +- **Left (60%)** — hero section with the headline "Complexity, solved. Focus, restored.", animated `AuroraText`, and three stat callouts (10× faster, 98% accuracy, 500+ documents processed). +- **Right (40%)** — login form with a single "Sign in with GitHub" button, a loading spinner during auth, and an error message if the flow fails. + +### What happens on click + +``` +User clicks "Sign in with GitHub" + │ + ▼ +signInWithGitHub() ← authUtils.ts + │ + ▼ +POST /api/auth/sign-in/social ← Better Auth sidecar + │ + ▼ +Redirect to github.com/login/oauth/authorize +``` + +The browser leaves the app entirely. GitHub handles the credential check, then redirects back to the app's callback URL. + +### State + +| State | Type | Purpose | +|---|---|---| +| `isLoading` | `boolean` | Shows spinner while auth is in flight | +| `error` | `string \| null` | Displays error message if GitHub rejects or network fails | + +--- + +## 3. AuthCallback + +**File:** `components/AuthCallback.tsx` (~151 lines) + +This component renders briefly after GitHub redirects the browser back to the app. It never shows meaningful UI — it processes the OAuth result and then navigates away. + +### What happens + +``` +Browser lands on /auth/callback?code=... + │ + ▼ +AuthCallback mounts + │ + ▼ +Better Auth sidecar exchanges code for session + │ + ▼ +Session cookie set by sidecar response + │ + ▼ +App.tsx detects valid session → navigate to "upload" + │ + ▼ (on failure) +Toast notification shown → redirect back to "login" +``` + +The `code` query parameter from GitHub is handled server-side by the Better Auth sidecar — `AuthCallback.tsx` does not parse it directly. Its job is to wait for the sidecar to finish and then let `App.tsx`'s session check take over. + +--- + +## 4. `authUtils.ts` + +**File:** `utils/authUtils.ts` + +This is the most important auth file in the frontend. Every single API call in the app goes through `authenticatedFetch()`, which lives here. + +### Key functions + +#### `getSession()` + +```typescript +async function getSession(): Promise +``` + +Fetches the current session from the Better Auth sidecar at `GET /api/auth/get-session`. Caches the result in `_cachedSession` to avoid redundant network calls on rapid re-renders. + +**Deduplication:** If `getSession()` is called concurrently (e.g., two components mount at the same time), all calls share the same in-flight promise and receive the same result. This prevents a burst of session requests on app load. + +#### `getValidToken()` + +```typescript +async function getValidToken(): Promise +``` + +Returns the current bearer token. If the token expires within 30 seconds, it refreshes the session first before returning. This ensures tokens passed to API calls are never about to expire mid-request. + +#### `authenticatedFetch()` + +```typescript +async function authenticatedFetch(url: string, options?: RequestInit): Promise +``` + +A drop-in replacement for `fetch()` that automatically: + +1. Calls `getValidToken()` to get a fresh bearer token. +2. Adds `Authorization: Bearer {token}` to the request headers. +3. Adds `X-Session-Id: {sessionId}` if a session ID is available (used for cost tracking on the backend). +4. If the response is `401`, refreshes the session and retries the request **once**. +5. If the retry also returns `401`, throws an auth error (does not loop). + +All API calls across every page component use `authenticatedFetch()` — not raw `fetch()`. + +#### `installVisibilityRefreshListener()` + +```typescript +function installVisibilityRefreshListener(): void +``` + +Registers a `visibilitychange` event listener on `document`. When the browser tab becomes visible again (after being hidden), this immediately calls `getSession()` to refresh the cached token. + +**Why this exists:** Chrome aggressively throttles background tabs. A reviewer who leaves the app open in a background tab for 30+ minutes may return to find their session token expired. Without this listener, the next API call would fail with 401 and show an error. With it, the token is silently refreshed the moment they switch back to the tab. + +`App.tsx` calls `installVisibilityRefreshListener()` once on mount. + +#### `signInWithGitHub()` + +Initiates the OAuth flow. Calls the Better Auth client to redirect the browser to GitHub's authorization URL. + +#### `signOut()` + +Posts to `POST /api/auth/sign-out`, which invalidates the server-side session. Clears `_cachedSession` locally, then reloads the page to land on the login screen. + +#### `getCurrentUser()` + +Returns `{ id, email, name, image }` from the cached session. Used by App.tsx to display the user's name and avatar in the navigation bar. + +--- + +## 5. Session token lifecycle + +``` +App loads + │ + ▼ +getSession() → cache result in _cachedSession + │ + ├── No session → navigate to "login" + │ + └── Valid session + │ + ▼ + App renders + │ + ▼ + User makes API call + │ + ▼ + authenticatedFetch() + │ + ├── Token still valid → attach header → send request + │ + └── Token expires within 30s → getValidToken() refreshes first → attach header → send + │ + └── Response 401 → refresh session → retry once + │ + └── Still 401 → throw AuthError → user redirected to login +``` + +--- + +## 6. Security notes + +- Tokens are stored in `HttpOnly` cookies by the Better Auth sidecar — JavaScript cannot read them directly. The frontend only handles the bearer token returned from `getSession()`, which is a short-lived JWT. +- `authenticatedFetch()` never logs tokens. +- The visibility refresh listener only refreshes the session cache — it does not store credentials anywhere. +- `signOut()` always invalidates the server-side session, not just the local cache. A reviewer logging out on one device/tab invalidates the session for all. diff --git a/docs/frontend/03-upload.md b/docs/frontend/03-upload.md new file mode 100644 index 0000000..158a02d --- /dev/null +++ b/docs/frontend/03-upload.md @@ -0,0 +1,111 @@ +# Upload Page (Workflow Step 1) + +> *The first thing a reviewer does is bring their documents into the tool. The Upload page handles file selection, uploads each file to the backend, immediately triggers document processing, and lets the reviewer choose which parser to use. By the time the reviewer clicks "Next", every file has been uploaded and converted into structured text that the AI models can read.* + +**File:** `components/UploadPage.tsx` (~914 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Drag-and-drop zone | Drop files or click to open file picker | +| File list | Shows each file with upload + processing status indicators | +| Parser selector (global) | Choose Azure Document Intelligence, Docling, or Auto for all files | +| Per-file parser override | Override the global parser for individual files | +| Status indicators | Pending / uploading / processing / processed / error per file | +| Error panel | Per-file error messages with retry option | + +--- + +## 2. Upload and processing flow + +Upload and processing happen automatically — the reviewer does not need to click a separate "Process" button. + +``` +Reviewer drops files + │ + ▼ +For each file (concurrent): + POST /api/upload (multipart) + │ Returns: { file_hash, blob_path, is_duplicate, filename, size } + │ + ▼ + Store file_hash in uploadResults[filename] + │ + ▼ + POST /api/documents/process/file/{file_hash} + │ Body: { processor: "azure" | "docling" | "auto" } + │ Returns: document view (markdown, figures, tables, metadata) + │ + ▼ + Store result in processedFiles[filename] + │ + ▼ + Mark file as "processed" in status display +``` + +If the backend returns a cached result (file already processed with this parser), processing completes in milliseconds. The `is_duplicate` flag from the upload response is shown in the UI so the reviewer knows the file was recognised. + +--- + +## 3. State + +| State field | Type | Purpose | +|---|---|---| +| `selectedFiles` | `File[]` | Files the reviewer has selected but not yet uploaded | +| `uploadResults` | `Map` | Keyed by filename; stores file hash and blob path | +| `processedFiles` | `Map` | Keyed by filename; stores markdown and artifact metadata | +| `uploadErrors` | `Map` | Per-file upload error messages | +| `processingErrors` | `Map` | Per-file processing error messages | +| `processingFiles` | `Set` | Filenames currently being processed (for spinner display) | +| `fileParsers` | `Map` | Per-file parser override | +| `globalParser` | `string` | Default parser applied to all files | + +--- + +## 4. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `POST` | `/api/upload` | On file drop/select | Upload file bytes, get file hash | +| `POST` | `/api/documents/process/file/{file_hash}` | Immediately after upload | Convert file to markdown/figures/tables | + +--- + +## 5. `onComplete()` payload + +When the reviewer clicks "Next", the page calls: + +```typescript +onComplete({ + uploadedFiles: [ + { + fileId: "sha256hash", + filename: "study_001.pdf", + uploadResult: { ... }, + processingResult: { markdown, figureCount, tableCount, ... }, + parser: "azure", + }, + // ... + ], + parser: globalParser, +}) +``` + +App.tsx merges this into `documentData` and navigates to `processing`. + +--- + +## 6. Downstream invalidation + +If the reviewer adds or removes files after some have already been processed, the page calls `onInvalidateDownstream("processing")`. This clears all downstream results (processing output, entity configs, extraction results, evaluation results) from `documentData` and shows the stale warning banner. + +--- + +## 7. Error handling + +- **Upload failure:** Shows per-file error with a Retry button. The file is marked with a red error indicator. Other files continue processing normally. +- **Processing failure:** Shows the backend error message. The reviewer can change the parser and click Retry — the backend may succeed with a different processor. +- **Unsupported file type:** Rejected client-side before upload. Accepted types: PDF, DOCX, XLSX, PPTX. diff --git a/docs/frontend/04-processing.md b/docs/frontend/04-processing.md new file mode 100644 index 0000000..52e0b51 --- /dev/null +++ b/docs/frontend/04-processing.md @@ -0,0 +1,117 @@ +# Processing Page (Workflow Step 2) + +> *After files are uploaded, the Processing page lets reviewers inspect what the parser actually extracted — the structured text, any figures, and any tables. It's an inspection and verification step: reviewers can check that the document parsed correctly, view the raw analysis, and re-process with a different parser if the output looks wrong. They can also view figures and tables with their bounding-box locations highlighted on the original PDF.* + +**File:** `components/ProcessingPage.tsx` (~534 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| File list (sidebar) | Select which uploaded file to inspect | +| Parser selector | Change parser for the selected file; triggers re-processing | +| Re-process button | Re-run document processing with the current parser selection | +| Tab: Text | Shows the extracted markdown content | +| Tab: Figures | Gallery of extracted figures with captions and metadata | +| Tab: Tables | HTML table viewer for extracted tables | +| Tab: Raw | Syntax-highlighted raw analysis JSON from the parser | +| PDF bounding box viewer | Highlights figure/table locations on the original PDF pages | +| Status indicators | Processing / processed / error per file | + +--- + +## 2. How processing results are loaded + +When this page mounts, `documentData.uploadedFiles` already contains the processing results from the Upload step. The Processing page reads these results — it does not re-call the backend unless the reviewer explicitly clicks Re-process. + +``` +Page mounts + │ + ▼ +Read uploadedFiles from documentData + │ + ▼ +Populate file list + set first file as active + │ + ▼ +Display processing result for active file (text / figures / tables) +``` + +--- + +## 3. Re-processing a file + +If a reviewer switches to a different parser and clicks Re-process: + +``` +Reviewer changes parser + clicks Re-process + │ + ▼ +POST /api/documents/process/file/{file_hash} + Body: { processor: "azure" | "docling" } + │ + ▼ +Backend returns new document view (may be cached if already processed with this parser) + │ + ▼ +Update processedFiles[filename] with new result + │ + ▼ +onInvalidateDownstream("processing") — clears extraction + evaluation results +``` + +Re-processing with a different parser does not re-upload the file — the original bytes are already in blob storage under the file hash. + +--- + +## 4. The PDF bounding box viewer + +The `PDFBoundingBoxViewer` shared component (see [appendices/component-index.md](appendices/component-index.md)) renders the original PDF in-browser using `pdfjs-dist` and draws coloured overlaid boxes at the coordinates returned by the parser. + +- Azure Document Intelligence returns bounding polygons in inches; the backend normalizes these to page-relative coordinates before sending to the frontend. +- Docling returns bounding boxes in page-relative coordinates directly. +- Clicking a figure or table in the Figures or Tables tab scrolls the PDF viewer to the relevant page and highlights the bounding box. + +--- + +## 5. State + +| State field | Type | Purpose | +|---|---|---| +| `files` | `FileStatus[]` | All uploaded files with their current processing state | +| `activeFile` | `string` | Filename of the currently selected file | +| `selectedTab` | `"text" \| "figures" \| "tables" \| "raw"` | Active content tab | +| `parserOverrides` | `Map` | Tracks parser selection per file | +| `reprocessingFiles` | `Set` | Files currently being re-processed | + +--- + +## 6. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `POST` | `/api/documents/process/file/{file_hash}` | On Re-process click | Re-run parsing with chosen parser | + +--- + +## 7. `onComplete()` payload + +When the reviewer clicks "Next": + +```typescript +onComplete({ + uploadedFiles: updatedUploadedFiles, // with any re-processing results merged in +}) +``` + +App.tsx navigates to `study_selection`. + +--- + +## 8. Error handling + +- **Processing failure on re-process:** Shows the backend error message inline. The original result (from Upload step) is preserved — the reviewer can dismiss the error and continue with the original. +- **Missing artifacts:** If a figure image or table HTML is not found in blob storage, the gallery shows a placeholder with the artifact filename. This can happen if the blob sync was interrupted; re-processing recovers it. +- **Parser unavailable:** If Azure Document Intelligence credentials are not configured, the Azure option is disabled with a tooltip explaining why. diff --git a/docs/frontend/05-study-config.md b/docs/frontend/05-study-config.md new file mode 100644 index 0000000..d661c58 --- /dev/null +++ b/docs/frontend/05-study-config.md @@ -0,0 +1,116 @@ +# Study Config Page (Workflow Step 3) + +> *Before running extraction, a reviewer needs to tell the tool what to look for. The Study Config page is where reviewers pick a study type (toxicology, epidemiology, or custom), load or define a set of entities to extract, and choose which AI models to use. Think of it as building the extraction checklist. The output of this step is the list of entity definitions that the Extraction page will work through.* + +**File:** `components/BatchStudySelectionPage.tsx` (~1242 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| File selector | Choose which uploaded file is being configured (multi-file mode) | +| Study type picker | Select toxicology, epidemiology, or custom | +| Template picker | Load entity definitions from a saved template | +| Entity list | Shows all entities with their extraction prompts | +| Entity editor | Add, remove, or edit the name and prompt of each entity | +| Summary prompt | Optional: customise the paragraph summary system prompt | +| Paragraph system prompt | Optional: customise the paragraph generation model instructions | +| Model selector | Choose which AI models to use for extraction | +| Global vs. per-file toggle | Apply the same entity config to all files, or configure each file separately | + +--- + +## 2. Study types and built-in templates + +Three built-in study types are available, loaded from `components/TemplateLoader.tsx`: + +| Study type | Description | Typical entities | +|---|---|---| +| `toxicology` | In vivo developmental toxicity studies | Test material, species, dose levels, route, maternal effects, fetal effects | +| `epidemiology` | Human health observational studies | Participant count, pesticide of interest, exposure measurement, health outcomes, measures of association, strengths, limitations, risk of bias | +| `custom` | Reviewer-defined | Any fields the reviewer specifies | + +When the reviewer selects a study type, `loadStudyTypeTemplate()` populates the entity list with the default prompts for that study type. The reviewer can edit, add, or remove entities freely after loading. + +--- + +## 3. Loading from a saved template + +The `TemplatePicker` component (see [appendices/component-index.md](appendices/component-index.md)) shows all templates accessible to the reviewer (personal, group-shared, and global). Selecting a template replaces the current entity list with the template's entities and system prompt. + +The reviewer can then modify the loaded entities before running extraction — selecting a template is always a starting point, not a locked-in configuration. + +--- + +## 4. Multi-file configuration + +When multiple files are uploaded, the reviewer has two modes: + +- **Global config:** All files use the same study type, entity list, and model selection. Changes apply to all files at once. +- **Per-file config:** Each file can have a different study type and entity list. The file selector at the top switches between file-specific configurations. + +The active mode is tracked in `useGlobalConfig` (boolean state). Switching from per-file to global merges all per-file configs into a single shared config, with a confirmation dialog. + +--- + +## 5. Model selection + +The model selector shows all configured AI providers from `SettingsManager`. The reviewer can select multiple models — extraction will run once per model for each entity, enabling side-by-side comparison on the Extraction page. + +Provider groups shown: +- Azure OpenAI (GPT-4o, GPT-4.1, etc.) +- Google Vertex AI (Gemini 2.5 Pro, Gemini 2.0 Flash, etc.) +- Anthropic (Claude Sonnet 4.5, Claude Opus 4.1, etc.) +- Cohere, Llama, local models — shown only if configured + +--- + +## 6. State + +| State field | Type | Purpose | +|---|---|---| +| `fileConfigs` | `Map` | Per-file entity + study type config | +| `useGlobalConfig` | `boolean` | Whether all files share one config | +| `globalEntities` | `Entity[]` | Entity list when in global config mode | +| `studyType` | `string` | Selected study type | +| `summaryPrompt` | `string` | Optional summary system prompt | +| `selectedModels` | `string[]` | All models selected for extraction | + +--- + +## 7. API calls + +This page makes no direct API calls. Entity templates are loaded from `TemplateLoader` (built-in JSON) or from the `useTemplates` hook (saved templates). Model metadata is read from `SettingsManager` (localStorage). + +--- + +## 8. `onComplete()` payload + +```typescript +onComplete({ + studyType: "toxicology", + entities: [ + { name: "Test material", prompt: "What is the test material or compound used in this study?" }, + { name: "Species", prompt: "What animal species and strain were used?" }, + // ... + ], + summaryPrompt: "Summarise the key findings...", + selectedModels: ["azure-gpt4o", "gemini-2.5-pro"], + uploadedFiles: updatedUploadedFiles, // with per-file entity configs merged in +}) +``` + +App.tsx navigates to `extraction`. + +--- + +## 9. Validation + +Before allowing "Next", the page validates: +- At least one entity is defined. +- All entity names are non-empty. +- At least one model is selected. + +Validation errors are shown inline next to the relevant field. The "Next" button is disabled until all errors are resolved. diff --git a/docs/frontend/06-extraction.md b/docs/frontend/06-extraction.md new file mode 100644 index 0000000..c73bfaf --- /dev/null +++ b/docs/frontend/06-extraction.md @@ -0,0 +1,177 @@ +# Extraction Page (Workflow Step 4) + +> *This is where the AI models do the work. The Extraction page sends each entity prompt to the selected LLMs, shows the answers as they come back, and lets reviewers see exactly where in the document each answer came from — highlighted in the original PDF. Reviewers can run multiple models side by side, edit individual answers, and see token counts and cost for every extraction. This is the largest and most complex component in the frontend.* + +**File:** `components/EntityExtractionPage.tsx` (~4300 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| File/document selector | Switch between uploaded files in multi-file sessions | +| Entity list | All configured entities with extraction status (pending, running, complete, error) | +| Model tabs | One tab per selected model showing that model's results | +| Entity detail panel | Expanded view of one entity: extracted answer, source references, cost metrics | +| PDF viewer with highlighting | Shows the pages referenced by the extracted answer, with bounding boxes drawn | +| Full text viewer | Shows the raw markdown with the relevant passage highlighted | +| In-place editor | Edit the extracted value directly in the results panel | +| Paragraph summary section | Auto-generated paragraph summary from all extracted entities | +| Export buttons | Download results as Word document or Markdown file | +| Re-run controls | Re-run a single entity, all entities for a model, or all entities across all models | + +--- + +## 2. Extraction flow + +``` +Reviewer clicks "Run Extraction" + │ + ▼ +For each entity × each selected model (concurrent): + │ + POST /api/extract + │ Body: { + │ document_conversion_id: file_hash, + │ entities: [{ name, prompt }], + │ model_config: { model_type, model_id, ... }, + │ session_id: sessionId, + │ } + │ Returns: { + │ entity_name: string, + │ answer: string, + │ references: [{ text, page, bounding_box }], + │ duration_ms: number, + │ prompt_tokens: number, + │ completion_tokens: number, + │ cost_usd: number, + │ } + │ + ▼ +Update entities[i].extractionsByModel[modelId] with result + │ + ▼ +Mark entity as complete; show answer + reference count +``` + +Each entity-model combination is a separate API call. With 20 entities and 3 models, 60 concurrent requests are made. The backend handles concurrency with a semaphore. + +--- + +## 3. PDF reference highlighting + +When an extraction result includes `references` (source passages from the document), the `EntityPDFViewerBeta` component renders the original PDF and draws coloured bounding boxes at the referenced locations. + +How it works: +1. The backend's extraction response includes `references[].bounding_box` — page-relative coordinates of the source passage. +2. `EntityPDFViewerBeta` uses `pdfjs-dist` to render the PDF pages as canvases. +3. Bounding boxes are drawn as coloured overlays scaled to the rendered page dimensions. +4. Clicking a reference in the entity detail panel scrolls the PDF to that page and pulses the highlight. + +If references are not available (the model did not return them, or the document was processed without a parser that supports bounding boxes), the PDF viewer shows the full document without highlights, and the text viewer highlights the passage by string search. + +--- + +## 4. Multi-model comparison + +When multiple models are selected in the Study Config step, the extraction page shows a tab for each model. Within each tab, all entities are shown with that model's answers. + +A **comparison view** can be toggled to show all models' answers for a single entity side by side. This lets reviewers quickly assess where models agree or disagree. + +The `extractionsByModel` field on each entity stores results keyed by model ID: + +```typescript +entity.extractionsByModel = { + "azure-gpt4o": { answer: "...", references: [...], duration_ms: 2100, ... }, + "gemini-2.5-pro": { answer: "...", references: [...], duration_ms: 1800, ... }, +} +``` + +--- + +## 5. In-place editing + +Reviewers can edit any extracted answer by clicking the edit icon in the entity detail panel. Edits are stored in `entity.extracted` (the reviewer-accepted value). The original model answer is preserved in `entity.extractionsByModel[modelId].answer` — edits never overwrite the raw model output. + +The edited value is what gets included in Word document exports and session saves. + +--- + +## 6. Paragraph summary generation + +After all entities are extracted, a "Generate Summary" button appears at the bottom of the entity list. Clicking it sends all extracted entity values to: + +``` +POST /api/generate_paragraph + Body: { + entities: [{ name, extracted_value }], + model_config: { ... }, + session_id: sessionId, + summary_prompt: "...", + } + Returns: { paragraph: string, duration_ms, tokens, cost_usd } +``` + +The generated paragraph is shown in the summary section and included in Word exports. + +--- + +## 7. Session auto-save + +Every time an extraction result arrives, the page patches the session on the backend: + +``` +POST /api/sessions/{sessionId}/extractions + Body: ExtractionResult +``` + +This means the reviewer's work is persisted to the database continuously — they can close the browser and restore from Session History without losing results. + +--- + +## 8. State + +| State field | Type | Purpose | +|---|---|---| +| `entities` | `Entity[]` | Full entity list with extraction results | +| `activeEntity` | `string \| null` | Currently selected entity for detail view | +| `activeModel` | `string` | Currently selected model tab | +| `extractionInFlight` | `Set` | Entity-model pairs currently running | +| `pdfDoc` | `PDFDocumentProxy \| null` | Loaded PDF for reference viewer | +| `editingEntity` | `string \| null` | Entity currently being edited in-place | +| `paragraphSummary` | `string` | Generated paragraph text | +| `paragraphInFlight` | `boolean` | Paragraph generation in progress | + +--- + +## 9. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `POST` | `/api/extract` | On "Run Extraction" | Extract one entity with one model | +| `POST` | `/api/generate_paragraph` | On "Generate Summary" | Generate paragraph from extracted values | +| `POST` | `/api/sessions/{id}/extractions` | After each extraction | Persist result to session | + +--- + +## 10. `onComplete()` payload + +```typescript +onComplete({ + entities: updatedEntities, // with extractionsByModel populated + uploadedFiles: updatedFiles, // with per-file entities merged in + sessionId: sessionId, +}) +``` + +App.tsx navigates to `evaluation`. + +--- + +## 11. Error handling + +- **Entity extraction failure:** The failed entity is marked with an error badge. Other entities continue. A "Retry" button on the entity re-sends that specific entity-model pair. +- **Partial completion:** The reviewer can proceed to evaluation with only some entities extracted. Unevaluated entities are skipped automatically on the evaluation page. +- **Token limit exceeded:** The backend returns a specific error code. The entity is marked with a "Context too long" error. The reviewer can shorten the entity prompt and retry. +- **In-flight navigation guard:** If extractions are running when the reviewer tries to navigate away, App.tsx shows a confirmation dialog. diff --git a/docs/frontend/07-evaluation.md b/docs/frontend/07-evaluation.md new file mode 100644 index 0000000..1086994 --- /dev/null +++ b/docs/frontend/07-evaluation.md @@ -0,0 +1,166 @@ +# Evaluation Page (Workflow Step 5) + +> *Once entities are extracted, reviewers can ask the tool to judge how good the extractions are. The Evaluation page uses G-Eval — a technique where a separate LLM acts as a judge, scoring each extraction against criteria like correctness and completeness. Reviewers can also provide their own expected answers as ground truth, add custom evaluation steps, and review a cost/score breakdown across all models. Results can be exported to Excel.* + +**File:** `components/EvaluationPage.tsx` (~5000 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Metric selector | Choose which G-Eval metrics to run (correctness, completeness, relevance, safety, custom) | +| Judge model selector | Choose which LLM acts as the evaluator | +| Entity selector | Which entities to evaluate (all, or a subset) | +| Expected output fields | Optional: enter ground truth answers per entity for correctness/completeness scoring | +| Custom metric editor | Define custom evaluation criteria with step-by-step rubrics | +| Run / Stop buttons | Start or cancel evaluation | +| Results table | Metric scores per entity per extraction model (pass/fail + numeric score) | +| Per-model breakdown | Aggregate scores grouped by extraction model | +| Cost breakdown | Tokens and cost per evaluation call | +| Charts | Score distribution and model comparison (Recharts) | +| Excel export | Download full results as a structured Excel workbook | +| Human score override | Manually override the LLM score for any entity/metric | + +--- + +## 2. G-Eval metrics + +| Metric | Requires ground truth | What it measures | +|---|---|---| +| `correctness` | Yes | Does the extracted answer match the expected answer? | +| `completeness` | Yes | Does the extraction capture all relevant information from the expected answer? | +| `relevance` | No | Is the extracted answer relevant to the entity prompt? | +| `safety` | No | Does the extracted answer contain harmful or inappropriate content? | +| `custom` | Configurable | Reviewer-defined rubric with step-by-step evaluation instructions | + +If no expected output is provided, `correctness` and `completeness` are automatically skipped. The remaining metrics can still run. + +--- + +## 3. Evaluation flow + +``` +Reviewer configures metrics + judge model → clicks "Run Evaluation" + │ + ▼ +For each entity with extraction results: + POST /api/evaluations/jobs + │ Body: { + │ tasks: [{ entity_name, extracted_value, expected_output, metric }], + │ providers: [{ model_type, model_id }], + │ session_id: sessionId, + │ } + │ Returns: { job_id, status: "queued" } + │ + ▼ + Poll: GET /api/evaluations/jobs/{job_id} + │ Returns: { status, progress, results } + │ + ▼ + Results arrive incrementally as job progresses + │ + ▼ + POST /api/sessions/{sessionId}/evaluations ← persist each result +``` + +Evaluation is run as a background job on the backend. The frontend polls `GET /api/evaluations/jobs/{job_id}` every 2 seconds until `status` is `completed`, `cancelled`, or `failed`. Results are shown in the table as they arrive, not all at once. + +--- + +## 4. Stopping evaluation + +Clicking "Stop" calls: + +``` +POST /api/evaluations/jobs/{job_id}/cancel +``` + +The backend marks the job as cancelled. Already-completed results are preserved and shown. The reviewer can review partial results and re-run only the remaining entities. + +--- + +## 5. Custom metric editor + +Reviewers can define their own evaluation metric by providing: +- A metric name +- A series of evaluation steps (e.g. "1. Check if the answer mentions the test material. 2. Check if the dose is specified.") + +The custom steps are sent to the backend as `custom_steps` in the evaluation request. The judge LLM follows the steps and returns a score from 0 to 1 for each step, which are averaged into a final metric score. + +--- + +## 6. Human score override + +Any LLM-generated score can be overridden manually. The reviewer clicks the score cell in the results table and enters their own assessment. Overrides are stored separately from LLM scores in `entity.evaluationResults[].human_score` and are preserved through session saves. Both scores are visible in the Excel export. + +--- + +## 7. Results table structure + +The results table has one row per entity and one column group per extraction model. Each cell shows: +- The numeric score (0.00–1.00) +- A pass/fail badge (threshold configurable, default 0.5) +- The judge model used +- A detail icon that opens the judge's reasoning + +The table is sortable by entity name or any metric score. + +--- + +## 8. Excel export + +The Excel workbook has: +- **Sheet 1 — Results:** One row per entity, columns for each metric × model combination +- **Sheet 2 — Extracted values:** The raw extraction answers per entity per model +- **Sheet 3 — Cost:** Token usage and cost per evaluation call +- **Sheet 4 — Config:** Which models, metrics, and judge were used + +--- + +## 9. State + +| State field | Type | Purpose | +|---|---|---| +| `evaluationConfig` | `EvaluationConfig` | Selected metrics, models, custom steps | +| `selectedEntities` | `string[]` | Entities included in the current evaluation run | +| `expectedOutputs` | `Map` | Ground truth per entity (entered by reviewer) | +| `evaluationResults` | `EvaluationResult[]` | All score results so far | +| `activeJobId` | `string \| null` | Currently running background job ID | +| `pollInterval` | `number \| null` | Interval handle for job status polling | +| `aggregateScores` | `Map` | Average score per extraction model | + +--- + +## 10. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `POST` | `/api/evaluations/jobs` | On "Run Evaluation" | Submit background evaluation job | +| `GET` | `/api/evaluations/jobs/{job_id}` | Every 2s while running | Poll job status and retrieve partial results | +| `POST` | `/api/evaluations/jobs/{job_id}/cancel` | On "Stop" | Cancel running job | +| `POST` | `/api/sessions/{id}/evaluations` | After each result | Persist result to session | + +--- + +## 11. `onComplete()` payload + +```typescript +onComplete({ + entities: entitiesWithEvaluationResults, + uploadedFiles: updatedFiles, + evaluationConfig: currentConfig, +}) +``` + +After evaluation, the reviewer is at the end of the workflow. The "Next" button navigates to the Batch Results page for a consolidated view. + +--- + +## 12. Error handling + +- **Job failure:** The job status shows `failed` with an error message. Already-completed evaluations within the job are preserved. +- **Judge model unavailable:** If the selected judge model is not configured, the error is shown before the job is submitted. +- **Score parse failure:** If the judge LLM returns malformed output, the backend falls back to a per-metric scoring approach. If that also fails, the metric is marked as `error` in the results table. +- **In-flight navigation guard:** If a job is running when the reviewer tries to navigate away, App.tsx shows a confirmation dialog. diff --git a/docs/frontend/08-simplified-flow.md b/docs/frontend/08-simplified-flow.md new file mode 100644 index 0000000..5ac477f --- /dev/null +++ b/docs/frontend/08-simplified-flow.md @@ -0,0 +1,111 @@ +# Simplified Flow + +> *The simplified flow is a one-click version of the full workflow for reviewers who don't need to inspect intermediate steps. The reviewer drops their files in, selects a study type, and clicks Run. The app automatically uploads, processes, extracts all entities, and generates a summary — showing a progress table as it goes. Results can be downloaded as a Word document when complete.* + +**Files:** `components/SimplifiedFlowPage.tsx` (~995 lines), `hooks/useSimplifiedPipeline.ts` + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Study type selector | Pick toxicology, epidemiology, or custom | +| File upload area | Drag-and-drop; accepts multiple PDFs | +| Advanced options (collapsed) | Override parser, model selection, temperature | +| Progress table | One row per file: filename, current stage, per-entity extraction progress | +| Results preview | Summary text + key extracted entities per file | +| Download buttons | Download extraction report or executive summary as Word documents | +| Cancel / Restart controls | Stop the pipeline mid-run or start over | + +--- + +## 2. Pipeline stages + +Each file moves through these stages independently: + +| Stage | What happens | +|---|---| +| `queued` | File is waiting to start | +| `uploading` | `POST /api/upload` in progress | +| `processing` | `POST /api/documents/process/file/{hash}` in progress | +| `extracting` | `POST /api/extract` running for each entity (batched, 5 at a time) | +| `summarizing` | `POST /api/generate_paragraph` in progress | +| `exporting` | Building Word document in memory | +| `complete` | All done; download available | +| `error` | A stage failed; error message shown inline | + +--- + +## 3. `useSimplifiedPipeline` hook + +All the pipeline logic lives in `hooks/useSimplifiedPipeline.ts`, not in the page component. This keeps the component focused on display and makes the pipeline independently testable. + +**API:** + +```typescript +const { state, results, run, reset, downloadResults, downloadSingleResult } = + useSimplifiedPipeline(); + +// Start the pipeline +run(files, studyType, entities, summaryPrompt, options); + +// Download all results as a single Word document +downloadResults(); + +// Download results for one file +downloadSingleResult(filename); +``` + +**Batching:** + +Entity extraction runs 5 entities at a time per file. This avoids overwhelming the backend while still parallelising work. The progress bar for the `extracting` stage shows `N / total` as batches complete. + +**Error recovery:** + +If a stage fails for one file, that file is marked as `error` and the pipeline continues with other files. The reviewer can see the error message in the progress table row and use the Restart button to retry from the beginning. + +--- + +## 4. Difference from the advanced workflow + +| | Advanced workflow | Simplified flow | +|---|---|---| +| Steps | 5 separate pages | 1 page | +| Inspection | Reviewer checks each step's output | No intermediate inspection | +| Parser choice | Per-file, with preview | Global option in Advanced settings | +| Entity editing | Edit prompts before extraction | Uses default template prompts | +| Extraction results | Viewable inline with PDF | Download only | +| Evaluation | Full G-Eval scoring page | Not included | +| Target user | Reviewers who want control | Reviewers who want speed | + +--- + +## 5. State + +State is managed inside `useSimplifiedPipeline`. The page receives it as `state` and `results`: + +```typescript +interface PipelineState { + status: "idle" | "running" | "complete" | "error"; + fileStatuses: Map; +} + +interface FileResult { + filename: string; + entities: Entity[]; + paragraphSummary: string; + wordDocBytes?: Uint8Array; +} +``` + +--- + +## 6. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `POST` | `/api/upload` | Per file | Upload file bytes | +| `POST` | `/api/documents/process/file/{hash}` | Per file | Parse document | +| `POST` | `/api/extract` | Per entity batch | Extract entities | +| `POST` | `/api/generate_paragraph` | Per file | Generate summary | diff --git a/docs/frontend/09-chat.md b/docs/frontend/09-chat.md new file mode 100644 index 0000000..13c25f1 --- /dev/null +++ b/docs/frontend/09-chat.md @@ -0,0 +1,99 @@ +# Chat Page + +> *The Chat page is a freeform document Q&A interface — think of it as asking questions directly to an uploaded PDF. Unlike the main extraction workflow, there are no structured entities or rubrics. The reviewer types a question, attaches up to five documents as context, and gets a markdown-rendered answer. It's useful for quick lookups, sanity checks, and exploratory reading before setting up a formal extraction.* + +**File:** `components/ChatPage.tsx` (~949 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Message history | Alternating user (right) and assistant (left) message bubbles | +| Message actions | Copy, thumbs up/down rating, regenerate last response | +| Document upload area | Drag-and-drop zone; accepts up to 5 PDFs | +| Document chips | Shows attached documents with processing status and a remove button | +| Model selector | Auto (backend picks best available) or manual model selection | +| Input box | Multi-line text input; Submit on Enter or button click | +| Markdown renderer | Assistant responses rendered with full GFM markdown (tables, code blocks, lists) | + +--- + +## 2. How documents are used as context + +Uploaded documents are converted to markdown and concatenated into the `document_markdown` field of the chat request. The backend passes this as context to the LLM before the user's question. + +``` +Reviewer uploads PDF + │ + ▼ +POST /api/upload → file_hash + │ + ▼ +POST /api/documents/process/file/{hash} → markdown + │ + ▼ +document_markdown stored in documentContexts[hash] + +Reviewer sends message + │ + ▼ +POST /api/chat/query + Body: { + query: "What was the NOAEL for maternal effects?", + document_markdown: "[doc1 markdown]\n\n[doc2 markdown]", + model_config: { ... }, + } + Returns: { answer: string, model_used: string, tokens, cost } +``` + +Up to 5 documents can be attached simultaneously. The combined markdown is sent in a single request. If the combined length exceeds the model's context window, the backend truncates from the oldest document first and indicates truncation in the response metadata. + +--- + +## 3. Message ratings + +Each assistant message has thumbs up / thumbs down buttons. Ratings are stored in local component state (`messageRatings`) and are not persisted to the backend or database. They serve as in-session quality tracking for the reviewer's own reference. + +--- + +## 4. State + +| State field | Type | Purpose | +|---|---|---| +| `messages` | `Message[]` | Full conversation history (user + assistant turns) | +| `attachedDocuments` | `AttachedDoc[]` | Uploaded documents with hash, filename, markdown, status | +| `selectedModel` | `string` | Model identifier or `"auto"` | +| `isLoading` | `boolean` | Request in progress (disables input) | +| `messageRatings` | `Map` | Per-message rating keyed by message index | + +--- + +## 5. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `POST` | `/api/upload` | On document drop | Upload PDF bytes, get hash | +| `POST` | `/api/documents/process/file/{hash}` | After upload | Convert PDF to markdown | +| `POST` | `/api/chat/query` | On message send | Get LLM answer with document context | + +--- + +## 6. Differences from the main workflow + +| | Chat page | Main workflow | +|---|---|---| +| Structure | Freeform conversation | Structured entity extraction | +| Session saving | Not saved to database | Saved as AppSession | +| References | No bounding box highlights | PDF page + location highlights | +| Multi-model | Single model per message | Multiple models in parallel | +| Export | Copy to clipboard only | Word / Excel / Markdown | + +--- + +## 7. Error handling + +- **Processing failure:** The document chip shows a red error indicator. The reviewer can remove and re-add the file. +- **Chat API failure:** An error bubble appears in the message history with the error text. The input is re-enabled so the reviewer can try again. +- **Context too long:** If the combined document markdown exceeds the model limit, the backend returns a specific error. The page shows a warning suggesting the reviewer remove one or more documents. diff --git a/docs/frontend/10-session-history.md b/docs/frontend/10-session-history.md new file mode 100644 index 0000000..d53b62c --- /dev/null +++ b/docs/frontend/10-session-history.md @@ -0,0 +1,120 @@ +# Session History + +> *Every time a reviewer completes extraction, their work is automatically saved as a session. The Session History page is where they can find past sessions, restore them exactly as they were left, share them with colleagues, and clean up old work. It also shows sessions that have been shared with the reviewer by others.* + +**File:** `components/SessionHistoryPage.tsx` (~800 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Tab: My Sessions | Sessions the reviewer created | +| Tab: Shared With Me | Sessions shared by other users via a group | +| Session list | Searchable table: name, date, study type, file count, entity count | +| Session metrics | Cost, total tokens, duration per session | +| Session actions | Restore, Rename, Share, Delete | +| Share dialog | Search for a group to share with; shows current share status | +| Confirmation dialogs | Required for Delete and unshare | + +--- + +## 2. Restoring a session + +Restoring loads the full session state from the backend and navigates to the appropriate workflow step. + +``` +Reviewer clicks "Restore" + │ + ▼ +GET /api/sessions/{sessionId}/restore-view + │ + Returns: { + primaryFileId: string, + uploadedFiles: [{ fileId, filename, processingResult, entities, ... }], + currentStep: "extraction" | "evaluation" | ..., + } + │ + ▼ +App.tsx re-hydrates documentData from restore view + │ + ▼ +Navigate to the step the session was last on +``` + +The restore-view endpoint reconstructs the full `documentData` shape — it re-checks which artifacts are available in blob storage and rebuilds the uploaded files array. This means a session can be restored even if the reviewer has cleared their browser cache. + +--- + +## 3. Sharing a session + +Sharing makes a session visible in the "Shared With Me" tab for all members of the selected group. + +``` +Reviewer clicks "Share" on a session + │ + ▼ +Share dialog opens — reviewer searches for a group by name + │ + ▼ +POST /api/sessions/{sessionId}/share + Body: { group_id: "..." } + │ + ▼ +Session is now visible to all group members under "Shared With Me" +``` + +A session can only be shared with one group at a time. Sharing with a different group replaces the previous share. The sharing reviewer retains ownership — only they can delete or unshare. + +To remove sharing: + +``` +DELETE /api/sessions/{sessionId}/share +``` + +--- + +## 4. Shared session behaviour + +When a reviewer opens a session from "Shared With Me": + +- They see the full extraction and evaluation results. +- They **cannot** re-run extraction or evaluation (read-only). +- They **can** restore the session view (load it into the main app for inspection). +- They **cannot** delete or rename the session (only the owner can). + +--- + +## 5. State + +| State field | Type | Purpose | +|---|---|---| +| `sessions` | `SessionSummary[]` | All sessions for the current tab | +| `activeTab` | `"mine" \| "shared"` | Which tab is displayed | +| `selectedSession` | `string \| null` | Session ID for the detail/action panel | +| `searchQuery` | `string` | Filter sessions by name or study type | +| `shareDialogOpen` | `boolean` | Whether the share dialog is visible | +| `pendingDelete` | `string \| null` | Session ID awaiting delete confirmation | + +--- + +## 6. API calls + +| Method | Path | When | Purpose | +|---|---|---|---| +| `GET` | `/api/sessions` | On mount | Load reviewer's own sessions | +| `GET` | `/api/sessions/shared/list` | On "Shared With Me" tab | Load sessions shared with reviewer | +| `GET` | `/api/sessions/{id}/restore-view` | On "Restore" | Rebuild documentData from session | +| `PATCH` | `/api/sessions/{id}` | On rename | Update session name | +| `POST` | `/api/sessions/{id}/share` | On share confirm | Share session with group | +| `DELETE` | `/api/sessions/{id}/share` | On unshare | Remove group sharing | +| `DELETE` | `/api/sessions/{id}` | On delete confirm | Permanently delete session | + +--- + +## 7. Error handling + +- **Restore failure:** If the session's files are no longer in blob storage (e.g. deleted from Azure), the restore view returns a partial result. The page shows a warning listing which files could not be restored and proceeds with the available files. +- **Delete failure:** Shows a toast error; session remains in the list. +- **Share failure (group not found):** The share dialog shows an inline error. diff --git a/docs/frontend/11-templates.md b/docs/frontend/11-templates.md new file mode 100644 index 0000000..6d3053b --- /dev/null +++ b/docs/frontend/11-templates.md @@ -0,0 +1,172 @@ +# Template Workspace + +> *Templates are reusable sets of entity definitions and prompts. Instead of manually re-entering the same 20 extraction fields every time, a reviewer saves them as a template once and loads it in seconds on the Study Config page. The Template Workspace is where those templates are created, edited, versioned, and shared — either privately, with a group, or globally across all users.* + +**Files:** `components/TemplateWorkspace/TemplateWorkspacePage.tsx`, `TemplateList.tsx`, `TemplateEditor.tsx`, `TemplateVersionHistory.tsx`, `FolderCard.tsx` + +**Hook:** `hooks/useTemplates.ts`, `hooks/useFolders.ts` + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Folder sidebar | Hierarchical folder tree for organising templates | +| Template list | Filterable list of templates with scope badges (personal / group / global) | +| Search + filters | Filter by study type, scope, tags, or free-text name search | +| Template editor | Form: name, description, study type, entities, system prompt, summary prompt, tags | +| Entity list editor | Add, reorder, and edit entity name + prompt within a template | +| Version history panel | Timestamped list of previous versions with a Revert button | +| Fork button | Copy any accessible template to your personal scope | +| Scope selector | Change template visibility: personal → group → global | +| Immutable toggle | Lock a template against further edits (admin/owner only) | +| Permission editor | Grant explicit read/edit access to specific users | + +--- + +## 2. Template scopes + +| Scope | Who can see it | Who can edit it | +|---|---|---| +| `user` (personal) | Owner only | Owner only | +| `group` | All members of the linked group | Group admins and owner | +| `global` | All users | System admins only | + +Built-in study type templates (toxicology, epidemiology) are `global` and `immutable` — they can be read and forked but not edited. + +--- + +## 3. Creating and editing a template + +``` +Reviewer clicks "New Template" + │ + ▼ +TemplateEditor opens (blank form) + │ + ▼ +Reviewer fills in name, study type, entities, prompts + │ + ▼ +POST /api/templates + Body: { name, study_type, scope, entities, system_prompt, summary_prompt, tags } + Returns: Template + │ + ▼ +Template appears in list under "My Templates" +``` + +Editing an existing template: + +``` +Reviewer clicks "Edit" on a template + │ + ▼ +TemplateEditor opens with current values + │ + ▼ +Reviewer makes changes + saves + │ + ▼ +PUT /api/templates/{id} + │ + ▼ +Backend saves a TemplateVersion snapshot of the previous content + Template.version increments +``` + +Every save creates a version snapshot automatically — the reviewer never has to manually checkpoint. + +--- + +## 4. Version history and revert + +``` +Reviewer opens version history panel + │ + ▼ +GET /api/templates/{id}/versions + Returns: [{ version, created_at, entities, system_prompt, ... }] + │ + ▼ +Reviewer clicks "Revert to v3" + │ + ▼ +POST /api/templates/{id}/revert/3 + │ + ▼ +Backend creates a new version (v5) with v3's content + (reverting does not delete the intermediate versions) +``` + +--- + +## 5. Forking a template + +Forking creates a personal copy of any template the reviewer can see, regardless of scope. + +``` +POST /api/templates/{id}/fork + Returns: new Template with scope="user", owner=reviewer +``` + +The fork is independent — changes to the fork do not affect the original, and vice versa. + +--- + +## 6. Folder organisation + +Templates can be placed in folders. Folders are scoped the same way as templates (user / group / global) and can be nested. + +``` +GET /api/templates/folders?scope=user +POST /api/templates/folders → create folder +PATCH /api/templates/folders/{id} → rename +DELETE /api/templates/folders/{id} → delete (only if empty) +``` + +Dragging a template into a folder calls `PUT /api/templates/{id}` with the updated `folder_id`. + +--- + +## 7. State (via `useTemplates` hook) + +```typescript +const { + templates, // Template[] — current filtered list + loading, + error, + filters, // { scope, study_type, search, tags } + setFilters, + fetchTemplates, + createTemplate, + updateTemplate, + deleteTemplate, + forkTemplate, + setImmutable, + changeScope, +} = useTemplates(); +``` + +The hook fetches templates on mount and re-fetches after any mutation. Filtering happens server-side via query parameters. + +--- + +## 8. API calls + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/api/templates` | List accessible templates | +| `POST` | `/api/templates` | Create template | +| `GET` | `/api/templates/{id}` | Fetch one template | +| `PUT` | `/api/templates/{id}` | Update template (creates version snapshot) | +| `DELETE` | `/api/templates/{id}` | Delete template | +| `POST` | `/api/templates/{id}/fork` | Fork to personal scope | +| `PUT` | `/api/templates/{id}/scope` | Change scope | +| `PUT` | `/api/templates/{id}/immutable` | Set immutability | +| `GET` | `/api/templates/{id}/versions` | Version history | +| `POST` | `/api/templates/{id}/revert/{v}` | Revert to version | +| `GET/POST` | `/api/templates/folders` | List / create folders | +| `PATCH` | `/api/templates/folders/{id}` | Rename folder | +| `DELETE` | `/api/templates/folders/{id}` | Delete folder | diff --git a/docs/frontend/12-groups.md b/docs/frontend/12-groups.md new file mode 100644 index 0000000..6bd76bf --- /dev/null +++ b/docs/frontend/12-groups.md @@ -0,0 +1,129 @@ +# Group Management + +> *Groups are how reviewers share their work with colleagues. A group is a named set of users with roles — once a group exists, a reviewer can share a session or a template with it and every group member gets access. The Group Management page is where groups are created, members are added, and roles are managed.* + +**Files:** `components/GroupManagement/GroupManagementPage.tsx` + +**Hook:** `hooks/useGroups.ts` + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Group list | All groups the reviewer belongs to, with their role badge | +| Create group button | Opens the create dialog | +| Group detail panel | Expanded view: name, description, member list | +| Add member dialog | Search users by email; set their role before adding | +| Member list | Shows each member's name, email, role, and join date | +| Member actions | Change role, Remove member | +| Edit group dialog | Rename group or update description | +| Delete group button | Owner-only; requires confirmation | + +--- + +## 2. Roles + +| Role | Permissions | +|---|---| +| `owner` | Full control: edit group, manage all members, delete group. Only one owner per group (the creator). | +| `admin` | Add/remove members, change member roles (up to admin). Cannot delete the group or change the owner. | +| `member` | View group and shared resources. Cannot manage members. | + +Role rules enforced both in the UI (buttons are hidden or disabled based on the reviewer's role) and on the backend (`GroupService`). + +--- + +## 3. Creating a group + +``` +Reviewer clicks "Create Group" + │ + ▼ +Dialog opens — enter name + optional description + │ + ▼ +POST /api/groups + Body: { name, description } + Returns: Group (with reviewer added as owner automatically) + │ + ▼ +Group appears in list with "owner" badge +``` + +--- + +## 4. Adding members + +``` +Owner/admin clicks "Add Member" + │ + ▼ +Search input — type email or name + │ + GET /api/groups/users/search?q=... + Returns: [{ id, email, name, image }] + │ + ▼ +Select user + choose role (member / admin) + │ + ▼ +POST /api/groups/{groupId}/members + Body: { user_id, role } +``` + +A user must already have an account in the system to be added. Inviting by email address only works if the user has previously logged in. + +--- + +## 5. Changing roles and removing members + +``` +PUT /api/groups/{groupId}/members/{userId} + Body: { role: "admin" | "member" } + +DELETE /api/groups/{groupId}/members/{userId} +``` + +Constraints enforced by the backend: +- The owner's role cannot be changed through this endpoint. +- The only owner cannot remove themselves (would leave the group ownerless). +- An admin cannot promote a member to owner. + +--- + +## 6. State (via `useGroups` hook) + +```typescript +const { + groups, // Group[] — all groups for current user + loading, + error, + fetchGroups, + createGroup, + updateGroup, + deleteGroup, + addMember, + updateMemberRole, + removeMember, + searchUsers, +} = useGroups(); +``` + +--- + +## 7. API calls + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/api/groups` | List reviewer's groups | +| `POST` | `/api/groups` | Create group | +| `GET` | `/api/groups/{id}` | Fetch group with members | +| `PUT` | `/api/groups/{id}` | Update name/description | +| `DELETE` | `/api/groups/{id}` | Delete group | +| `GET` | `/api/groups/{id}/members` | List members | +| `POST` | `/api/groups/{id}/members` | Add member | +| `PUT` | `/api/groups/{id}/members/{userId}` | Change member role | +| `DELETE` | `/api/groups/{id}/members/{userId}` | Remove member | +| `GET` | `/api/groups/users/search` | Search users by email/name | diff --git a/docs/frontend/13-executive-mode.md b/docs/frontend/13-executive-mode.md new file mode 100644 index 0000000..a9dc51a --- /dev/null +++ b/docs/frontend/13-executive-mode.md @@ -0,0 +1,70 @@ +# Executive Mode + +> *Executive Mode is a standalone summary generator that skips the structured entity extraction step entirely. A reviewer uploads a document, selects a template, and gets back a narrative paragraph summary — without going through the full five-step workflow. It's designed for situations where a reviewer needs a quick high-level overview rather than a field-by-field extraction.* + +**File:** `components/ExecutiveModePage.tsx` (~881 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| File upload area | Drag-and-drop or file picker | +| Study type selector | Toxicology, epidemiology, or custom | +| Template selector | Load entity prompts from a saved template | +| Summary prompt editor | Editable text area for the paragraph generation instructions | +| Model selector | Choose the LLM to use for summarisation | +| Advanced options (collapsed) | Parser selection, temperature, ingestion settings | +| Progress table | Per-file: upload → processing → extracting → summarising stages | +| Results preview | Generated paragraph summary per file | +| Download button | Export summary as a Word document | + +--- + +## 2. Pipeline + +Executive Mode runs the same pipeline as the Simplified Flow but skips the evaluation stage and focuses the output on the paragraph summary rather than individual entity values. + +``` +Upload file → Process (PDF to markdown) → Extract entities → Generate paragraph +``` + +Internally, the page uses `useSimplifiedPipeline` with a flag that skips storing per-entity results and focuses output on the `paragraphSummary` field. + +--- + +## 3. Difference from Simplified Flow + +| | Executive Mode | Simplified Flow | +|---|---|---| +| Output focus | Paragraph summary | Entity values + summary | +| Results display | Summary text only | Entity table + summary | +| Download | Summary Word document | Full extraction report | +| Entity values visible | No | Yes | +| Intended use | Quick overview | Full extraction record | + +--- + +## 4. State + +State is managed by `useSimplifiedPipeline`. The page reads `results[].paragraphSummary` and ignores `results[].entities` for display purposes. + +| State field | Type | Purpose | +|---|---|---| +| `studyType` | `string` | Selected study type | +| `summaryPrompt` | `string` | Editable paragraph generation instructions | +| `selectedModel` | `string` | Model for extraction and summarisation | +| `advancedOpen` | `boolean` | Whether the advanced options panel is expanded | + +--- + +## 5. API calls + +Same as Simplified Flow — see [08-simplified-flow.md](08-simplified-flow.md). The difference is only in what is displayed, not in which API calls are made. + +--- + +## 6. Error handling + +Identical to Simplified Flow. Per-file errors are shown inline in the progress table. The reviewer can retry individual files without restarting the entire run. diff --git a/docs/frontend/14-batch-results.md b/docs/frontend/14-batch-results.md new file mode 100644 index 0000000..6ccdd0a --- /dev/null +++ b/docs/frontend/14-batch-results.md @@ -0,0 +1,107 @@ +# Batch Results + +> *After running extraction and evaluation across multiple files or multiple models, the Batch Results page brings everything together in one searchable table. It's the consolidated view for comparing results, spotting outliers, and exporting the full dataset. Reviewers can filter by model or metric, sort any column, and drill into any single result for the full detail.* + +**File:** `components/BatchResultsPage.tsx` (~1356 lines) + +--- + +## 1. UI sections + +| Section | Purpose | +|---|---| +| Results table | One row per entity × file combination; columns for each metric × model | +| Search bar | Fuzzy full-text search across entity names, answers, and file names (Fuse.js) | +| Column visibility toggle | Show/hide individual metric or model columns | +| Sort controls | Click any column header to sort ascending/descending | +| Filter dropdown | Filter rows by model, metric pass/fail status, or study type | +| Row detail modal | Click a row to see the full extraction answer, all metric scores, and judge reasoning | +| Excel export | Download structured workbook with all results | +| Word export | Download formatted report | +| Human score column | Shows reviewer overrides alongside LLM scores | + +--- + +## 2. Table structure + +Each row represents one entity extracted from one file by one model: + +| Column | Source | +|---|---| +| File | `uploadedFile.filename` | +| Entity | `entity.name` | +| Model | `extractionsByModel` key | +| Extracted value | `entity.extractionsByModel[model].answer` | +| Correctness | `evaluationResults[].correctness` | +| Completeness | `evaluationResults[].completeness` | +| Relevance | `evaluationResults[].relevance` | +| Safety | `evaluationResults[].safety` | +| Human score | `evaluationResults[].human_score` (if set) | +| Cost | `extractionsByModel[model].cost_usd` | +| Duration | `extractionsByModel[model].duration_ms` | + +Columns for metrics that were not run are hidden by default. + +--- + +## 3. Fuzzy search + +Search is powered by [Fuse.js](https://fusejs.io/). The index is built from entity names, extracted answers, and filenames. Results are ranked by relevance score — an exact match scores higher than a partial match. + +The search index is rebuilt whenever `documentData.uploadedFiles` changes (i.e. when a new extraction is run). + +--- + +## 4. Row detail modal + +Clicking any row opens a modal with: + +- The full extracted answer (not truncated) +- All metric scores with the judge's reasoning text +- The source references (which pages the answer was drawn from) +- Token usage and cost for that extraction +- Human score override field (editable inline) + +Human score overrides entered in the modal are propagated back to `documentData` via `onInvalidateDownstream` (false) + a targeted update, so they persist through session saves without clearing downstream results. + +--- + +## 5. Excel export + +The exported workbook mirrors the table exactly: + +- **Sheet 1 — Results:** All rows visible in the current filtered/sorted view +- **Sheet 2 — Full results:** All rows unfiltered +- **Sheet 3 — Config:** Study type, models used, metrics run, judge model + +Column headers in the workbook match the table headers. Numeric scores are formatted as decimals (0.00–1.00); pass/fail is a separate boolean column. + +--- + +## 6. State + +| State field | Type | Purpose | +|---|---|---| +| `results` | `ResultRow[]` | Flattened rows derived from `documentData` | +| `fuseIndex` | `Fuse` | Search index over results | +| `searchQuery` | `string` | Active search string | +| `sortBy` | `string` | Active sort column key | +| `sortOrder` | `"asc" \| "desc"` | Sort direction | +| `columnVisibility` | `Map` | Which columns are shown | +| `activeFilter` | `FilterConfig` | Active model/metric/status filters | +| `selectedRow` | `ResultRow \| null` | Row open in detail modal | + +--- + +## 7. API calls + +This page makes no API calls on mount — all data is read from `documentData` passed down from `App.tsx`. The Excel and Word exports are generated entirely client-side using `exceljs` and `docx` respectively. + +The exception is human score overrides: when a reviewer saves a human score in the detail modal, the page calls: + +``` +PATCH /api/sessions/{sessionId} + Body: { entities: updatedEntities } +``` + +to persist the override to the session. diff --git a/docs/frontend/README.md b/docs/frontend/README.md new file mode 100644 index 0000000..c7c8239 --- /dev/null +++ b/docs/frontend/README.md @@ -0,0 +1,184 @@ +# Science-GPT Frontend Technical Design + +This directory documents the frontend of the Science-GPT Summarization Tool — a React single-page application that guides scientific reviewers through document upload, AI-powered entity extraction, and quality evaluation. + +Read this page first for orientation, then follow the numbered module docs for detail on each page or feature area. + +--- + +## Visual workflow map + +The app has two entry paths: a **step-by-step advanced workflow** (the primary path) and a **simplified one-click flow** for users who want to skip the manual steps. + +**Advanced workflow (linear):** + +``` +[Login] → [Upload] → [Processing] → [Study Config] → [Extraction] → [Evaluation] + 02 03 04 05 06 07 +``` + +**Tool overlays (accessible from any step):** + +``` +[Chat] [Session History] [Templates] [Groups] [Executive Mode] [Batch Results] + 09 10 11 12 13 14 +``` + +Tool overlays remember the previous workflow step — clicking Back returns the reviewer to where they were. + +**Visual architecture diagrams** are in [`../images/`](../images/). + +--- + +## Tech stack + +| Layer | Technology | Version | Purpose | +|---|---|---|---| +| Framework | React | 18.2.0 | UI rendering | +| Language | TypeScript | 5.2.2 | Type safety | +| Build tool | Vite | 5.1.4 | Dev server and production builds | +| Styling | Tailwind CSS | 3.4.1 | Utility-first CSS | +| Components | shadcn/ui (Radix UI) | — | Base UI components (buttons, dialogs, tables, etc.) | +| Auth | Better Auth | 1.5.6 | GitHub OAuth session management | +| PDF rendering | pdfjs-dist | 5.4.394 | In-browser PDF display with bounding box overlays | +| Word export | docx + markdown-docx | 9.5.1 / 1.5.1 | Export results as Word documents | +| Excel export | exceljs + xlsx | 4.4.0 / 0.18.5 | Export results as Excel workbooks | +| Charts | Recharts | 2.12.0 | Evaluation result visualizations | +| Animation | Framer Motion | 12.23.24 | Page and component transitions | +| Markdown | React Markdown + remark-gfm | 10.1.0 | Render LLM markdown responses | +| Notifications | Sonner | 1.4.0 | Toast messages | +| Telemetry | OpenTelemetry (OTLP) | — | Browser-side distributed tracing | + +No external state management library (no Redux, Zustand, etc.). All state lives in `App.tsx` and local component state. See [01-app-shell.md](01-app-shell.md) for how that works. + +--- + +## Page map + +| # | Step / Page | Component file | Workflow role | +|---|---|---|---| +| — | App shell | `App.tsx` | Central state container and router | +| — | Login | `components/LoginPage.tsx` | Auth entry point | +| — | Auth callback | `components/AuthCallback.tsx` | OAuth redirect handler | +| 03 | Upload | `components/UploadPage.tsx` | Workflow step 1 — upload files, choose parser | +| 04 | Processing | `components/ProcessingPage.tsx` | Workflow step 2 — parse PDFs into text/figures/tables | +| 05 | Study config | `components/BatchStudySelectionPage.tsx` | Workflow step 3 — select study type, configure entities | +| 06 | Extraction | `components/EntityExtractionPage.tsx` | Workflow step 4 — run entity extraction with LLMs | +| 07 | Evaluation | `components/EvaluationPage.tsx` | Workflow step 5 — score extraction quality with G-Eval | +| 08 | Simplified flow | `components/SimplifiedFlowPage.tsx` | One-click end-to-end pipeline | +| 09 | Chat | `components/ChatPage.tsx` | Freeform document Q&A | +| 10 | Session history | `components/SessionHistoryPage.tsx` | Browse, restore, share previous sessions | +| 11 | Templates | `components/TemplateWorkspace/TemplateWorkspacePage.tsx` | Create and manage extraction template libraries | +| 12 | Groups | `components/GroupManagement/GroupManagementPage.tsx` | Create and manage user groups for sharing | +| 13 | Executive mode | `components/ExecutiveModePage.tsx` | Standalone executive summary generation | +| 14 | Batch results | `components/BatchResultsPage.tsx` | Tabular view of all results across files | + +--- + +## Architecture overview + +### State management + +The app uses a single top-level state object called `DocumentData` in `App.tsx`. Every page reads from it on mount and reports updates back via an `onComplete()` callback. There is no global store or context for workflow state — it flows through props. + +See [01-app-shell.md](01-app-shell.md) for the full `DocumentData` interface and callback pattern. + +### Navigation + +Navigation is controlled by a `currentStep` string in `App.tsx`, not a URL router. Clicking "Next" calls `setCurrentStep()` directly. The browser URL does not change between workflow steps. + +### Authentication + +Every API call uses `authenticatedFetch()` from `utils/authUtils.ts`, which automatically attaches a `Authorization: Bearer {token}` header and retries once on 401. Sessions are established via GitHub OAuth through the Better Auth sidecar. + +See [02-auth.md](02-auth.md) for the full auth flow. + +### Session persistence + +When a reviewer closes the browser mid-workflow, the app saves `currentStep` and `sessionId` to `localStorage` and auto-restores on next load by calling `/api/sessions/{id}/restore-view`. + +--- + +## Directory structure + +``` +frontend/ +├── App.tsx Main routing + central state (~2100 lines) +├── main.tsx React entry point +├── index.html HTML template +├── vite.config.ts Build config +├── tailwind.config.js Tailwind config +├── package.json Dependencies +│ +├── components/ All React components +│ ├── LoginPage.tsx +│ ├── AuthCallback.tsx +│ ├── UploadPage.tsx +│ ├── ProcessingPage.tsx +│ ├── BatchStudySelectionPage.tsx +│ ├── EntityExtractionPage.tsx (~4300 lines, largest component) +│ ├── EvaluationPage.tsx (~5000 lines) +│ ├── SimplifiedFlowPage.tsx +│ ├── ChatPage.tsx +│ ├── SessionHistoryPage.tsx +│ ├── ExecutiveModePage.tsx +│ ├── BatchResultsPage.tsx +│ ├── TemplateWorkspace/ +│ │ ├── TemplateWorkspacePage.tsx +│ │ ├── TemplateList.tsx +│ │ ├── TemplateEditor.tsx +│ │ ├── TemplateVersionHistory.tsx +│ │ └── FolderCard.tsx +│ ├── GroupManagement/ +│ │ └── GroupManagementPage.tsx +│ └── ui/ shadcn/ui base components (50+) +│ +├── hooks/ Custom React hooks +│ ├── useTemplates.ts +│ ├── useGroups.ts +│ ├── useFolders.ts +│ └── useSimplifiedPipeline.ts +│ +├── contexts/ +│ └── ThemeContext.tsx Light/dark theme +│ +├── utils/ +│ ├── authUtils.ts Auth, token management, authenticated fetch +│ ├── session.ts Session ID tracking +│ ├── modelSelection.ts Model picker logic +│ ├── wordExport.ts Word document generation +│ ├── excelExport.ts Excel export +│ └── executiveSummaryExport.ts Executive summary export +│ +└── types/ + └── session.ts Shared TypeScript types +``` + +--- + +## Module docs + +| Document | What it covers | +|---|---| +| [01-app-shell.md](01-app-shell.md) | `App.tsx` — `DocumentData`, routing, session persistence, navigation guards | +| [02-auth.md](02-auth.md) | Login, OAuth callback, `authUtils.ts` — token management, authenticated fetch | +| [03-upload.md](03-upload.md) | Upload page — file upload, deduplication, parser selection | +| [04-processing.md](04-processing.md) | Processing page — document parsing, figures, tables, PDF viewer | +| [05-study-config.md](05-study-config.md) | Study config page — study type, entity definitions, template loading | +| [06-extraction.md](06-extraction.md) | Extraction page — entity extraction, multi-model comparison, PDF reference highlighting | +| [07-evaluation.md](07-evaluation.md) | Evaluation page — G-Eval scoring, background jobs, result visualization | +| [08-simplified-flow.md](08-simplified-flow.md) | Simplified flow — one-click pipeline, `useSimplifiedPipeline` hook | +| [09-chat.md](09-chat.md) | Chat page — document Q&A, multi-document context | +| [10-session-history.md](10-session-history.md) | Session history — browse, restore, share, delete sessions | +| [11-templates.md](11-templates.md) | Template workspace — CRUD, versioning, scopes, folders | +| [12-groups.md](12-groups.md) | Group management — create groups, manage membership and roles | +| [13-executive-mode.md](13-executive-mode.md) | Executive mode — standalone summary generation | +| [14-batch-results.md](14-batch-results.md) | Batch results — tabular view, search, export | + +## Appendices + +| Document | What it covers | +|---|---| +| [appendices/component-index.md](appendices/component-index.md) | All shared components with usage notes | +| [appendices/hooks-contexts.md](appendices/hooks-contexts.md) | All custom hooks and `ThemeContext` with exported APIs | +| [appendices/types-interfaces.md](appendices/types-interfaces.md) | Key TypeScript interfaces (`DocumentData`, `Template`, `Group`, etc.) | diff --git a/docs/frontend/appendices/component-index.md b/docs/frontend/appendices/component-index.md new file mode 100644 index 0000000..36cd7a9 --- /dev/null +++ b/docs/frontend/appendices/component-index.md @@ -0,0 +1,181 @@ +# Shared Component Index + +All reusable components in `frontend/components/` that are used across multiple pages. shadcn/ui base components (`components/ui/`) are not listed here — they are standard Radix UI wrappers and are documented at [ui.shadcn.com](https://ui.shadcn.com). + +--- + +## PDF and document viewers + +### `PDFBoundingBoxViewer` + +Renders a PDF in-browser using `pdfjs-dist` and draws coloured rectangular overlays at coordinates returned by the parser (figures, tables). + +**Props:** +- `fileHash: string` — identifies the file in blob storage +- `boundingBoxes: BoundingBox[]` — coordinates to highlight +- `activeBoxId?: string` — scrolls to and pulses this box on change + +**Used in:** [Processing](../04-processing.md), [Extraction](../06-extraction.md) + +--- + +### `EntityPDFViewerBeta` + +Advanced PDF viewer with entity-reference highlighting. Renders all referenced pages for the currently selected entity and draws coloured boxes at the bounding-box coordinates returned in the extraction response. + +**Props:** +- `fileHash: string` +- `references: Reference[]` — page + bounding box per source passage +- `activeReference?: number` — index of the reference to scroll to + +**Used in:** [Extraction](../06-extraction.md) + +--- + +### `FigureGallery` + +Paginated image carousel for figures extracted from a document. Shows the figure image, caption, and page number. Falls back to a placeholder if the image is unavailable in blob storage. + +**Props:** +- `fileHash: string` +- `figures: FigureMetadata[]` +- `processor: string` + +**Used in:** [Processing](../04-processing.md) + +--- + +### `TablesGallery` + +Paginated viewer for HTML tables extracted from a document. Renders each table's HTML in an isolated container to prevent style bleed. + +**Props:** +- `fileHash: string` +- `tables: TableMetadata[]` +- `processor: string` + +**Used in:** [Processing](../04-processing.md) + +--- + +## Content rendering + +### `MarkdownViewer` + +Renders markdown strings with GitHub Flavoured Markdown (GFM) extensions — tables, strikethrough, task lists, code blocks with syntax highlighting. + +**Props:** +- `content: string` +- `className?: string` + +**Used in:** [Chat](../09-chat.md), [Extraction](../06-extraction.md), [Evaluation](../07-evaluation.md), [Batch Results](../14-batch-results.md) + +--- + +### `RawOutputViewer` + +Syntax-highlighted JSON viewer for raw parser output. Uses `react-json-view` or similar for collapsible tree rendering. + +**Props:** +- `data: object` + +**Used in:** [Processing](../04-processing.md) + +--- + +## Auth + +### `LoginPage` + +GitHub OAuth login screen. See [02-auth.md](../02-auth.md). + +### `AuthCallback` + +OAuth redirect handler. See [02-auth.md](../02-auth.md). + +--- + +## Templates and model config + +### `TemplatePicker` + +Dropdown that lists all templates accessible to the reviewer. On selection, resolves the template to its entity list and calls an `onSelect` callback. + +**Props:** +- `onSelect: (template: Template) => void` +- `studyTypeFilter?: string` + +**Used in:** [Study Config](../05-study-config.md), [Extraction](../06-extraction.md), [Executive Mode](../13-executive-mode.md) + +--- + +### `TemplateLoader` + +Utility component (no UI) that provides built-in study type templates from local JSON files. + +**Exports:** +- `loadStudyTypeTemplate(studyType: string): Entity[]` +- `getAvailableStudyTypes(): string[]` +- `getStudyTypeDisplayName(studyType: string): string` + +**Used in:** [Study Config](../05-study-config.md), [Simplified Flow](../08-simplified-flow.md) + +--- + +### `SettingsManager` + +Singleton class (not a React component) that manages global settings in `localStorage`. Stores API keys and model configurations per provider. + +**Key methods:** +- `getModelConfigs(): ModelConfig[]` — returns all configured models +- `getModelConfig(modelId: string): ModelConfig | null` +- `saveModelConfig(config: ModelConfig): void` +- `clearModelConfig(modelId: string): void` + +**Used by:** All pages that call API endpoints requiring model credentials. + +--- + +### `SettingsPage` + +Modal or overlay for editing `SettingsManager` values. Presents a form for each provider's API key, deployment name, and API version. + +--- + +## Metrics and export + +### `SessionMetrics` + +Displays token usage, cost, and duration for the current session. Reads from backend session metrics endpoint and updates in real time. + +**Props:** +- `sessionId: string` + +**Used in:** [Extraction](../06-extraction.md), [Evaluation](../07-evaluation.md) + +--- + +### `ExportUtils` + +Non-rendered utility module for generating and downloading files client-side. + +**Exports:** +- `generateWordDocument(entities, summary, options): Promise` — builds a `.docx` file from extraction results +- `generateMarkdownDocument(entities, summary): string` — formats results as Markdown +- `downloadFile(blob, filename): void` — triggers browser download + +**Used in:** [Extraction](../06-extraction.md), [Simplified Flow](../08-simplified-flow.md), [Executive Mode](../13-executive-mode.md), [Batch Results](../14-batch-results.md) + +--- + +## Error handling + +### `ErrorBoundary` + +React error boundary wrapper. Catches render errors in child components and shows a fallback UI with a "Reload" button instead of a blank screen. + +**Props:** +- `children: ReactNode` +- `fallback?: ReactNode` + +Wraps the entire app in `main.tsx` and is also used around the PDF viewers (which can throw on malformed PDFs). diff --git a/docs/frontend/appendices/hooks-contexts.md b/docs/frontend/appendices/hooks-contexts.md new file mode 100644 index 0000000..d7d6585 --- /dev/null +++ b/docs/frontend/appendices/hooks-contexts.md @@ -0,0 +1,199 @@ +# Hooks and Contexts + +All custom React hooks and context providers in the frontend. + +--- + +## Contexts + +### `ThemeContext` + +**File:** `contexts/ThemeContext.tsx` + +Manages light/dark theme across the app. + +**Behaviour:** +- On first load, reads `prefers-color-scheme` from the OS. +- Persists the reviewer's manual preference to `localStorage` under `summarization_theme`. +- Applies the theme by toggling a `dark` class on `document.documentElement` (Tailwind dark mode convention). + +**Exported hook:** + +```typescript +const { theme, toggleTheme } = useTheme(); +// theme: "light" | "dark" +// toggleTheme: () => void +``` + +**Used in:** Navigation bar theme toggle button, present in all pages. + +--- + +## Custom hooks + +### `useTemplates` + +**File:** `hooks/useTemplates.ts` + +Manages all CRUD operations and filtering for prompt templates. + +**Returns:** + +```typescript +{ + templates: Template[]; + loading: boolean; + error: string | null; + filters: TemplateFilters; + setFilters: (filters: TemplateFilters) => void; + fetchTemplates: () => Promise; + createTemplate: (data: CreateTemplateRequest) => Promise

XBg~h8YA!~@Ep05+4qU_WMR%9XE%!keW00(dA zB=+mvyJ?5&woGlG{<#G=4x5U#d}x1O)g7Nk$?1dR@I|tZLk33|+xnJqqRG#h8DOfC zV#`oOVgS%ZfRMYc@*ur#cwsWCvIUYW&(l>E^!3-=md5RbT?%QUm8hI|*&T5NaD(!- z=H=^f3w8dsmW+?gKxl+4xkG2WM$BSuNMZ^%i-_21021DpV}7G zZUJU^w@=hgHv<@DPYA*HigmwwWxbSsM~+AD4Jr9BiklUU`<9J+{FER@K>?ztZa>_p zFow;^sS9psb6FKa{{fay-Ifi*c;w03%PT^he=q2bC}OWIi}q9XLpKZ@HH`LCC)h`@ z6W+(h9UqPq7^Pd$_6ej|z1H(hnwDsUVnOgzDVbM{b=CizQ9g%hc{fXM?b&cX6ywLv zb;LeyR@fArcUK>cphzXt7Uuh_yyf%Mwm4au&+XTH=XHXu9f)4vY1&!82ZwfsPynsk z_RrNDd~0CN1Lp#3*aqh=gBRTc2I{D{?{zzxmA==-^3DyhSez7HFp}M7bhuYPwb8l- z(0CpWly?^FN}Lv%AR$}EPW4cm_?l*YSIkbb_5L0^5rKpCXFy3!6@hj@vSEsc_meb% z-OG0S6`hlZ5t2gm^(cj*%2sPmdR*5R-CtOKp~S`tg$m7VslB1#V6AW!IcbtkC|9Is zBE8Vyq!!Fm-de>`ttL*V`LN|cRLZGiH{(2Id<$1}hKnyn9B#vu4{f8T*t$(Qt zBMMa@3T~IvAKHnYZ`BSU)oklGnyaX0YmZ#!dNI0rsj3@=D88ySywvcnRu8Des_O=5 z=vIMG2GVUbhL9TW-LhJ(N>1JK6+b$l=WsKm2>6_FIA}{{r|_HwLQCz)@CSFgF^L3~ z+*H%H3+wTFmq6n)9llhz5d(Omhg{K>B46n=qpPiMTUC%1b<0LtO&@>&RY6M34+4%e zn<9GWx4M8J*u$MZ+Scr>eE|O?Vt#Gav<|Hujv2Jpnd-yxa4%XpY^TY$o!~baHE3MW zUb(JVe`JeqW9f*T^NPY1ojxI2fI2!;VhU8eYZIKVm7!w8gzn z`COg8u8mE_VBd^{>6ygIeEQ{ms){gUB#9vw)xvy@A1&wqO zY9kzA^gfIaZ9n;=5n3JX5j@%^`_M%M1VBj{{xN$`+v*X{#HJsQ$C5Suv+GZZ+L!VC zMay4pluRs8vfobsPbOuCmw5+wm4PnjgddxsXuWe=Zhq9OMNv=7+<+MaW zBw(p#t<$A4nZfE@U#6AzR`TH5|`kZ2CqK3?6q+BCx zqwLd|mG(R>@#RVhL=1a$AdpKHVL@lAVV{1erQGYtZuWDBhPsy8+DczCCs7j|577#o zK|fkvKjf5^0x6$_8;`!@#{g5k2CHynF5&bhkNa`YChqFZ6Th zDtSG46o?&SdH9wv-2nFY|9v3iJE-FdpUmOPP-l+d9?QsmQGOZp#q2V}_T{FWE0{Kx zxXJL$ZFVb@tVa<256oP`{RX~nz+Y43C0NzFuR$b@6Bt}J;m+^^nNOGJauqu+33E}df0-ja(JkCK0&>|Hf|0C-6NYvaBz}DXEtsK+DPCN zHPS)Go%SiMNz=&rI;;<7DuSQ=`%(!7<%dCkCRAOon`BhaGVvmXnW_E}?_tF|WdWhgNG=zG{3B)A&+^BKUjcmYutb zr~cN819E$U6A5dpkT~vUCh6_!^I%f?7ypBZSaxKyT zBjzXcYF4MCe|l2NB_--lvp-RMSdHj6W}yI!bVwYQoK%Y0q!;Qh16Do z_ocB32Z`-v^FFIdan}y-b*jAY%3DtW^Z?N7t!FI7*Y|^mha&yZiU(Slyo8bKCjZLq zTS+=Rlh7Wd;>^7PEum-j;~%egFy0y*Tp7VKAv0d)aqSo{;n43t|lGLp7MidbCP zrusi6vp)=&tJDS5@G{)L>lKX0S!=MWDO3AG^d8pSJ^u;GOOqI|ZWiNrn*2+#eP`u# z4i#vvL@H+{%#y6zn8nFdYh1kIn`tcitewJJtssXdg%&FU0xZgCmw;Mi=@-7Gn1KKv z%*DeUN5I!Ns|feKtC}U8HaqJ%mFvQEf0)~sr{qzw4Wi7_M~oPw)45+KL}X&BOe01Y z+i}cZ3PrPhx8%m|f>wEHcS{7G8gCB&A&-iW4d3anmX|`l<(x1k5B=`9vYfWG#hWxE zJ^Nq75ocW0>InL8%4tUNLb0{S_K(C%aoH3iGz(Y-q|r*m0}5n&nROu&)wa%910cse zN-UrxJ^7r^W(K;;pC3)E1|dFUVUMs)D{YF$DfJ~JBP93DffU*k?&x^)AE&Pgl+MC< zQTaeT1X>V9Io&-jf3iC#t8;;@1xhM5_Fckk`nRZvs#NUS2HdQp$v2DRAB^aORhLI| zR+^_hK8kFgz(*3Rc17T!kI5T;JPSmmEBq<*DciA7AHF;8b~Nf3$=TaTyrU(7PWN$2 zUtxGX&xbIy(>uYAHA6Hfh8eH`oH61J(Vx>y?BBKWB&5k5t{o;A`?Xt>To)gWqoj>y z6w$D1^$_nF`uWk$KCjme4-P{m?;n0feH>XJw)-SgAAjM4^@H(l)yK}FdUeZ4 zt%B;>vhlv%koR^f5S>bGooexva74JkB}S;|cCR5rW`PGLE(z)FGX(VESn`i}vx}sV zecm5Ne7wP%*!@~yr5y3W`7F{qQdBMp7kGF$7mWrY(c$vWfD~mf7ixLC-12CYD(XwS zTZ^du3loUtyut`8XYlFH(uWYsoP3Z5W9X30K>{#{R&#MFZ_CfBE)-2J9lYeq9_{+? zQ_u5b=!??qIoBg^I-aUcMPnT!-cC$xp^Kb3%DPKPg3F(w0e|VZH+CisGmB7?%=`E> z6Os>&DZ?sx23hI%l#i*BIZ<7V?>+0W{rpUKCT&!WQ(Q)7qPmU&(Zs{~V#mUl>hiRe ze!6oRShVIU9+7wZG`+&vgc}NbB^vLru}7ma*zI)|o9Q_a+Ae=n=^1=7tmV{z8J{RN z3-a?8AD_C%GOi;M2f(p^yr&Wm{vk~i%r^lv;?4crx-i!h^cw5+BA$FGz(+{;CQ;Il z(|c8N!)4yYdegx@O{y;}LVOsc@cDI|;W8P%Uo*Y4K5+ZF@F`A|8@agooQyM#kuO_B zc_;fH`Oz_Gea0sOT&m-_T{mL2?DFil)Gg!7R%7EVZT|4-K>ck;(ubmw`yO?4RKt)&2m{ml5pk`p>5(3cW)i`n%`m zToy{V{p~NDID*nFN#it65^+T@#B^Vx&7ISG(GOR~uzQ;^)s6N^Qm8SXD6_icMTSAD zRv62<)DHtEtCqfO%MMbdF`M}LFX~QA1t@@To0BX~n@IB`?OzNOw8=E)F?TDZnV+k@ zJPH?`e7~-jXu!7}F*;p~zkj5lrjCXVHRJxJfWy!qmdJt81?tafXbOPN`}nb%xq;Q2n|4lI~~e}l}hE8ZOz{O=cw3Z?iDlNzdIjxV1!rfr!KeI1$o z;j|j{FP)0?<`3Ml!qnQ-vtbK0^Q!zoeaB~SuLnD#3l$!`yVeXH@tgJjLmg$ZW69fb z_U_er%8qDgCLo;UW5I0TNH7n;=b*#A7mPpG8ap1T$MWao&Qlnr3}BuvuyOO0*_S%L zjlXs>uovd=m>vi)zR09p!J&^up7U{$5rWz2g!Ht+viJ8So#~!+IT}#B&2|+Z$fLlI zEs?Qm``Tr=i$5h>y3j1Q^Muog??-iw%k@7JyJl96;0p{n+4&tBDRbVkG7?0|=LvV* zVA34-qVjq5p3T3zWJ3#^7m1(J(A*zpjyg}}+qVQLmenRct?D{+x~wWYT^e2M&W$;8 z8aEtqIT#qYxn2_))Dto2J{tZsK<8;`R2SM!{ujqOlRK2l(IzT8HQWY-HouF}q5466 z;XMAv9exLgNk#kg@31nRCaXLr-;5iHt_8eA8*ACoF@IhHv)^shCgT)-+f6kjLy#JXIXDo1#HWi`Jkz z?N+MKl!A37ylg=Yy~`Zp889vBH)TFBSwDW58r+cIa4-)+ zys5ZFaj9LWg(Ja}mi8Jbb%*7>^qbH*#OUaMz$lB%-M3+uVsx!uizbRw%MwSOBQdRT z+fC5~0A0{~vZZK&o7XnQNs`eqHR$CI#uZF_w*DP!p=pkH`f!mrQ=*4$)637&Kiosheu4JESGZ}JJ^9JYNfbuAC189b%})t<>j=|q1=d-N?Ds{Nq4s)?ZA=i z#LaxOMMZYcK#w5)@VoN1!S&-r!Xk)re{!t#(>a7KsAK}t#ck)4~HU+Oxn4}}Q}&X)-j zQ%}9RGxM>PZd!CKvcbjEFvcFx(uKkYvT|Ji_u^Fe>_?{Pkx0qs#AUoP0vrl~mD3 zYe^CXMn`bW*!)>*8%oGDTsDj1+?Y``?>I(yZS}DK>p;Zg zeo}~coNo{n7QvCq#OAt_Awj*K7sboefp;(?w7oLM*0i!+;+Ft6iHp>m994j6B@WVP z!uaL1^W1-c%|tEPZ%&SnVGaQ?B9JPJ;uXY&akJ%iqG+*K^ z878m^C&;}=|FuhIT$Dk?O3$4T+SLOe$<&ST6Jc}zUB$2`2iO@PRTJGV7{Nd=fckyU z_7JbKqhxeoIhhk%okQ+Wnd8dEdA3DK78_DgV!z5+b99$>(73B$%(GNyYF>E3bVT*g zX5O$dVekieX?Ru*l_7I6cN6isdppoc)4&NE) z<2T)+oiWs`6LL_zyUiQ$+2wk2ajp8H=EkuIex$kK{cKwNwq`q6X9a2O^exP8{FGcT z^&=SSkHc#7{rHLX_TXG>1@G_1_Fi|L-|>82eL8={Y&NAZkq^ajqiENIlMCxvec7%8 z!H1Sx)8n=}>}#_#tKe{K*Qki1xc2|B0DYF7dBy6i>|Y>byX1|TPN!ZV0^T_fk99pZ z)M=(Oq8C^dUeOx5*l+<8W3nneII4Z+3q%Ch+Ngf-|IPOo{ zZH)KS?dg8 z0r-CeNdykwO|G$8zyp5w0y(INbM=G>@rBYuyZYYTSJL^i>>T6Dd0XtYUat&gMI?qE z_Rp-@Sb5B^$16&ye}Supi_*+h3yM;Vp=+)R)i6zn!%*0Ig^Ju}_q*Td#`Gx#G2LtX zO1P;V?FuMavi&!tt5~bjbmdAHz4cHnaG2?_;&$uqSeIZ!0ds80l7}#tXAs-@0bJxx z)|#J#v%#?Gwu|Ev>kFzJ#A`6S!BOaZ+imO79*6vIAQNYwyLuHiPijo=w7m^B9zj{_KnUN0PAS%0?&Ai`GF^^p zNx-L#dvlF#%6OH)okrMuQ!~(5=JizP~CGx^VV%& z*rYMtt&a{jf2ceO!2eCcd)?UGd=eevF4DT-YP|E9lV=?RK&s2ucc0gD;LqSo%3B;uiScm1lOYOld8U2azO35J)(e8o?56Vf)xck>wL2b zF}_7Z^FX7qBVA;DNmDm=Wy<8-P7x1L=E^v6$c z>-6AYcjNT<@bnjQ^$Hu2&Wx;%zKXVrEyB&M6bM02@T~dj^UXg46RKzpYyUei0SBjb z^U#itp$ZlnmtKLqJcV~8c3S9Y#4b#nD)I(5oBVe;4XRuU3Kq>Yo zBl;agBU`syMiGUNZSI!>cr{GF=gN zQ72$&Y4@;&mBWfJ+Aa|a^Z$pqw~UIT>H57VfdGL30fJiy0TML0LxL0R!kxk0H8=$K z;K3b&yAAH{GPt`8?shuKbwBSpXFY43^?Z8k)6DdARd;n&b=9uDfB$*HT4I#oSwADA zJX7U7YAQtEw#%G~?()q3{EFa3=FPpenFnvjX$>I0JJQ@!T|uRD7%Q2WSd%CP17SX? zKXI>ruiz`}etK$e1j{@g-~y8K?XUe8BWQdB79Y3#k^Uo1X~C~I?eC4ncXCi&>L-V$ z5~fR(U^Vul@TR%Ef=w^f^*1jr<+Q#?;qQ*spgb$z@iq-%S5943{!D+H#k( z39uUb`wOXoS6cmNq=SH@tjfH~q@+SSZka13Slv1~LPT~XfUA_Ow%oiGwfXp;i#hIDl#@YM9=}d} z2XeHtDakMS^6p=`p`DL(g+;5j0pjas!Jwa}`@0?h#q$pm5kUWJO#_@P?e|idLC>{; zQPnY(1Ymppi8BDo91d#-?CK=pXlU8u0F+ZwC8Q=AkP><}=l!=>ubPKPn~{#iuACEY z&o~xMkYs&57jU;(#J@$KfHpfkRs&YXHDZV6)?FyCjh?&91K2QZ?084h<^CoUW$Zuy z0wrpisBRW>U(LC?uo%oUjAW>C8I_cjWH6TbWC;SePADbV=vhr=K@{vDn#6} z;?84}DDh-h$bK{AD4v^}v#uTj@HZPc$T9zNI*m<$W(U;vzz+W(FiO%^kZ3Sb;=@TJ z%PacQU(__E*+a_!_EcIrTsj;rJY34fj_GeXEa`iDdj)$01Seb^9HYI^p{d!au6%q! zu+|x3Yt(-ScD-WJxZGaZIWVU~O;(yj#jcedJ^l?M15()YXHzYjZ7dEXL<8P0l5fJTV^r9-XO2B5E^9PV z*H5Ac6dX4v+r!zvM_VaJy(0a?q(+ezc7SY!V;cyg z(*8ck)(`B;+ZEkMXpj?iFB)n9_51G=^THsp9F{tkTyh^^9S z{|;2G?~SMs0sf51EHUvO7Ra+f-*^F9afLpj(V!O`+&p^ljoiFa$ft?rD5CS(-%-wN-0LR z3SnmP#d7kIUQre|V(JKwAp#QWPO1fyr1U3= z=Z7jRMovuIpL_&ZlCMGHLSGc<7&#Y^r0y#o1bsymI4{BtG~N_j=;mxLr|FrqwHpgG zSW2fRzK(^7s^JgZXe*k_jU`hwHoB8877G`O~%#q_vZ z`?NaOn)a0UQ;PVs+y9?ShNkbI_c{l9A|C7xuWcS(yhnb<$}9YBI;|oza-ih1BoQm5 zfn8#*NEu;|Z(H#6syM5WanyHs@9?By?zi_?9Tdt$-N-Jol{V;V;B}Hh8j9N_8j2$Brpwv0U z9Q4@t&U7lK*dB=#X1Z4Ep+0Y&lPRMrCVJ;^wueqXfN&}`;YQ-nOK6|*ky%QldND8i zn9SHT*@K4h3WJ7|6z}evP37=)1g!{m;;5_hM_L~%#_1-J^s0|yqfBfjSpiBSnjD$gdwl|CJm;k z;_yCi9WLuMQpi{8rMyu)vt#33;FyYODMx(uKz($qDn>p0(@cDO@XJIV4=wnM0o+q( z&o8+phdHihql0ZhRV*%2*C&WG`&0gCTdb}*t++ZB>kWcmqI25cH@sJMS&&w`+3IhQ zZ_;|RVUuK-Dt>!)HAyO|XV~UWw*Wf*_k|aO$w5M%66QNn_Y0nDM zzOjS|yw8|f9btNl{}}A>qy>d6wd9^Sn`%5M=+f(8t0bMh$){m(L_bEeaZuqO`2w^Z znXtRW9HRkCUbHYAu6^BdC;z29m%{YgkKa`|cxAWC2fM9u{(XvUbYvjItK3KCo&6T0 z(dl9Sa*i+q$~!tJAF|4k4d0i6C^-@rV7!ZI+Mj~<>=f0<$yuZVvb~9TmIP*vQd7<@ zWdN@>Pw={jBM2fBRCwrNNLII*W};tp45v(@KJNs93Hr-B9sK0`VJ4QBCW19<=WYgm zLD~`^?yn%-RE!MzZBwCZk=Kw@(o&RFRn*j!0ug-A%E}6ns6*{0j=r{#`yaW(Jp8ceMpQF1LAuSbP%`2^_!VK>V8> z9c*06btzFL5~8iw#^xp&jF^Qb6AhAPdoplL#w5>i=7E&w)$ zpJHXjj*SIS?-V(k0OU1eZ!$xQ26*EnQzCh(i~N?^{Jgl>=*|u-l_u(HrD)KpsODYx zp3mU9=yVNr`8`&jS~sX`oX%IP^8u{E!xdouoi4ue2_4WiC_WzMC3tYDtFN(|AQ)p{ z!8a1^L9mD;-&91I46i z<2ygY>6%zqXW>l%ng~okSeW2jfvJCHrjM=u0*bl( zm0gwDnAyLG>ZJKy@z~mquJ9KvUEy0G|DS*I=cT6qXlHGmj@k;}id+Y&8%0*tkOScJ z>5Qwbzt0b>so~`2?lbk(T3Yc<+;#(5#RzQKFOqBgP)Ep9Q>293q>I z5B7*I+pSicH}bU>K1sD_Vs!??cYCA}(=<&a&!h+vC>WmV08d_9@DHT-A4Guzst}Kl zrM*|=3PSu0Ty71GeTrfd9$(20Rd2=klC5eKiNFz#lI7w`anGefSX?a72)~(J82c$$ zd~{W9;+5l1Q3rbL)+M3az3VqN;%0@VS4Al=$1|RRS`+^m8p+6>wMx(rxn3$8x4BuUDeGs|2_!Il{;Au8++Re+C#a6zeISuCj}iHotEi zR^?7a0f&`@b24+@BVc9&S-bqr`X?UN?ZrQVo}a+ z(xmS5NFT-duq`BW;m~OhhL$#xjr4M@)^yc$+$@#i9rFl+yhMxEw{G|sl9CHu^j?%+ zueQ4JUlv@o!_D#^Y^xPuBMo2%+gumg>lLnJN8Ud`Fz%!Hvq_7QrqhCscNsWQokkde z-t(|R0z7QZOLYqG$ARd8z8s^}y_2BogUTYoqng&5ssG{wjFoRJwnVH;w z_KAJp#6nq}bKAC0!LQ6!NagKao+z0A)RQy2M5M7bUJCn`s%2`8`a`L7F*6Ws9YIv1 z4GhL!A^|wR%Ni)>h{iLC7z$nO5N^3a?4*>JhHRj@}?Jd40F_7)N&u?IJI$Gm9 z_LJJl3OtnqGS#KFsiz@b!3|B7$Q9@N_xGZYGDD9My9U2Mi4Dmy2$B9D4_@ zFfJo(8Ac&pI5;f28TN%`?R&@K!$SFgoc8wrprF&GRT%!c{n+M#g;3b|j_rh3%;rID zH}o?VhD#>&g z|9kueel)WM0HWDN6@Uc?o?|xzU?O1bC+?8weL5B$b9Z9jUgFb40xVC!VEe!MKK?f* zmk~ha7zDz+_qaqq0;cR@76dcbSN}!NS(C;@%m833S<9X%bow_aF zZySxF9VOl5|4BTaf=?&o;NpUCa05n++Ou=xO_+c=mIv`DUeP5~R!yyj`)2=%_kQS! z@MNL$_{n5_HjyP1xJV;@qR3i`$iYDMi|@fTS@g4+I;8Nz?u?$G_zj{bI`%D}1tecH zv&|l7YZ2dwwO0W`k+J3l-V%=CW5?vKP1mc5xPV8;#=^VCW;MJA12nQqD$l+p%#N<| zn3dU}ryPd+cUtG;^agqeSN#mSn|s#p2AOXe)Squ3q|QsJ!RuJv^$k!Bh0!dTjaUmR zMZXl3HlL)+s|xgHes``c$M=%P3+mB4(=HNSoTUOvIixLlG2o|_(1$B%V}o#(0T#{W zXBWI2E5%q?Gse12wJhAmoQ;eB`aE}q<2jx907nDdS@r+LY2Hy6{mWlQM0`H_x?nf5 zIlBl_p!UCWnkUA_*rKUg@5zA zqN8F-ThiOAe}S&AGVcJ3t^Lu#>h5ewQ$uAT_Z~e9f?5ytfW&~r*R%hFk*~tO3mxk^ z^JuuFNVUY44(BlN_E%OsRMFV7&FdsTYEe|W-v1HMHo|uoniWlxAMY(+_c}!6pBB@{ zq%VZBeyiwwMU*4GbG||GBT}}S(cIR31kF;_^C%c_wffY9)-Rzub5jEW@V9p5V0H%Z zX<@nWj$|PaP|o7~MYizT2I4P+^mTBnCz^HRbaRsKd(CPhyh$$#SDLAMg!o%jf?3F& z@ZjbEr}nW=4=Yravo8e2Z)n`gw9}_Bh|eS{pozron;U;~t4A*KTMHwR=(=w;NATqi zYBn~f-Wv%g46s0cSLVj$*A=TVw|>v^CK}&S{t&`>RcP1u<7BZ8q%w1=@8$WATTP-R zmzp%l?d(#MAgm73;0o+hZ2-Fj1gK4AYkROSPdsx~A0HpCmP-~- zcc;tE5M^|Wufe}b(rIX(bzI6AULt}rfhJN}p6FFgNlpzu4v$>dpEy3=?34NUbcd5n zLmYgP*N{}(-jR(svZoml%G4pneT%#uRfkWh4C)%&fwlM#=#5#y;L8u~eI9#?9wxRs zQc4{*38W+T-9)mM7twp%8Npp)aVFWJd;0`(=)k>q)NZQPa1dlhPvw=mZr;9nc_hk& zn5bVVX+#1dZWjJ{Bt(q8>ik}l~?4iQdY{XF$^7ht`a(&}KvJjYjIzz2+ zA9Zpx4~|>Hhl`J{_T&O9ag3QPhZUO|mNKzRO+1JVHnF$q?-T2ej+aY{bOD>{Ut7TP zy#APPtRm`ZR(e>%GHlctgpTS&>h?2)-zrslK#AwTgcxdO}ceTLxIPzF}J! zKiD=LXJ=N<`{f$iB~W|y(&$bF_)Hj{ErMwnO6EH@2By|(ZY@XR^{x52MoJz8#bA%? zolp2RHg4fPN7L3F8A=@CUYaIr>$ogUaih=OO0#)HHFuUE4oTs}NS~wiuuF3(Mdf#3 zTjO(Csra#|p-&F>myJjBC+N>dH{7@%WCg{-{aMCKvsR{$L}m2z-0vPXX`HuYHeoO} zTVw0btsqe0xkML-8bQ}w#m_XvK#+D5u7k!Fj;cyE3+xJ{wRKYyyQWr9j6tbCT=40SpAb`GX2gTfgJ@fgece3F=AQv#)o zDY=W`jHD)aL+<2Ml3_KO`(1g06GO3go4}}@G7fj&w01@UTMqkdVNLYxZgQrF!C2%k zC}}h$_pesS_)m{(#lw}2g%TS2bpgBE^)0D@y#gv%C__hiM#>%Q@=*T9DkAdB&u2HA zBHO{HS5z*p(FnG+*N^P@c@Zi1mwDh4F*wqlx!n^{5|_piLBgl%i5Xv(^H{Dn$nVf} zrP&NR%aiA3C;v6a2xM)ufj?070R}duzdj9w7nwdowf}Ox3-oY>T1E$X=AEt|x;?Fy zn&*L?UtQvz(yYb`h!d0>K3(<3zV2+oQ!-Xl z=hLz9!}(Jbv+x@yz`y3quM6dcp)~ z=7h(=qCH9!u;H+uo6oe-(4oNq- zEWSz09Dd^Bs$n1lDW%KrM=e`APaZ;To;-F_pNMAf(y}OW41f)YEdKyWcN_cd@I$kd zAL#Bq4<;)6N+j?gTHkdfQ2z1>N>TJ*Iyf2{8a*t`HGXx8 z7K;N&Uf9OlZPfb29>jUg4X>}-rn#L$h>3~)`Efs4jHf{yVJiCyNbb+}(0edIauGm9 z(fww6Q&XpCY8dChmmyS9bJf?y#HtGy;5|50*%n({{{pVtOGy|#{%eQ_an}{h^Ln;q zlI1cmY5RL>3JJ#6MY?JVoD>QBURT-b@0?%$&N&arne%tf12n`$>b+T^83xwY05H5P zSo8gZ0|%!97N~G_L&e~+biKh2cX|1`i_{4=e!0ZH{cuq(>VjD*6>;qe586WoGD{Wi zVJP#cA?8(5oG6UCFnLn6BE8vRM&G}0a{gYWuxn+#f88xE=R~z^zN?;}`WqJ>tC&nb z3Ko4}m?w4D6UkeeYw@D19ZZ7tBrIGrCzYpecoMhvA74eBiNj&C^%C+&xtJ!esd9=U z>JFE#dx)Fd=Xx?|m3xb{JTbptiM}?#5pDsE{;pR_Jivp; zVLH2hQS*E#?-p&u&Hey~)%$jhfj)H6QfB*#4u#t$;W~Dl>VDO&6Y8AJca24U7Yxc! zs)w?a(yN?YoD8z17B7c8JG2#AD#_u9&*@D$79oJhoWlxrz-*DzDaZZ z!l8JQdPVXyF3)`x3u*@i$m-|AYp#5#u21C;P4jnH>K~$pk>sQSH^@_y>K7#StO0nh z@`SlCa=?Z@27MK|4sz)^QrViw{FEu%qgQoZnxTZIWf`p^&ipOTB1SH(w+T-5xyt~- zb2U0M=e`7M0VOWc;Ky^i(2gf}`}0oG6agn|S$lx{EJ6@{K{-a684pKETOKWtn{XcK zR>#9QVoYPd6xTwintVWhV-3;5!IgRZSexjW4}olf=Pzl&XAA@n&ByoY)igLYUOQKY zr)z$F&>{S@22{`0o@|Ec)bYFHKdTCZcXYdxqF8Ln6l_}ubA6P`$ZEO6SyXm9N3qNF zeHG62xb5MxMx}*L#-DgT?4VADmqxqLv2hGsbFHdk{J^5Uj^zEES%F_ zzvVin1{j%2WT#deLK8jOW$QwQ=MS2%t|)apJ2Jj+*p zlrOZ5+tji-TQyY))*Gl0!V;xyFk#8!`-QCSCSeD(UosRj#zm`q@5Mt$k;w=}R|h{6 z?h)RahFxZrP9tp-RShy24D?_O%SvKLsNcseF62Fm$7>Oa9LSC>u5}GSk8Va}agDZx z?Vgy7w)Cl}?zq@^nXf{oRu=<{E8o63V)xuGz*T@h7>sz@xJu?F-J?UjGYzvZ4SUpz z*EGbA;0s240da~&&t9(-Q?x>7pmoV@_n29cCWS0dhdEp|WmjEI&(E7}8%{?GHw(G= zP64b_-hT*)G+vDlU`Kd?0ETUJr7Z{4>!WWvd0g*V+MN*T-Ax1dN{T}L24vf5czd~h z6yCJkvqT)KcUZf3NK^l*Vmk@^(9ic^+9!Xm=|Y725dIhw_OzczzOzqrCx2FY|4o=J z4eV(+3%ybH7^4q)Xku5X4ZOJ@_rqvbYga2kKZS8cB`v4o7BB~j|mY`tp;o|#6ar0y=EQQb$G9QrCN460?pGn1*Cyg;8TR{~Z-x^Rn zf4Zo?KyjZc-`r5;H(-j@RF-}`9@p+5vopL6khKjZ)+aEUUH8eDuB!!fw)l>6EkE)U z#iQi0ycEt`Te20Lc|#i!o+{IMLWRoWwZZp5E5uj2WYs0UjzU)TW05X2?fkjUmmQ2n z*YVR9kA0fM4ZrxcC3m$)M*hR+j|6QuG_~1GDC=itSBi6EbHSn|ZoHA)tAuw6DIzV( zwx>pP8aEw&;0F7>vehQx3^F(*P!(UCpVOPV^AY8sGAQxgEY6`; zjDAb?W?Z??_cEtVbl!Wko3C@;_xpLJy;~imd8egmP@p=?)m?q{yg81UJ=ywWiJxN9 z4v!@!dBrw0>**OJ$vh9ozmtcQ2|cz}Ssg|6)9gIB#eIueofdw=BP(j=cv&k1$#)uH zP@y4u_6+o_>>19$E__;6R<4U3LkS?Ppc2R(|1|09a&CNSHGE|sK-FEPyK%s(=kiH* z`*PCT36MTR2b%d%8P09qT1Q1MY*d|}dqt#Jid`bXAIKGdrxts(~QgiDryuBP* zDJS>Md2Nw@9nqU!uQ5}Uwf*xSk3-*=_(QRUTr_;&zOX23nxo zk*WLP`5@Q4yB0LG#Xvh#-g|T6|m&5_NTNG&Dgcoj1keX>dFh1yeAj zzSTd|dNOSTQ%V|oRj%mUPglijRwwP)-Ua6*x?QEPZM-7Hyy|PW(?VYQBjnhvj zOTeUB6I3-~7HN(8Xzgz`frRf+6qcr{}HU)rPgSNx4yCf^4tn;BEbR6$Tf@ zlugT7fz<(_0DZF9>{7$EXN5_u=iZe_!b(uv@L z!ewxg#m1Zn9Cv5?%C=t--CTA(ENBTwieAg<$m-F@JcbIGTLiMo6<8n9dE)A6uQp~_ zjSL$OKaO9yp)JK6p%EVX2{*Pr{ivs*iBX+4`nl4RkQ=-`(O|TcL(}eGU5m5Q(Ze+D zUeT1ByLB_#%C42LXB>_0@N~(cTJLJaPOmoqIkS0Y$Z=l7zV?jumWj%SJxCAE`5z!BN-tP~hEnikA~!pP~mxAMXESdC4WMU7+ZMrV(Dp$LbHmQp^Ek5d?4s z!yW%@IV_^AZ$L?W$jY|5fy%6sFq>SP=A!cmnkb|UyPSi4U?Q7XwkB=>Oa+O_4~q*P zBctC_q{)4@9Vws&W?yk&_n9;p0?Ui*zpW$_fsjIA5qbKyk4FOGTu=UHm_oaU&;8rb5|ej>);c%(pQvMIrd zmM8l~T2mOY>S-xqQC1r&!wp#7E&Yu$DQFKw*Sd8|z}Fqo%jA951;Cw~_jvDxYepb= z?DBQ8N|gp?To`IDc1k4}-0@sDxid*I&`@_d%F4+fgpJ`jM4suB;d@ijEi?%ubz8U? ziUU~w*-D!n9vM_m%0OHe{dU6eVecc+r4>5A6(&#kfHU7~hC%RPpoa~I#ov5q-C=8V|!Br)N|_INrGiL3O(u-uwVDr7EsUVvtd%F zSq<*gc$8Mgfj2btG}_W&brwqg@d}Sw@QEa9KXJiZ!k44+x0whP;cG~P#~yqF<5Kc3 z^`A9`@#rs;Tj?N4@eGW5QxH_V`*aaKNNK5u;PV#V;W=9qXSdwmBgg5DP2=WdU@16( zpigjHaEnT2k~c}Ka2fn_rRzn?e0}i z!dm#GQ8#~eeoIkMtd8afiTw6uFew{Q&yS|HWimL-nKw!kb7g^QjTruH+(}OQ7`Bnu zjg}t5e7X{)9o}+Wb%BV)jO1MURqW3|6Qj?~+2YRbNh~sQhAQWl3INvk)FegLnjy&s zq-%{7lV#5bf5OjuV`U#bgjg!<@tn8gvE=sre0-liBvqZK*cJcBCK;)IgmETaH;rFk zW&l>SiuCC!oCezEGre^{(}u$&LjnihEI#Kfl8t;$QsX+MW26O`L#(oC6L!)kw-hpG zUu}3CAdR4{I%`})(HwW1;#_8KrfTm2xBev3G{Uts7QsL~2Ac$Jt6eaYaFYqy!BJ}C`a$uKc0QyS9%i^T7sl*dt^;HP?E^KH{Z_lxS~-u-Ok{4yI022f>Ck?xG)#x z^LYIIiiXm!HVEO(!)H*c^bd@5!s;QyoFHTVc~BA%uFcvveZ zc4gIrgY2RU^mHO!8MeL@J7s-{3Sp`${@k2+VBYtBdL_M5WbL7&8b_w_#C%zT?uii_ zNS>ZnSYaNT$th1aTSYi;9vod~_UDNa(0Tg-T4zhxrQsl)l5Qr*G4n3jdW zsh)hEy_p)x#gl3r=dUYku0E*behHG`jEPf@60s}IUg#UkNXg?iA&Ngm{z@(0K)aeMHgzzs;cd1hx_s3nXs_sfz*7;`l#JzTF2x&3C_$9p?KUH8dXHqfI zdLgT7S~G@`c&Cxog<eO;o_6=ff_^+LR;Cb7d-+J0pJ{_fu~P^n zHl1;<6=~ffqbh4^eEztmz0jKxm8nc8-l{pJ)CBw5s`jqgHq7w1JVc_PZ_2?+IIGC+LH9Ppud7CP(l*0Tpje#kRljPL}2>$ss5{cddQu zX->W!J3FLUTIetwot^8;7)-x!(l-x@B_}wzV51bx!~Hm*oII77Sy!77-AT8SX&mZ= zZ|#5Bu8JuBG+c-^QMb|M3@$fmt0ju$%6uy!a0uIyO!N~b7G+ujJG`aVG2viDhb6InB)j|~lVh32vBzCE(3c%* zU8y)l!)Jm3mLcfr4y9JBwTh|7F+lTPRlmXj4Fph*e)V?G?3%q{;fyYK?7H|!xE+wc zgclj}b_o)_^(o~2Y@5&R^=Vdzp2*K@sl-V71g?xRjFqmc1C!|X!0LW(PZs@C4hhud zVj|DmQ`-f8cB-R#u#&X7oQRt2{d)tR*{=C^LDr|w;9@9?@e$KooI6nzlYQ8lHUkh4 zK?cG}&n$?`^_*u>2GP>K>A{#Nw>l7w6LH{=nkKSPzXvBra#erG&#|os6%#PELh3#^ zeZN#K;D`wcOG6H!wCXIyg~lFFijvRlh%ew|_Ivp&OuJrCTkyq@RfOp_w=O%l=Y)&T4Wv)1CUp&!2?5q-7 zEqT-G+webVl0+LUc7M*u9HZ_FCyea{6U3Y)uzTPGZ8u-G|NS z6+UbnoD4@60y~w_eLmr(Gt{y&tzn_Cf<99Q) zGt!n{`=`w_4*E~ z&-jF+$;Dq({{m7vkDD+A{i=e}4&5te$z0+Cs=;!U!K$3gdf*+t{JQjbW#dm-s#r6s z!EbL(X;mLDFf5DLPp?f23ycmJ3^MMys)lG9X=3|`4wAAKKYB4%%c)9PSWvZGW>fq; z}`4Y@0^e|PBdxM>QD?M?W=su=?cNd1_5UB1j+S<@w zyBtVQ1%L+O80HH$&aO~2Ipiwbws70WcslOst0?-8 zi>fiNxIH+#MIF43*;U0S?#;Y5b4T$p?0OE$B`~&*R2%8HD-jpUb@J1eKxbFz2q|aT zvUf!`^as&qF7q`No6Q}F1)x;rgr=yAop*nZY7u-FKF*ne_G*WgGqR_mx$rU=8bQJ? zI|bNGo%82QMMu{2UZtIFE^#hYOiM3Ba()jHv#X(uj&Stsd(pwXJaTW_tW*(SO-d5e znj$=2XBn%%cgvvK+oPrtoqykZz3y`zNy{WTGo;%da#Sd48^Wx?x|wNWa)Jf03IT=R z8de9nZ=nphiNo^^^YNv{6}oxK*$fL3;$;Sp!N)HS$ zF&xoD94)b^+UN@~40S_23A~{mDiKwg3wyNXBi$j1cr<3ORW!M&M5~}nuGiv?dTkJ5 z#>QLw1%X!X!_qR4XK^IO2KB-pe5$Bjmd^dXkTP= zuUF5%cs~iQUn9SIIS?#s3GXN(>?V4Ots*^rD50(HpR#s;~ee=xw;IKn1M}n3w(m1#w61_&?M@!GW`L<^_sG0iP{^Mu)}w8fUkXFWk-;p zABN5e%X9s)Ia*zTR~j+S8axg@Oz4@B#n=IFY@QHgq*hQ)h}mdS+VyBbYALrxK=m@AdZK!Qe?{8Z^#f0SESlE16?&ta>B*wdG zuq`%j6fUh)()7=KGs?|fatFe>x`Tib`Ixgz@zvlLh+fdzc?wW!EcdvL>J zU0Q$8nfgaq@_T)k${<^LYnq>pT+CKNTDQ9%@@rEPeFH7h(i6Uwrw+?JO`8P2{87hv z+;?ZsOs)_u#G&pf);uVRBsVj?9ArYYH?im7{8zXgR2MS3xCIhBHjI|dw^0^fv@paQ zh$3^{h!q9e-32F_4_3PX7uUbGfYdov2LtFn$oipG1XE&EQ)0|yeb@79O#B!^RM zmOiq%&wdoISaV`c0is*gQuR}yK3tlOQ1PPV3;%`X)nA$j2<|N9>%}t4{1O?>p>%tJ z|Cq>n`at>V_7Jxx{?}b`N&UMP(jXW8k1Y$oU{*N>bg0UjuNLEljPlQ1XRG$`bvLF- z3~Yavt*@{|b$-`zd4-uMe~9Y)aQV1Jh0R~?4FJH^#n*gAzX>Hk%j4A7fddS zDmd{WzL2ZR4}~yebsjNY_BCjK%htV%j8okNFW4wzw}46mv7M}7t_^#fUnNIS$Ii0975<; z=Sj@6O9_KmMUR-ixF&nRG>z&ZQ^p?5+f9OMZ#-`+?2fJkGhH9(a?J=M3>&j(j~v5i z*i~GUwPuO{)g1sDw>#0MkBgx$anZo!rr!il?90TJ>wDQ^dlcK#^ z<_dF&a?Tw~(PquvTAgFq2O|xfleqI-=vg(($6Bb1nP)(oJ%UK%b2*uA&>=jF|l3# zKz1gc7~o;H#I-zie~H3!Tbvr{sJb8Zfw-gk`xZk>$^FcE=_aHwk3z3UeB2qbdi7jT z^hc@8hV#?hX5@^u?aGnr3vXdgpLe^t^q*|%9Xt*zcdl-|nC}Gq3|03i{4rNoy56bB zaiZg@@e%6wvs_GJwZ0mC8JBweazgA>^1>%@CZ!et1PeDn3rIv`JcoR}6bkQX#N&j- z$S10-!)>FIvy5&ws8Mlcu0kdcXe0{n3S0Tc-a7}3*7$c%wRcth6V<48!;>At9o3i4 z*Sru@$VwX38I`$<>&G+^^l^r^50TJAdJs~kh3CvgH}UyOIck0T=J}aiZCWtV>yiec z3Wr$RAo~~M_A2RCDplQL?X|+o737t}n*`rX2C^#!;o3#%X-sJFF-q*bu3SQs zHPR2#e2DW9{cbDavR&S<+WM%fPh6<`mB;CYus|?%@`&{i^Bz*$D156M9*~Ifa|i_2 zMaUA;ryj6ZOBQN{hFG--sFkc7S8baeuasHRto1{V;RC;yN)yQ{t@PS%p8YswiULO8 zSKGN7B{x6bCqn5sf40%Z#F>mcFtH8X5~J>S`lU9~=Rb`}`aj9VjWndV{s7^IX4S?#Yx6QYOnj7GX8-SHyKh2Ty!@ank#)iI zobElshUJ7_oGf3|z~t%b3JPw zpEu!@I0?rYTJ}70ncBfWon-Z?!#IjLqZ+;s^WGW79II;UO)eUao(26gl*+c#V6Th< zrH$KfC3kzAfosPrz0O~(pRPe@Xfa(%G}=*}B+r3r2)%14N#UxpS{I=W zjgDS#^>DuxIX^KiG(NZST^wcHGov?~bGLrOA6{)5zG`XvGHkB|V%kT5&4A0*HV66j zLBJU|G=ssK>E}HOWAK1D!EIvk-Cc326@QbJ!{#@KJ0l0bT7BY1k0_GH@X+y{jlNcD z_H-8(U5!!*+=u>|#op(@RNbxyr)y|?fBkNnb@^GhjD*y~oU)YFaWCeh*zsv;-@E2F z9iCq;L~=3i9wFw6@XMOI&S+P}s5 zz`&{#@NMcIqWkF(pnNXRku7OArQm_xJe3~bht>4Em8yv|Dvi<|_|z-A4NQ4gDS!q8 zn%%Z3?2q17a4)(M5QFS^U#jnw2ORCo6;72y&cp|b>qmc?^QoR*&D#`T-9WlEK3-B@ z9u7jBc;41)$hzen7>b)%6o)ovw*>_X;JuO(D)})m`-IGOuw#6*JvE)zg!teaX$aNm ziOejpV>KVn8NTTZ~;-%IGEDi2wa;I}+z>erTLd9%B-=>MpoFtq^4=#@lH zjr)`lg=J=#1PXo3&+xQI63IIA}X8;J3D*)UzHRpE^byMAkT-gV6kwQs{?8=z#m%;fdH5?rf6ON77o)-(yD;P>&?~Ifu%>~aZ=@qMGeh3 zI*+r+00`h1461qc?2}h)0?yrYz~BGV7h!{`1&YEmdjQhrX=sN~x)f+z1Mf3Xne$(}np%c|+0)&bm1+t7@W2x%$`}I(g#Oz%9`xC) z@?zk@1fM@hC^IKyX+@n6hz6i|$3_n{uyept2G!DsGh;Z_Taya`gujfhihLKH?N`?B z)+v#(wuwWtHbd3ZBI`ZQi+r3ZW3z;eR&f?T-P+kWeB+M>v3M6zWL+BUgbob+EZ`qk z!&hr_S&+WD%Z+Jt2`P30?09Efc7W^GV!%!-{~Fl6(Un~{gW!Z$!^tG<=62|tr()+X zMfny;@sm=Ua?l{2ZEga!+z@ZUE z#{6LAx>?CC!|;Yr-+ke4wb+9`rgsR{KM9>da39}^bih}=v8m%X=@N*Cp-_N8blXaO zi;9H&d2s8ZsJo;mugX8MY;eDhEn`V$sTOg6*fMypxt|6Zd512E%5Ch@MfDQVfFP{r zkI_oOr?&j|1NvxU#!LGSgKUXh>2JZLI05jlL*Aib1JM@WUSlh={w6`0=~v4oXwXxE zq`thzLB$Tm}fuw^pBdJ#0?ZL1T}rY2`s^0hbNvkBGj{0D~0o`3@%up zQN4+0$6Z%F#T>%D`8IiX|BJb|jEZCHqD31?umAx91P>lOAxMBAfndQx2(H21p@T!P z;10pv-Gb9dfS`@L1oy@pZMX&JeBZeD-8aU2_us8wjM3Fy#jagz?={z4bJDx>Nk>5DZ^9_DSrE zZz+s{mto(Wd)XZjTgv%C8>7kwzsH1TU#xv|+C(<}44|O#x_NgvI={6`iOVDc0la17 z3||yrO-7Kq8Mp2PyG=$bLWFs0O%W;`+JSlM`qrGD@V3xGJH~4CAYe6UB~Q#70u@vt zS!OugrIa%Ej&HN{PGSA=#*>5?)Bm{6w@+xBQQj}5x1=mxH&>2J|5=bnw-Be=Ub{>x0zgJX7=La|D{} zhU(&SjyNC)Jkm8W);O)Qm#s=FYM(WfSh6|9;WSY#zJG?oL0pgcTGBqNvnBM>i-S|i z_Yuu=&Ze8x_}oWmmJY{h!sQ$FO%1Jrcie7nZlhJ>y1pG#fXjpzOIT!$vg93V+>Rxq7T~ zWlFOCTr^*P=_+gT3R9AW-n!qEl~-Xmqjyr2J6ob^>iK^VpcX`seKJAVYErjUnLa6* zAC%7l#m?8$C8V^(+^z_Fmr!^Dj5w1VnjUD!(jZC3@lH>IxKWdUX@apRPCh#==8kZ9 z?Dk*!HPty!w7+8C7?4@;6m+G$%Z*`-I*bW`UfNS9{v^6wzj-yuu95$OG<&oTK14k~ z*jWrdSL_-k9R;+}E6lcjYbr7NkGJ#vXd`6ZV<-^c(UMg}fMCjzYZ42R=!MI0u?3(~)e z`(*Up&Uxr%renh+5=i8G1B7%?<3~rM2B)uYTGO43qiwbxU>RYK-4{nRX6khBVh1N& zSSzuYCbg-H(b2v=4wz%cgbikF+yx&;?y zSNpg%>VYAH0lC^H;Rwf^63|$G`ZdS~uaSalMLM;ya!ZwucCO-DRMuj709o6pSF5Yp ztm~9WRbNlOx0ep)ZA9KA^&c!?Xk#{tP(V1AQX-Blo+1GNve3PqfW^+>__#b~jEk?t zeIX%)IfOGJB;We)%D}*xjiJJ#Ku=xk_h7o2wP$iTEV}10&oLEfiH9~} z#8%wfGuctwKc1LckbWjulRz%DOUb*`1S6z#5vSX?_hWvW|K>3fUM8ufWC*cWskZ3W zpMr8+!%$3Ih{sBS4SBzD0rKB0)?Opmb)9caa5nTO6z^MwqKJiFtS0<5L-|AGjFaGy z8u}CP&K#%dOq1%rSn@d-qlA%iI_jZT(uvcbn{lnIt*neJ_Ah4U#uyJ5jbO3JvzMLr z)XF7S34qHM>j~Em*5?*|-&I^25wZwP%cAL!bLUJcMMM!LPJI?geZek9RP;w7 zyE2Qx*X3(GFXR{kDRbv$Sgw;2?AadxpL{$Ji$ACyhmU#?1HG53E-$JnJ%(=|ug))rwruW-!h!*wx>&>q~tQF-+nVi%6s#W5yT$MDh(3 zqp~ETPg#A|3Tj`LfGTO0RyZ8e)|N_ughywauuqyJOg=hbKKPFb++&g@1= zTzqZpO>1jEt%>8}n8gcTy{DO*Uer?_lC94_j>@O}Ik`QXUA5yG)5#7CoZBsz*EwI_ zIJ=XE(ZSu;GKYS1&bO+{XX_G!BY1SaePZUKazjH?ph~}{2is$P+}q!4o@!+ z1Hp2^cfe<;$I9KCuyt)-s6=+g-T#@yF$&Jga8 zS(^2PycCeCeV@~7xc63?UxD|Sp+$6GpsGDEt$Xm1UeLW3p~R!dj`5Z=nFuNBL0I`T zoxT!(%5iSuT&V!rRc!W+Mf6{Gts@tc2$}KK`=Gu0#r|D&xRfY)a5W4Mu$9qOqb zxaVSs4iK35r{j&k}sL1c4k^nHaio-@^1G9_+nU4!|r?jpcKWa zjBF~DJu8s$EYyWW+H66RRN=157&>)vQwo+!=(lNsQdubzVgcX=r5Ptr!f}PCfKk5@ zvR-W@63P1;XJvlW` z4~O|?|1cX58#BewXi~f=7%xoLmni+r#=>qsp_yH;qny>aJ0-a=5sWiv@7LR*TGajF zEP|MrWcHc(Zp03~{Wi&HRu;Mutu1w>Y+YMz3`Bl4`pt3#DhG!v>%7XWaPgD9scUoR zMb&E@4)v%S>zQ{ZjEtjMW%Dnc9i6RL*Tq2^Qe-w9iL$QT2a_?GVeVBQdQ;t~tUqR= zl*^NhF=fzJKAqL|Jtd2?p&lR|Vx8JTEgouc)L6GA7(W*;g0U7|^T&H&m*$JC+n?c( zUxiWynD%N16Acf%TE-RI`}17rK4%Bdv=;GzZ(Kg4CFrib$DW zlezMZ=@@QqFN+S)veeao{`k&=KhiidA*n*FIT*6364{^w>D(leD2^pJukqpfxzMtr zS%&i)|EeL^4J~S6dqYVS#M&9BZ==5y4x_MdaPJL{VAr}5Y~CaY!n&2$7#f_*b@2zc z+#vQTZ26vRMAziO<(CIvK;QtjAcey8EZFb2)7>@V~+edYt)sr#@&Wy%yVw z!iu~J>B14h^urUWEgHzSyoL}Kk<%Qj9e1tMm)K@}z(SF_F#i-G8scDU?`ZGD*6fka zH9v*snK4pcT|Kr^cWWM)@BH53Rl|D$Z}ASrJD+#lUgrrpnO~s1=Jr0c(asSsK%n@c zi7um=l|--fJFi|{iYKsV!@?^hqkGK7<`(8gX=%8#Cr?FUZ0*1tEl$Nnjgwt7Bc~0R zAvRt~&4D=0uRXF9(B^w^jB`e^Ya4;2#FE2P9*xYFC9gK135SZy0 z#Z7Q&Z*1w8B|CyhfA@C9dA{gpwjg~5DxW=U_``)5#zyGi!!1e^}oZ~ZCMy{q5Z z*x@_~GXr!fWdG^>9>}=$=#HRt-PGC6x|>teQIt+nlnWb|_w#+B_EaEBTduSa>2SgA zTzIA`8K?O+;b{LI**st;;XT+IqtdJktq3Kwyr)_Yo<nimp~kG_irFe&W2V5li`S-!ra@Y~mU0sDqhgD-cca5(}H#RS>Kb?VNVwz8$U{2DFz^ z?BaM+P<$Aj)$sogQbYFFqArfp_@hd41#)ce7f*mlaSB zJxjL~cDOasUzYbA>~dPrC1;)VdUH4HG?*~ckA{Wq&5bN7n4l_L?7DlrL*lL{P5YS| zT9VSeMbQl0lAJW-m}#=dzOK!de--Q1gjI8CHR~$Qt<~Cbe^H0z@$zo1%*^cARu5DH zi|>M$E0Sjf3c0=9$IVYoCEx6BksWvfw6p2Y0=;}8PH1hgvY}J3l~c7dIi{YhoD<;# z0frR>0mrKF;eoI|X$%TQp1~*<_`$B=_9Z|DJ-+mkLvVzVNS0*98RzwQ%Gj|iEQn$iK8&b9K^M6cYZ{* z+5h$$uO{HMB$%RW!Xg`L{WfTeXu4VT{sgU!TARHqD>OC~CQY=W5cTjgqPR&~ce*ZD zF69&!5?GfKvT#JCf8Dtn&fR)O(|p}i%b#uZ_si6H$*n01#YO+xYl`MMf;XLZsNO%_ zZ;)B^7m#cuNEIMuqjjLCgslG}c7~`n5YL<;V**>+oq2pxNxJW|^koT&rFY|V8g$Mz;}m#^{(H@wc&<29ccrdX)>_sN03>W?i!7>Q(FA><%{5VZ{sTntID;! z1$p=s-mPmoA@jLi{=KppROWSyrxWg}TQYXhR;C{ID>GiYxN5_tiQ1w%@~W)1$TRK* zT3^E~wt%_y{S2*b(F#B+`eF0bAhx2tA#D@Mz;GR_A6PE0s}@6)qLbU9nnkM!w~n~m33e4=v=eAZ`LxNi?zbjhljY3RHBT5EBh>+ zzsX|IPw+=h_A(0*Yx_6PmdLMk$ztpN5ZC6Jk{cG5Xr(Rf!e?3J~xGaNa!|j^}(bwx;cu1bDISl~l$y0_0w0ydk zF7vf%qj=;@OaOJkM{)7<@snS`7<(eDvQr{U$L9HD^!4@_Y#Uem&gzB`A34?BKrQ+RfD@@6^cpcc4hK0jds@1+MtqWBub)Tb7F4X-P1{I*wO6Q!&I*FZ( zW|zjjv)DycKN``DGlv$^x3Z=J8r%{C=b?nL#1HIDHV~N6s5f-QeO}r2mAS@_6iV)g zP2!PtqSQYWCHx*=nudRQ`Rph}1>wTYxN-U3E15sM<|(;kn={w)5f*9YzqNx#vEA(n zZ%+{lianrQ%+B-&d~|3F2#DF*;{_ca@|(KaH;8H|DJ!WftE)%|8)yqS0V0=K0=-}M zDen3YwpJBk+ZyZ3S{5A91m1ZJF(G|L6bH(UL{zJg87|JEjwBaR(^if*?%`hXQh3(h*mD=cI5vR1>GC_C2Ol3 z2V&UAPrdxDK&`ESEV*PxE$8kL7VD>7Gvw0jtfCDabt9*+l6Z!Z^$YWWJZk))EqYr( zKJ%*3*`adMO|$|GR46+aqtY(Nj&XU$EIuJR($dbmzYr&&xiZrxi2!ybHpqhD-Xvh- z#cwY)J^+!YiAU{;GA2krWJk1kP6(h5c=KkXf7wz@1orhh=!yf{=nSULwLzFi9NV!u1|f*2Jt6ciME zyxn<0^>~Scg%PA;bdYHPxpQgpX!-?grJ{feN{o5!IiTexvP(3w+sjC~4L^WF`0Z&{ zZ@QMYptQJ7K@e17??J1^76PyTtRPDcCaJWK03YqZVH#Td(!O(;1jw?lx@+07HS zl?DvE)AKrk7<6wdQ(#a>?l;;cb3kn%VE{3=aTWv>oSSNIU;N9ZtUS>K_q+qxt!CEP zVOj0^6oQ^f5)jYUMK2Gg0D!bja!@I1E}{8u7*-)UvM`yFRyXq?{du}oAP(ITef^v?SA)(O_7~XnA;Q` zHg^9TXY1{EMj!NOS6z0UZhJyF6D|8fXd9g{02w_!$}LntYS}X33j?j*SN1$3g=DV4 z=`=hjwo8uYpRG&}KuGT=_C0&Ng3UcpP0&q<&EEdx)KZ2tY=coXr`pF;DX&msBbT8$ zDy0(}u3ax6l)_CdgxB@BzOIV#Aob+g)R0TyH!*iT^A3AXm*FEqO?tl{`5rZ2{hW*9 zhMHEM@zSMLy=LlGb;#V=KEdhkvWc=a$P0L0kgpS0AJDg*sZ?EONnaD`P0>ecUN&;J zYKX_8tM0|q@9l5+h}TDguUOuJ9!){p=B*-YY@*Q7Gu^MsR@dnI!O{EOOJy}FmF*>Mts0 zzrQa=_9}_h>#0SHXSp541mzE`;^#Y7{oReG3464r8=+Mo8+jF;JbM0xpZM$ObNl(< zBdco8v2x{n>VJO}MmbF=snc7(wXc%z;NcL+UwXE{J({KU>6@Xhw6hRza2^nE)hOQz zKyIyzw}P>DWdOtov(Bi7x&2pklxFxf``U4%kA7vY|{ll4*rV5Po>+! zolK@%*XBL6U6j`>-V_5+oZN%`oAg`v9Thk1V80gc;f^{i?4T1I>j?{FtNM=&<%+4t z<zmQ&w9$=09$98mlEm7;T9YN#k--|CF8lGMs2M|xhqb(ZGxYX#C zrV3!B@<~+)Z0nJgKdt(!WB!tG?aGf^s4Ps>G6if?81X6=CPwF~0mdb~{S@KV%DG)r3E9s5yLb4xEkzEztts zFaW-j5I}xqifiCqhg%q)0ufJ-pUdYlFmPFypLA<`Ty-iE)}3Sd|)!_72*tCK?6=^ zC3$GbA-!Ugi9L+fOAh1G(G^IC!sui4*XStfdd345GgCLAe+ZX1xqHnAD}6Z8=HK}6Op{y@o1oS2 zG$?X%wa(T*Z`fnSEw7r!}?K&`UCW|S9~Pm98y}eifL#I4NGA~Er@0fS~~^aJamE(u@fm!KOy5`W%@xqlZRx!ZVDAVmOt}jJ|IS`CQ`< zj(!Gw=L)=h7Q)z-Ie))GI-JHYv*7g3G@j7Be)%fH;R@4;W3UfdT7>v$o6FM>VDJWP zevubpheBC{0>95Ec$gDgo$ zZy0tT(%ib7wm;lqKX44|$-h3afC-qHAIp~II@&`1=pp?5pfhisCI?131&+$H+e`~ z(B6ZJDFC(nXUu;nZP+dG+W@o=JWW>V_ENX#EH?^dff;Vj_3ks|(uIx&ouaa6>5$Pq zhFMH*<9i=z|5SoMt#_6LA}1*#DL z7hI!9D%LMCtEnw6EX#r{ETlY)=Y25rW;6e%XN#2uY;Q-eM=`ycCr@TUYPDX}*9WAS zbN3tIS8pETE1A=Yvl-y91NZaY|Y z0}(ASu}*j@W;zM?1m^2^`3m6FJeN9$gx>WA-h)56pCjNpa2z;l+l}F8^_kS@q@p6A zba5&M6zwzAfAWHU(SFGM1guCPtc~4??veIe*W3N*@9xC7xF)K^XpF>B1yQ)@j|+UY z$|5JfOngyMj*lCGs!hpL%+}e_0@sS_U*8bAO5z3OE$w|y1grxTx03`x@e>byo-85}>J%+=!|B0ANp3(r zfxAk>6;Cxh`#BeO)Svc|_L-U9_OZ^c!AIa5WI!{v96y+R(2XNH5k*e`es9K8LCN*@ zy*see8NS)uQv*p4^bTAap($FG$})GYI{EE^m_F>*j{;|k9oaVI1d{dWQWRb*Z$T4u}T zb(O#c7?S{7hWSQK`G0?vYrau+@mCm@8PVV~P{;>BE4<8~-spbq1P*VdZ%rksfyDqR zi7Uhy(;H*z>FmS|o{QeYr=SeeZ(D#@*t@WFmqB8X62OF%56Ag~^3P?b-zHt>E@xI> zrXz|A)lu|1y4nC=yXdUXr$LV%zOLt6RFvx?eh%>QllZQ3liXbVEU@<1x1A3>l?(rr zr3Qv5z1e621{*jsa>vI%9xRrBe!8+S)G`CRo)||)dbND@+k9#=uIy&tjSvU!JR~1p zHUtoIfB60LC5>c_!jdWt4Y|9hUbB^{-b$mDPotlaUPYAsO2|jS_5})4MuZdL;c(1` zjzt|qjw*tnq$Vq#`|Ei>VrDC60I5i^C-kpjk{->@%#8e*0w#t3EZ9#eF zI8~J?6=j)zX7l-lloU<&C)SCN#$*p#s+g0o&*6^QLAt=Z4IHGHA=YC{LKL$Epk2x8 zG%NCyxq5-A@jh6*va-tLuQOY`+7G({MCjfY=DC0?!0=Ei!@m|jTTIQ$qF&d|hcuv4 zbUCl9U*PhhsKrK2*!J@5&CKwIwUd@MZD~mn_UYM&`;9fQ-abi3k^ns^#m9bZCZ(pR zEY>MD+ML7<$}5%}gS6@Z!i@JvD9Z|Gn-IEp=Go5H`uon#q>V72WDdaMM@3nCE;Ci& zVQ{_q5w4e*2*g&WHoC~JcxPwu&TNtfi5WZaj`yzzk5-wl0V@P>>?pfm4^gSw&$|3_ zS&6bn%}~yE`RjJhV%8>=B>v`6+j(CRz)AUM_zR%Rt0bpjNJaks-r-X{Qv`nvn1ijD zW-yBn`R@*Cp_E~uPWkkjKCQGOaCe`-@;-Q6k4h}qP!>je-jq>KcGQa!!9S+qqFm03S z>^7D{P8I}}mBj&`Uq^55e@6sSrUkJ(T~m)r)gCy3;)66-_SQaG-NFFYHBwnzBfr2u zhB*b$z+~s)rR)IQCx<8WpkKcyW->|AFf12AYRqv*4gdNFIeip%JF-{#71*>p9t0%- zk*#j&DR||4=6lU}5|C%_pEUzuKlhi_Ycbu~ih)A1P?Q+M?H$B%tMo$DZVF!6fGiiA z9L!EFI7F`=ER`}S0et`fM?5;%dSh?;45>nz>l+ssdEy4CLXf}wAzBvn`^!M~XTfT8 z_z2ot4yhMDgZ345)iBV{N$pdSmb!H9)4iPoZXIDRg9Ui6{sLG)Zn4ft*4y6l7Cw{B zvkr7_z1lp%Wk4fwuR92!GCrqYI~~QS4mTTkZM%3$?;!n*pu8`~O zk6QX(hESxJR!Tdu7<^+drN>{TQl;e{U+~sQzF!AQhD)QH`lqb&P`o_&HkPfaQQK5T zEJQvhaxbCGuieg{PG%|f5$DTeeQgU{?Qz)s$>g=qLUl-u7dH6hH#NhUhO6Ket5-P( zZ$JC5t$9JJM+JyI!1s=}rlY={;(5EMcM-VO9@lVTYAiNVV-FNyq4 zaO^iT(~x5`JY!SORnBbS?7EGXRQ-G?2g8h)n&oGzj4yws5C2a7>+7)evF+1YGOiy< za^Xk46Gt9r76lA5t$s3QMIro69s-4ukeN-dpK`He`Qe6tw{zW1O9#x46Gsh-iLu8M z*H24wJ)lz*1rAOIWYtPbnQN!#d&s|^BUq2plNXm3p;B-)W$b0#Scr4~Oz&gjzCgfQ z5J%AY>e?V*fnz&6E_&i_SC+@g_wcm(>M}ok4AL>|;{h>|c_w5zP>5@6=H+=@chJkQ z(yQq#uTuIW%F?-Q4R@$I=n37#33qYknH{%=SSdEJ;RdO|#`fLHwIIt2iw?_ssu-Z3 ziQGosVvXh!MDAQ<$2T=wfuRi46V(9~wxT!R7~!IEQhQA~?bIKnBB$5Px?=A7g70o2 z)t9FS$uYYr=i}^|Go}3Bciy;EZHE*mGhe8d*+XW?9#?tQjuvrc-fsEjlBzEy3A=cA4vy6MJkelaM8dpih{W^LG^) zjY*M&@HJ9gaMDd)rDN8s89$vdc!5Q4blYj^jW9Q@LMo|{z*o+xFK>l;lMgLE47}hK zPDYHKyM$d~5kD(iv#^|u^{(@W2lR+NTf4F1v#xtrbR`6sGR@rP1@){raH!1-w1>2(Yh0{zDULS+)> zFW(A=+%*e^z(;}SH_5!K0UMpTqpv?B&LB}22eGE?I$(c7*^pJL^)g0!j%^{tknrG0TgpiR5f`5f z7+FMe%0|zqKCJ7*bo_ER%CGs_d>!jBAPVZ?T6d_O} z1{6xp@rGxrVmw$>hp4Fj6_wq{@8sNnPczZ^{S3ubs!e`sVKmNts@-8zcMhSJKwpSB zB4)VI48hj+MklRyl>jXOB+z8Joyg=trwz-D1rDlkOf#KKsL8&{FvG1==-4d_I)(L- zv}z`VWtpR+E}hpkbsdQWQNQCMAA4o}SJFCOe3rcpVj%06^7zjY7 z>?(Mm;-RxRZM4EGuSvrf7&DuLBzri@M!q_|@Z3I2x+UA$Q&QVr*~wz17T%tsR79ls zCnSe>&qIc(pTE*{qWt6A@=XfojMEo}20MDXi=JwrV>xlwo&6;{9liKCqpz|xD*dNm zS9ZXrPT1x-yUfI0HG^;6S2Wo)S=AjVS+_%5gBt@D^fPHW@t?wLugRSBXsdDE9zCNk z2w{sAG3!i3^TuZ&MwpFm^zc)u0*}iymF|)F$&$LNiVoh$b?>92@>#)QF)^f!uO-J! zbMBzLzR=XWuM6bt0_Pz*H=SsDJF*9a>#2<$C_Xi?Ams%V%M3HFrC0U9L;7+KVzHqf zT53&gl5ye0p2e0hEpiixtFt>kiZ~`RVsBtx(^IZ|&8ze6C9KU=z~GT|m#MVjwWPsGmvzdLnMN zP%}_?UT#3T+}F|O(g&T8Z%u#Idf9lWt{=gKe>RehD6vBqn1>sa;A5J{u95_((x0Har3 z#m7!cI$G4oywjZzYMxHd(r?#Kii^-oht?>Y#MK3~zLXiHuL~b87iTQyUq2VcQqL!u ztYTP_paRo5ezo$B^qexSWX%~1a~v5sM^!kRHIvQ8B!^|UDiycUr_R=MjFpA+X7CPLi;QU2N9 zJKVQe9XlmkaHZ+kWfIua< z_wXn|dN}`HB}uJ!!7Yg84zW2wu1dUFx7h+ejak3|se%4QpXIhUm#P4c(?QF%(Z1l^ z?e4yMdnz(INbT`|P7urdWVz&FI#-6`_%|w$pCj>{x7;}m&`5ubtapDu-VC2~6dN1| zz1VLLaQSam3aHEFDcM8=p&+7v?8HEAzK!;X?xx~fZTIW&Y0siHvj|T~*(pNMm9d}!Ye=-%zA)JdN zNQ~2oOcMo=v_DQwvR&@;>IS$^2CVsr? z2cn}M5%?oZ+2&H7$>`Ajhj2<7H`RBzk2DDVQ>0kdYmJU~n(SQz7XE2=sq`!ooSN&( zE{!Q}@(nTGVu|Ukqnf{)h&tFtjc|l%`Wk?}-ZsuPI5|G$qv1v^O)ODSbWu{#q{clyKejq}UqS-e{N-ei*W@BFGq>C9 zwrbqI!RyV=^^ zy@b3=@RkUOHM#mvri|tinGyYDs|^p^!_1W{Y_4L^w4a+%S_{}Bw!~YvdTzG^w$q@wa_5c~Y)L*N}htV?kO$ewoJ3y)&zcU>FD0K#j)O~&4{ zx^1f+PCD_3EW?{1ogRg!=8L6_Vkkdq8(g!e7e*Rl0w=Ls>JAp?VjFkI6@*8Ze^3hE zlD=v0DHcSk;CWBE$Zfw}+pMXew%j_iX8R4u{!BS}Z4xFw8d(V}FB9lx&%5u*IGBfg zgk8JVQcwEA8n{2JZ#q*b8yIq4E*eOJ-Q6mR8fT~acf+Q8q3@1gB2{SkSnGJrvs5&C zjFCT7khn-(t(V6o3=jM~?b;+}nx^?&rwJE-RmE5~ZvJwxcc>(m5A}jLTX`I-NGnEV zXLsf-)Arz!NT8v6b354Co;ups-v3s`8{olnh4mEy33H;PWR6#R9-qr>f=rI`L@oQ! zWF^O;TUkB!F75duqUtUW#&35^PT|>jP0Mu@l287=cw}*g_S&{cEW63uw^|!OI@B;4H;IU!4c;2cID5AG`Xcz>C4hJr#aCI&9 z1cGWl!=0Tct89`Ye%wbErN~}FXWZDkmIeb6$Ah*lV%`NhV>@TX7c5>Qul1EFD`s!@ z-Z;;{7`EIBe-YOkWK>*MC?}18$0f>`p3aq7%{?j~Qsc33&d$>9_3Cy!xQLyET5PYi zuQNn^%?+E0ma!z}5go(FivncqHUb`EJn8PV8yD9;1vvg)2AOV)R6og~vXGg1PphD; zSH#s^j{JrerW#?QtmohB8c1`)+DWX(#00l*7VX{T2^8XO7YBEK)9fK>iEl3P4xy)iY>;$B)wRG~d@ImkQdTT=R{o`QFwqv#3VBCq=qkQ)OKwqDnhEE|RS~80F|nkb~$p1C=MvBBEzy}fofBUX|>m9S6N`xm0v%U1dCN>J2wmY(4?&=o^7+LPu3^$u6 zBd)i>#ZWUkQcSHw_B+b(3rTxusk3kR#65j>(o0dr}sAVdLia@wm$q-^@MNZ{2<_ zSQp`X$39&2pkh#Ada2s*JoKPDi1dP2h$mYy{g7ztcfyznKJipxGMdGvQ`l3^ZZUk_ zwKusQ#H}O)w}XRbS^?F&4k&&_LcD%IdkQwMs=Th=L3?(DNw;3aLsog;9@%*8vV=m! z-e$0P)ooNmtHZX5Z+_nTEp%hAb{cY@dJ13U!h@+w{RF8w0^R!t}6BWIZX&9z1A1`u& z`&8tl4sY$ko@?NV#X+2@ljlfplu@V^WB`mXXYQh1OYXN@l)< zot~~J3oMRs4>|NvQ$1&^?RG(^Su>RXH)(<8aDPX?j6i6fd)OZcJY?kD&vyL9S2jvm zRs&|=b9+Be?o-t@o2xkCY2WfEgUlB6Hqx^eGkfVhV!vmx%u&DQp5Czw@4bP8k&?Zi z0Hi=~@m+y5ftLAms^xGcqYzWlS*E4W3#e|+Jq$(~-^Q6I`=)Z9e)(fUDgVrRQu)`9 z4g7`$%Jr&tQCjnuMh}%=E-LUw@@KOB+s{s)k`X0gC%1?h59U*GlNcqNwA!$VQtp_C zZ;_Igt1!zUHnv!7*k__#f4qD~(EViLc=;}jO0b3`OV`(Lzyuy_)Q6MhsrIVpg8kW9 zP*E;>Lcw^~>^l}sEQbd5n3r?!HYu-@n)D`flYQJ)vKj0LgU*{d*5c##Bh}#-Gm3x+ zcoT%Vso~?bLe&Q;^%!57SPK1h2B_kqgoUZNBeO!|JNcS;spGol?%@2WWRsBxZLYd$wQ=9RYZPH}o>-s6m1qPh{bkSr7 zUp;x#gKDD!7A5>8?3u;~2OkR@MGjG=KW`TP0Q%Zh0q%A;a$jpyl;gr8ebcr+;^J#Fc-=yDl{uaOsgb zB!fn(1H3~KuGXICf{q`<=$(^=e;$ar1;M`=sF7bU7)^Fu3b4n_t8aQty1QcRDugg( z#kH7YUmq}HUp9#p8?qo({4d!Q#omvYKSOCR(Y-Gx@S6atdC`mLtS0$zxaXpkK+Vb1 zpuHdT?Lx?Gf0*G>MC-iUA23NWb_#!*`u%9M8A&m0Yr9K65ihA)DuEz4I{fCm1zIj9 zNRIV?2lkb)rrN~-sgKDeF_dycKmvBPvG{w5%k>)r(wi^+qr+!|XB=K+(DTs&#;nUC zYd43wiXSFZ=`N0?u6BM;+}#ibR4a1f)4Q2Rr?hOijj`4at>3KA`}>sN9_fR8;#Gl+ z_->9sm+EF&$Be{d24VwMWsKnI>rvqK4J#al8cAg5{CS;3OBT zQt1}#{09p#$&93WqT<7xPXFZM^P9vazOFWtdi&fErLgb(a9^^s!C`UjcrpJtsOCa%k zZ11!4oSgdjjix>Rkd`@svJj7Sjjo0)vnVMGIGAZ=7QoSK7H+wZc?b3CsK(wli2HVq z+3k&d;-YP7F66A?ZCxqDG1VdVZj4=*Uf4?!^L78hx4^-Y&$NVCN(UI0D1290(_J95hcRe_dkqs?Bd>l9nY9&G8X993s?n>A|VMjZn&eP6%pS471JK5CGtx-tDDj2 zHTv{mm`B5A6To3tof_xofBS@L-e%=zCQ;1UBT&d>AIjgk^SZZ9zeMt)C^DQijV?>bfo~iQ)k*HZ(BcP>Y$fir} z55eg+DIDlWu)CLOk(ank;{3D(|4jQPDWib^9G4wqGjLqoF-y$0^SSVhyibiukcY`O zKq8*iaX4R{qrQw6bmeMqPXiEqr!8KrB-ct?W>1^qdF;BBdl-La%JX5Wt_Rc24MJ{eV@a=vu7+UG9QAt|L;qEXs)}4Y|V@c-M)WZr-ivv zzY=ptC*gIV$f_tvYW6rQ4>ssQci-xhK>jOGfWq537f;A$w|0q?y}oeV`$#dlbr7LV z3Y;?qV+C-|<2-I>R%fb696f+z`cW+aaSh)$^PY|m$L~tJyN^~X{1x1eaU*Ll!~3XL z4Z+privJwMRl{`W6_d5DOkFS}h2I~0xJQNqWrVgol+&0OKHb=RC+@FqX{c$51oO|k z{d!Nt;r7jmSAO9H1I}cE=N=2_Xk{TYJe7kD!*8$H1vuhCEy>R$f}+2D_VSD^3{2Wx zbX;OUv5xF@Fpl*%?)FZswY#`!hRkP*9kZIH&%9}B=$Pe@{*7#W{u6<*3(}zl;?hFY z@?Jsgs`Ra835HhKr_yHk-y9z6x^rAc=hO%_`w{fB$0c{*wBtzy9UZBx<;ZlO+0vG( z!}ZN^16`qud7;D*$dpkMXspwfaDO4`w-l>_qayPm6u&xSHx$WCWJjiyNuq_D>CL8x z_lSsGr=x(E2L7}lBOW^BlJRy$L(6R` z_3~PyF(=Qk^|Ze~(7&n_NFqIV_Q0l*PDW~BB~^Rc9p3kL5`iKZu+4NoT<^obBuS|k zNV#E$hf`}AyWb3rw{Fx`(dhxHtJ(uNa}o}M-ujToGshz_pVM}V*`2M*p7|!YYNQd+ z2z&I%F1j=(F)JT(58J))(9|jx&(6&OQR32f{95OY`OcZK`n!$uce_LIZ;Q;y_iFb! zf8YxWR>6d&CD|$_@*H6qV-w0>Yk71oIKZ&!(@U^3rv|$APC`WG%*bSy;RHRs%yLiF zn@XE`jmy_T$CA(tMM-~^@m|hp;@OXc)CTsBPTG{D>uT9kBC^xJJ+GSGJ|G8m&`Bu0@2Uxt5PchnVFT9_E`}S34 zjhi#&Hy`tdw{tHXi^Ffl+aVLjSepqSU>?k}&qMPr)ZX(iH)9eB4c^G)N~sU&UTxvK z(_4%ULa`tw8KKgA67)sYqMQ!4d%7{>g$12W*@b+ThRsInd{@yWY~biPGKO+EKy=ei z2FLl+_L+!a9Q@%X>dRMo`kkIn50Z~1OyS$>;C5(rOCy)WdNC_ack8Dfhv%ngm>iuM z%hko7!aY}YLZvWdk6#w{G+ET0qp@BX{y!QE9M z-P}=U>p4yRHPx^k$jfd}C%lq)Oxtx5Y>iIF=FQ zgr)Rm(P=43y;!U^;sC48CB64nHeX8^R%w}FT*-kJfp9H`C`DFz?@5#O$fA|v8V!Z* z`adszP_6zk`2qu`xOivjfjcx>ofqWjSd6|@BZ*H=$}bgnEcs~VL{>bOfm||3Oc<1w zU5O;8R;h2|^KHxGs-)sqStmC+p{wcGq?VJziX7-CqO>2}%=?T!rckRFcg>lTum4=r zG{d%Mnd4qLQU)%IRo&ILcd}>4HukW^N2k`;$<(F>|TqdG!E& zwYPC&kQAQl+cvj;pnS6Iy@q=_u1O;xRk~ln?{v56K}9*pWOQX?RQ^``gSy#$Kl8=O zH32@KS591^A^B8_^0z)^=TkC+*G&#);RNRyO_Mt6h2w@l5O_Po(~dJ)RGCchG2ru=0UMCD8lXSPxHPW!PWDsN-*A*4&#RS zwam(k#a^9N@Yxxmro*?)w{iEK;V86vjxL9H;uNfxG*f@(B{hA0h8Bm4l=8m1UlTfQ zy+h@oHpPhYRcK#Np-?>+Ci4V8^ECZbwtIRX0#(!1N{EMKr9{1b9%V+Mwp5UPd!@m^ z?W6-8W$Ya$0hyMtRB~-y{dWk-^a}t-X-c&T*e9D((0t za+$#!AL^W<%0pr29(pXaL4w36N$(85 z&fxPoy=>U8P-JGRF-5WCj2`es4T!FsY8sq_zW&Z9z`6AXzn}5iH8Cf-e=WZl)>G09JE}_LUX(1<-t3Md)wGiJZxRX%NQt z%y#{vL@&GJd|62gna`y@W`3U{qVu67iQ33T?d=P?QIG93r7|g7*Mya_`-B}u{<#}{ZBJnEAw^0=?vR*H}ir~QxYG^+s`K4U}&T3`Sn0fW4I8coJZXl z65ZN1yS*E{JZf z$#fE!L%7q>3*8GyM)_5(pO`ou5-=L@zsVz_9LTo@Ilb9oaanLw)FC1!*yxA=xX`Z0 zi>87(*UpGX_AkI^MFqo#pB^M^Wl+_yooU$1T@$=ZeYqPH#~nn1N?Cb4)kHTMt1W|$ z6Esb+xLCNjJePvJJX-o+xz`eheWO!wB}kfv5_V^f49b7)1$N|b+&R!aQUH-Mqe%4K zF?&?$FdJuRT-Jg{S*@!rbD))o^4RK9I~ zWG}2D%b7w;nVuwf!a}4uU9@W?D37p)+5<^^CL+GDQC--_eWl?hZOLoYzuYmvz8k`c zqu((1M|^`e$sjtp6GPJ<1FOO7L9I>RI+Els&?p&+sa4=-M{;sYxxe18?k7|~S2R5^ zOB9gv6S+b3^XSl^aqVFnPm81)ESd8WnM)i0F$rpf1!}oX{ektj9M|KA%GzXGi;>)P z%p(8$+Z0C1xW~^`PMf|GW4uceLPFDwdf$7OZm=#$9-=i;$PvB&lHN?s>f)8x5+plM zDl|0Z$=AX@iQjPdY@Z`$$H`CGwhu-7dT8sXwsbq7ZWX17Qgt6@BKIBUyX^(aQ^2M5 zVsC@h@#*Qz{rQJxi$_~r#FOoD&1I)sK2MT{bU<#`nm~tR4e_3?DeCr;qieUxF#Ajc z4it(E)eD5pv9Oy`>ps2g0FR4DvwQe&09l9KC z*r4_IIKi}LhMSu;BAD!i^DG=hI`pjx_=46F*i#EztnHF8YXPHMnz7HI)!<6jy*pF{ zHR`k(*JwlK(wLB`-rc_7ynVR>a7E8r_C{CxiHxy1eE_CchoGm#`|P&!JVd;n{)F#! z!B1t~M*IQa_vzIu*+eZd;fm#s>&ZWt73u}T8b?ci@g}obg7wyiObbE_!&Q2>Ofw}-qpI;ei|Lvvt8J~zPoD)jz-EW zkGeH*-L>-FMf>i+0M(vd#A{f}ATS}QOcJlDuR#5<>9@pknrlygo=!T{{Tp~*0%KXW zA(CWdE8JPY}Z5t(jNL?0VaSNHoU z)&jBEfz-UMg)Bky{FpNA({<5nv1$o24OQP_`T@wFS#HfIfq^XdlFTG0n|Z40P12u) z^?y>6-5K$oW3oD@z8919UXB^?ni}-oo0xyER-$orb*3=!_Xo|eLGxJIX>!-v*jD}? zApq#(4yXt}w15^efvHcR;GLak9zszbO?YD6LjE1#uTjW~ZR{y>mXuW;@s!jgB z{y$+{{}{CY61K`d&N#vgk^UuL__+PQiCF#+b2xfGemuAf0M0jqjtxCN&ByZ9iwMJ| z(bW54vk8gA|a6&z#n=GDSrr<5fd;cZE` z6QKbX05l8K(pY@TWnnSjk_%aSNuwg?&1CGG^!jS(VPJ{RAmhwsXtJbisk>aMYKv7w zIfEJ))tW(%;#G+UZk+F%6}@$sh2FgxQHr4*e_lvw%)1O~qd_L9Sv$EP>0UlA=Q0;7 zB83MuOUA>nfBhJdyNvBt;)O8a1osv~Gwyygq*h^HYoZi)EA_c|KkG$^Gs||jxq&bC z6vKNbljK*yJHDGBpvt%C^+@F2k7Ef=>ZFb7Np;T!fB+ScK_d?TTf7vQqx{_?H*uqL;rFFwcv1>X; zU`*A;T(8$3Ajq6p!$6%TR7qE_dUex6z(p_`Aq% z_V1^ncPLK#Bfubw>rpSiOSfHwxgTPciQDDs6m$6Z`E8uxFdLKL8YA*CrEeMrg z7_xw!w-GNn1LD?%;F9Jc(zSe2>9aWTF(h19S-Qb4`tr_{alctMNt1hvy2`^^Xy!&D zru>;Pg}4%zo)<&#g3xw=L=|3D_cI};6e?R<(k%iEq$UW_z*7$E##~G?{~aQtc9Xh{ z+s%d!j}%Yzdc34j#M(H1_P(WC;0@X{$efEXeRPX9*unh`h5>ac&y&X4>Mld%(u20u z1`Fw!;nr$)q7@r~2T9$#LW{8lN|(FT>O5O?jLgY8r?YRvGhJR?bZHy%-n{CQ1?u?w zxOoSo1f~05`?p#HI*va`=U#q4-uR;L##H5%Y)c1?`X=D395iJ#oDqNf`b9$2Q0l(P zu{c2(BBy1gX^EWit^@%xl$_mp3F>Oa^~@B?ZF-~ys~wU}b2Sm33$I`3jpcP8jRG#~ zY&pI25j)FGIjIB}N}VaP?>FdPT+Fl*kKI7mq;f1NYqV)K&m9ii6m&D3GrPh?yTUz$ zp%OA?oR=(>E3gYqq=oiHjhMmG3a?rm5t&p~RzCN0R`77h4!%w)BWJf(c@I9j+b?=) zQ#_5Op^;G5Z0YAZh-R29#{ISas}VZAXx%Hfp3anan=b3LBW``RgI)_XYpU79rcFZx zvlEv#M7U8ESarCI1xe#k=g5!r0jHq}Wx_J5Vd%fbIZL(>Lyc`WZ0{L4IjnJ2r6_L0 zbSi2WT;_`Zik3s|j>ghjw3*AJw6(&N7M)wZGmwoKGz_Nes=;ho+*V zQQ45ZZ2df#a*bZD_t1AS)U+&t@#{S|_t;|ADD61E;ba?C7@}a`3Q`uTlvTvj2IRcH z!wouwoFEWnWzuH%q&)U){HcvL%mF*_3T_o_#sK>)r#9G?5!KX=Jv@HSM9Nf^+LUNp zF;T9`w43pU*Yhq2(kb0l?j^7bv|8@vii2{#^&!XB2QeV_ua?{b;@$Ml2UEN41*$Iu zZ(_RVl~!@s+`=7Rxi|9h1Zuu6G@>B&y51pu?32o9*IQ)pNZZW6T4;N`mnCKW6V||M z9`LZQN)Z!g>n@*yU-ord8>>MGpCGUax50Y!mhZ@T!$RQ0*N8WOAyr0THyLzCGE3ND zD@Yv_{#mJ+*KA13#$)E>M{ICFz1kPg7;j#Yc8)sPG;rXV3W(2&a?5|lMYN*q8cz~KUtOuPWN(~sA7VtJ+EW!Yz7gqa2qY3qsn&k z`#U(n_#wK&=hC3j&R8Lj^&U%)MC}_sKSn=7W|`bUiP2h@S7)IEk{WURCicClH!t+Z z`+{H+I~)4r!=3!*MOzVJ@kQHlyekriZewihX{#nim$%LjcRBp^oGI=ewH7hCEiU*{ zh`7aqiEdX5cmaE(?RZ;V`J80r{-&dW4$1Mb%sGaKZwOPL_93~aD0ojX3nZuQKNNw) z^pVrhnmf`p!M#1fDqjpi)%NzA%Lmh6p5Lxa%TcNX^!V-)Jwh!7J70V26sl0(hA3~M zHWY8k!BjulhFI)PDtNBU>b$)W87g@UU@Xsi!c|a~x)c+>C=mX2z+vH3433O#4<}7~ z*U7xUP5u1gry=EjzFLZk@r~H~hjCYhq0GxOjYw+*@?nhAorB;Q=noW z>9lH{Y<(){?>*(;b_9e$Xc3bUIO+h;2AITn)-mi$QKIUN9#69N1-qYw7!+Uo1$DW2 z#v{(d_nhmvedi&>=7yR?n42qN@itf8LRSmuoV)i`f(FDYnX=X$vUH_&WZ)R$0N0l= zk@YH@HtatCtWn#~+fd%EJ4d?CPa4PNMC&KqBHl{+m7E)c(0cY(H9>iX{QDgXCghE= zY^}Oj9$(84Pih0-$S{zSFA6QvkrwuE=ojnkzin@T9Fj&BBJUw-Cf_ z{W)foP$jTO?kK8i+mdwx3R#si#KD>w_T=njy5hIZ)NqlTsc#!L&e?p<13=>XWj7xBDitl z{sam}X*Ua)-6eXXMA4$S1*MT0yil(-=I2E**}h8)w)##@^?L`7Um1-IKQa0X1$UGn z7w5|Mi^R34g^6W9NN-r0h~7Z(z4*CD5l#G`VirQ~Qd!ph+ixUkU0+d(SX(i`@>R{$;f`SJl$W zx8#Y2o=+LV0?zVTX~)HeN&Y?Fe^OPm1$`q_=LPuxOpaxF7*l^IE08DfUJW{;6s;q6 z9iVTsikOj~3IEi?5P@vyj_sn)*y6ApYDGXGu zSY-oU0;y(^H>Db~sZmYhjIH3hl^&w0hlA}G_03e*-mivTFIrC4qtZ>|lD?GxqJkb8 z9>`OAsH?|^6)qRMlCq}#pD>&Plq!jaT&|j}r9oQX$NFz)WA;e{hhjIK2Y5(wSWMJ} z#0k-L!EZcey|dme_fl%R+pjZ2`Q-9IjR6mZfx_DL82U%lNlu5Qk2V+@PrJtmRWA-m zLLmY>cTASVi>mGhlQ)D&xkSgYjcN_vW>dyh%iZJvQb95c81!=HAT1xBn2X{28^X5S!*el8H=7ogP#g{zk?CJxxb~IXgQbDD; z83eHY+;Nb(n(bw;-}x+69jR+fAD?K}6mpnI9Z9NyqJ%MMQ_|s=vLrT}x#jnw3^~ zqwcH?&e((urco$q%LJTa0y_edI{ap-6)6!B=jpSKTKfM^LqN%9X1*W?J`Fhq|~IU*6}ztdfGH|9x;KE#Pkm@Y2$y&4uM zH*62~Xr~Ty50W6%MzHkddu*ahM%kTUZ-nelcbfeV3s{=3dLU1W?4%O#Qvk;uf$sd} zAI%13-h|6f(}^wq5*0zo?xZK2fA7~%wa7U zNAMI;Uyc-Qk^0Gewz>kHD0i;>+=4n`J*pnVIDM{^0xZ6Y3TB`V!*(Z}nQ*pE5#CUm zpeo4$$$IYdkA>uhnhrs6IHZ(wSfEiJum7}hfRz~3&|kKOwN|7@6sxtw*#5TKp{EK> zHNf4V8!p@B{(!<-Z1yz4XDCCqdo6myPGvqk{7N{nO+U;Ml&d=&{^Bm`l^|}=#!m@> zXR1Ce7mDR;RRVLQ3I6zyz`}CHbACbW=S+WLW-H@BjHN%uEU?eO{AbK!k4Vpm55&PN zk3n$fg2uKSh`5N@^hz)q$+*gwX)&Xda=*chpkV$B5R0ob+{gGDeZ0V*j#rh-6^DW8 z$FFZ&eeg!!V}b2Gav>5=p&+>Bkx$?Bg9On&n5POsaz_of^qr4la+Q^>gJMyrr!r?& zI^8y@=u8t!z~v4J)F#1`3h-_+d!>z))3ho(QPM)&k1>QJ_@vzY++%98zryq_=Q>7M ze7FNnCBv z#u3dxu4mYZrCPBm9j=+vx0}O40jzcWIgusWeFsjTZu5lYv<>av@s590cUg-Dx1`(R z$?!FdJvjfx`za&0sc{K5=k>RS>lsqtYU~M#Zbc8yqqcr|nTM-hqJTr9Fj}_W`DP^E zhw+X4IQ(KedCS16;WbtA2|wGOM?n0F`Admhcj}|4DJd&RS~Bv_=U2-If>_z+taJ@m>I$qw@z%NXap@|hiw1i$5%ox!9G)vyK z>?(GHR=J%F^V3YSy-dbl(96w2Fc%BU%RlV%{_&=QFZ%#1+jcnAzUOdmW@b>xI{&Dz zB)TzL?KZ6G_I7(pMv|PpC$OB$It~Z0)HYrRvAVoCQBdwNA32DGx8W-3+R_}aD{*R1 z<>XNxRvj9=<1^6U8?f${6=bpg-pSPN>~~ppa`7XzG&@`umz^=yz3mQutr7%c^0ZVX zi;p!%nZK+bes5c(mT(sFiTv4(df~#&LHM$CU3c~4iOdNiN@&$SbfB|+(hNp@fNLgT zopW^bgC;9gX?xA7=ab@@oh$zPC$;MmgD5C5YiW4-bJA1c35x2~l@hpv)a%MO0`pHc zL8G&ZPC&(Bi5w4@&qm=vFit{7u}RtW*4e2%x4x4WtrXpJAb{pBNqMcN74m0tg)>x& z7ccC@pOf|#nrX1(pN{CqmLUGw^q-@lpYsElz>rI(sS!c6iQrVB2;x(#!E=9LC=-&y z5~=9PqSatLm0gc{%IrQeT0=->b~i7%;kU_ho?1A%=}=kiYOHQN7L(N2ikq#4C+)dV z(pJR|BK`lMj|WfP{rxsJTLQFc#5kh^(ZH|XujsRqgRbfH?<(!pK8+*d1d;J$Nw46GRE$&=9 z8Ac-EvuujW0|8uxH1l$1Kqtld-?x|<6m0+3zC`Wa1p?Y%FcJ>#|Hrm$#_u1TC`ux9 z~RUp#?f}Mta9jz*yg)@4s*%g5Jx2im^g5|1s4TdK!$qv5LB|-Ambb zAgiv`U4CS^sz^s&n>SwOehKJBA#;&ZaDQW3|G5LG{TXDR1+HXFxNj`L=&)Ci%~`{v zvkDj_v~J^fir{!uTJ3=Y(Gq-1h5!&Qfeov*?pO8=H6BRfUG9US`ey-hpu*SVMZeCv z&$#>E2T-tf^1veA+XmF20TKzkm<#srUz~DCe%GKa1;P~3%HC_yZ(hI@gF0pP!@_Jy z+&IF~3LP8!SwGbUFv>p5EhoDh9$P(8Cu@Im)$f<}3R;VCb?)Ag7awma42S#@^%C9_ zlTpuITvCTxtHhohWoR8SW(*M+)`c*mNUMgwGz*)M^`p-BPOJ(V9>hI>_Js|~q`brK zdPEL$*abN%y4s{0?!;UyawLMFydmC+wT?{S(*>nZrpAfE4v6BXyUW$P8-xA7rHZ&g zwNCnUP9aY9TIT(LY+;QpFe)gCsgkGX1AQwFQ{#l0Widg1NrFR@QivJU;esevhV^o1`;IA3h{fUpgP{$fo30B*B(M<29-nM%QLQ+LJuvvxPQ2C`y&_zT-!6)A;pgJeB>)#KJK1v zo7%+9vU3rE)8!^ls$;gr%Kjf3I96<6-=F%hYfIa6 zhf->Okt~UYPFp@T%0RPB)LI+UTn-LwgnG=f>THMT&O~i3d_t5H2W=SW3i#ux?apf-ag&ocL&Nw)$F`yBku`=)Xp=ND z8r%TaC@DeQwNii^4>F@^Ne7H1H!pNs(bJo@b7ABPrgu3ogTT?&qn$a$!riZCBV5cT z3I{<|#J)vUi;38EC_8IUvNMvn%0rrvdczWNEN*uDrSjE6McUu!ja4VajT^FqM$c)(r4RPQxN#@qelEV%4#{7EE?r$~YMB!~` z<>Qb^W*=@MDf41Cy$rK{rw+kNefTBQmy6wZl9}%IZF#yvst$oo%HKA{^}8?XtlMLV z8#b|^3DJQ`$*9W5hzq@_@*cry-Lx(;H(Te3wEG#@YZX2!X2qCZ{zrL#l(U@&WIb#R zY032f+2m^!0$f63S2j<#2)O_!0{&psXaMO$^7O7uxHut)D~<$4$3};D^eYNg$I*0eA>@#qo>!T^ZEYLi#lVg7 z_UV@dMXl-FS~B#wNRZxD284~z0}9Q=Cu0saRG7eU64P;KVS#0GY#7QzaQZcK%GeY) z+-7jw^k8tT7*r6U{no@Tgv#n$Xa6UwwSCpIaax(LUflNVABSI`IXF-NDBJ53(#NrM zsXR8*z|rGan{w2f%dkCWflh7V5se@RdYlbY2>Y&rY0Nq$mDOhfv?0|X6(JdLodbu| zjAi+n*urZ;f=B^dn{BA2K_o7MEZ}6F{ynyK=dx<>h7uIdkt-Sdh4IEjeXxXrM|kH3 z&(C!;Y~k$lP2eykTiWWmwkWp@t)@`BpX;GudtUp_4Vqv%%7daAM|e9wxy z#w?-oZ2(_WIPOrGx{%I$dkgktodl>Ij2KM1N)t+2s_mTgq+KT%8kt~zU*E2*IL5>L zb#u)j26ZgorwliD64z=TLfOGdT6gj~zLAm7{^zfRNd?oNzXsC~gPSrvF3)`z%IbW= zvq{xY4E2F)Xf54TeB($pu(N1WZ016j{anNeFYk`%t~&y+bl8|T$>d3q!^(*E>tO^j z#{g@YMnC_L--0Jjw0HI!3}?8tx)arS?-NuVsZ>cPpTy|P49f3BeB`(ZCAC@xYpYzo zE_$jza&);U>s|#_*c}uUYYe}H+WZD3;Z~Mi?Imn<@ZW4O5<|SJBb~+=MZDP=4IyI{(to=AiFjJsefV(VabH0$TH&;3Yy zcRp%6B&~G@$@w6a1@uIBIZ-s?h64bk#vxv|6ThkLjxBI1y}2dnM~Se{OmE<8GF5Bt z9J+WiYZk^cvIEXJA0l>qtiL!T=MksqV`U+Y|FAV@qRVP}*}AtvqW7YGeX11IkTy!h zXrSTb`ykWlAIAM*EA^IWBa-~e00vx=QapfcjpOK;b2XrKB1>LxXXZKLc{zJGH|h?9UP$f67`BU3g2quC!PbR*)n)0at=^!qAXD+F7Q`r-EYGes4+RRdt;QD z>@WD)UTt5v`;q*9NJF8_d&;vfls<8$GVin4TGeXo=ozxOIJ?? zy`PbCzBay`jqRExR3E;-yt^HP*NOz(qhySQkd>q%(GL;`sEy=<5|IIxEZXZ z4yu+z0WpepwXdL?nar4j)h4x8@vI)3wuMU-U_vjy}sVd8H=Dr8Y=>&xUcDsPBSiFi+b~Jd~K0ZKfnfbUqvY%0I=X3vdB^ z3nwKitTdF;F58+84EFP1zoyDqH$6cUcue!wKPe&IPvCNTII-K*qB_q*o)o}SkxWH- z16$zZkc37|rpuj`(bd;vIb~=i{a*L+YDPlc>225TL!}S=p7x9IxWQ*a-P&1X@VeZi z-D0yeh5s+|!zaCVTR2@#Uj1r5*Xk8<(fCuMa~HJA7&v_XF?lbUnOm)^&pI=nvnQPzNIvpqLVU5FyRbT-{y zSFrUv*?AId+ZG|wuA^2EyQK`yCwuZ0?@)S#R^=@<7bWv^W<={>c4rUWdcg%XXo`q?HTGG zm|u{kijR0pOSrCgZr;dHJ-ir)hc4p&1b}xKpSRCDF!>BlQyI3{}mIW11Q6%G@VQDs^qzA9xyIl#V`b10fxxHFvjD!>7HGxD(< zl;u!&y->B3wC+sNoiIH0JE#N8HDGrbhf|tfBTHQot3lzuBDVk#u1#PQGKEDBl@=!^ zOaA(D>DEQq#A;o2MD;XB`N&a_S5hFyt)b+pVjg>&n4#~h=19M*B4l~lr*+9dkesBc zmELUxXJLk5{c&nVaki{%CHYb0XI~nPr1u74tg#V+S>o<7^U&97{wbp82ZO1vtcqD^ zRH0T~mT{BrxTW$?syGR%f)VZY5krMy3{lzSPr~fZBB+c7_$y4aEKtb~ z-X)_W@?+bq$d*{2Bi0O*F;xVJ-|yd=G>P6DcF&0&&owUDaR-D}d4H`jWs;etJ|J!r zZtfr67)H8Lt4)RW+Hn(HB2zVR(k1hcLqmUk>)x=`3EJA6lIDoE!>l_tT%dciNTco? zlUoZRSc->FQa#;$!}g9?9UcTGc-p;+d;^C8hpz{@^f1;o@ffUn4A_5Z$ub@J%QpRH z3RJS5SeA!26wBkKK3Mhb2}e2;OPQGqm<49qN7y%sq40DMx#{nvK|U?M<;B=E35I9unc_g0%9};H`e|fdGuji5`bbCgtZ!626n}2YjZLfl9C0*~f z=@&Tc&4s*)%Tg$B(=^=_`@lYVaZYOK=E%`;ZV_$-)f?4veRItQI@lfI`8>WXg?2bl zly-020^IOglm4%87s>NKaju0mjcep!UIUtMQ%sg6B zypdZ5SDu|#bvjVfs;%U)(@O)%zf2^^e+5*XUB+e3#^1k!dACQ%_RUy@uC>HCb{B9D z2>~lE9Sy5(8HVr64FAo)lk2q7FvT`QLb}&5=w`qF#ea`7ox>;u`AzNuWV2`EC%Tie zCL`9!1Wje$UjOwmG>g^PR<@8pQizqw_%bugv5Mc^{YgMwuyzLrKk0{-aDnkp6M=g0 zfPeuf0Wlf4%$Tj}^9w-26-_XFx7AtjoH8gNT zOpM}y6H^0Q4P$Exw^%%h2`PpiPy(MF`B*(fGh)4{O>`huYk}gk*`VCVe zk453mN&s(4SGK5*5I)-PPXRE{vo*>YM3_1@Qv%8ypS0zs|EhQ+bz&{|Q@i zEPVH})o<}P1>pEg+%y+y68t}L67Yj}Rda9&djva*Oj)$@TrEhb&P|^cm}nVqjQ$FQ z{wb{B0SXim7TCCVOWI2BoZ7(aA2}!9(R{svMt$XxqIhb?1>B1obAY;{yM0Jet+A zeV!$HPpDaE4^W6~-a2H}myiK=z-8+BK#xWR8}h#Bu2R=WT#C$oGPbdRhc{y_uEaCL zId(Z>tLMAkf)WH$^v(|DbM)Nnaae3z4C*L+q!ji#+?9x_`T(qfFM|cC`KALi^nk}i z>@9gH1mWuv)HB6tJ_W@7LoMgx{B~U8ip+~2-IecR&mEuY-vk+8s`y_-SXQNT@@SKD>?|>k(87@BWB3KAD1(muj02(q6Y)?V@bkQT5D#=~EF= zE~J=x6+Zd8Vs=;4$z$ymr*PgVX_&yP9bl?qV~GPBD|zby8>#NWVo09Rq8y9Md;-G*c0kU zBAb372@6~NnpaCYnEO2%mKdvXa%#RE^*`o^#cKfmZtUXp9=F8W* z(m_bJ^WTDSNOGHsSW2SKGk&}>VpJ7rmFAu9ys;dFH69``v zl2IceXh%M-*72w)>{is00B*eM2`<56c?ve(QGFw)zIy21RWz;{=ud24LCMt>yhTo( ztsvY$DsafZbIV`;enukQSp7+0jL(fh?lT#vW+QW4YxA&lH`&w{U54vH z4nqKo#`iHSKC*$E`O0ogCiB-%40+lGElWkWq8%qa&FSfA!O1s_>dC85!S!wF`t-P! z-lkp!*zR}#Wo$*A{AFyNjGBnA-M*|LBlvN|4Qz_ljfTK~x)_7w3Nh^B_goHc4ipL! z2g1ixJy^zFHdiYK6ALi+-mUBnG8^J}ZLHi4%f7Y4zvTeWMGpDJKd{FVu@b8T;^p*kF8CP4K(_I@|8m+jn+8k3fwIswyJD)9!AA>wkYdA{ zyPR+=h7p2VrArXma#%YvQKc@z(&!;kAgFs5B-0EyLICV6$IvXy-OMJ>H`U*#im7(_ z!PY%m?eUeoj(#?dn-||raqY0nRy<5g!?1ruiKS8T&PozQnG8=XA*VD#=`_{_HD%Sf zJ!0erxW^+(#o^Hs)L|UBvdgkkp2e=e1Z$_-IX4DU8lnfKWU-m3i~2S)?cuOzMd4&d zvGc>1tas$alwnO3{d)xJ#Y!~s>nJQEL@A;auK%^T!UN->HYH=;BUh0X&PZr{J`HP3 z-z_~|O_x7NIDu^gmH&@wc-$?D9`kPHTX84yUBdTH50F6ZWffYnK+cX!g&Y_2y#y1p zdNOvtDEYCge{83?8W`oc?z*8N7WY|>T-tB=L_oBsJO_Q5Gh00$R)Br$>=35H40`mfAB zoJDwqn-QX(lu~LY@-D`wP(>xT2jGx-Tc zEdf<%4v#cr{NvgpY$;!kemO)+S&j-aiGPS==H_8G$vXM^@H-$N1jQ%BiW*KOBTt2^ z0pa(HD@?f1XzywkvOlbc-(rgcmEDTC_^F`Z@~-3m3Ov^rPi8!xgyyTv@GPSq~Tq zGXG@=`~Yqm%S9Ugbe<*q%F#gdLg9jD@5kiqAu(V)$8C`m5ti5k_T^;L;T@bZ(`95C zv3Ifwr{WT!%~sVRfHUK|_y$1fdi8Fi*Ml<;+N@x$lnM#S0GG_&h4c1~&~V+6w1MC) zNigZi@rzm7E9gdNDt-_1>7p)LX=w{R?ySq@I$L4990`~w5fFl8YG~cTucqqzJJ@bP zT7AeCleWFQrJBM=n?Pq{UhvwewVwLvyxV`N7YTB5smFe{_-d2=v_bmY9xH? z;B-hpUI58{x|i9o+e9k_u`S?BqeF+;Z^p|0YLu+Tb*Dzz+p*aGDlzVFrgf!~L~#WB zIjTNzc^~}rrOqMcp?!BN^%{SPeq<)II*|Hkh6O?-4M_l{<}@od7hS7M>0?IiS@(`4 zJnogii3fA@HZ z1godTfKE5lqHlqH)eeKe^!1|dWQb+t14?|}AwG$<$Dee4N2SnElE<&KP1xgr@G2zj z1geK$^!>|pWA&#sHO>&{*&Lj~E47C!>C$GfF|vY+mzz(8DvOOli}XOiQ7N1TqU;RN z;ZaxhZt}E^dDLDnK+D-_W!Bg;8QKWD7msOzcjP){|(!*=$=1~ga`c4%FyGRXQYFg7vJSE@?W^Q*J$)V6$1a3 z%wLchuGMe!&Gto;h&=mfDfN0=4Nxp%!2J#BflCUSO%{5gh60G?%Vf+DWJgf{8e9Rd z8RY+382&F|2ZZ)Fbp)uO()Ai%lw(yDI0UA^&A@R-X^u9V)BH0EObYV%ZxXTDr^SVf z(JHWWDx+2LJF2e+LepjfH0lR`UdgimGvFHm5fO`4SkxnM>y=r95MrieZzn4WlarlHhDCyf>}5R zhW$%r6B;-BPdr95JtZ7x(OpSOZo-_}uoP#@cAQc?rjJT-cERAE|8odN4?I=HjEcPl zqX}46F;$;+G@0^v5z0SSwO?C&EHtVH^8X5Areda4G!~o9(YMfi4v`gB!e0X`jSu)| z08yp%KeJ!9(kSEhL5YcZ0M|K{z-7nteraSOdTP2BL!A3uEt{($Dj4D4Py{xLq4UFIcW#i1-|J25SJ7H(5o?^dkzs2Xxiw=2Lc!NM-MO2*e&ss@+y-v4Y4j+b?ZRB-ctU@ z6YRpAIY-OQujR?LkfvfmM#LiRFqt@2uX``+SilO-#f8C#YRgDf*>Gupy!JTmzUJD_ zPRLdhxHD@0$Mk|Ro<3zhgot1Cs)Pa)J!s@-@202)_B14YeplCgVfAO6-)_4->V^6o`kNN z$2Nq(D!%0pW@=V+sm!jbiQ^}f5*^MWJ`|<62#22v3u^wUK@ndrUy+s7l%}c)#8*=6 zTV1F+HC)VvwV&jPa7diF+vrP~qt$Nn{16SNKniHnyUl)GufzoDpJn_Fdbw|A{a2OZ z9HwM)gv5~u_{8VO0zUtqbDH+8gt?Q+8jFInGh2$;DuEF{Mw=k}(7A&18jO{jB@zCPeTAeObQJ)zfVbuHM#D#k zb2UNtaqt?x4cCQyNjYhITgX1ZmHUz2vd|)%Ne))d^>^398Il|kt*mZQ9PNm~wX!KG zOXFzG>l=8%CPmvEZp3?rglVfII_=5Y!aUU7}EK%=Z zWpDbrr}VSW0-&5a3Ts#g63!zArVSk{7h-q&=dr>>0AB%cdSu*9Tr>vgc zYL)wY^UfdRfQa5XweVfJ*e*n5B{gWV)qA+Qx&kDTLs#1^vcK$vBu!=Upv^0>34Yw6 zDL_X@V`3C?(L3v2EA;BR(h;%*FjxS@Z2oN1wZ=PWF981dtKgrp9AdjD?0#?&(rcM| z3_x=@G!J8@%ZsLFR z5evH7lB^^Hka9N#3iXEw^**41|4)J9Qv(nvNxuGCP{X}T@c7JPS6^jSW0D_58-1w@ zAm7SU_eH7^XaK$w7gtAn2S@Xt!wFL-(B)ADabJBAf{aX<$@nS6DJ__^bVQiV++w+e=2Qc!~r&UqfbD-UGxNz<`&o z!n4GE?NVvhf00B!8D^^mgl7CU0)2h}oerqAVvf7G(MusZPG4C?hj!vR3gAlP0cw%| zq2A(Gq<-$FbDL3-&u6ypgTZMYd^t|c&fA0_h5uA>wL@m1vUc;V`wJTKmCd3CHo)C6 z|LOm7W;bRb=LdA0`m-}r>3m;L|D?NZ1NrIvRl)>VcP=fBY`Y%lxVdDWLhI22mVp}G z8&X!#aQ!m-U#>(_Px{P1zIt)w3-xk<1V}A-j-m;IAzt}k@dIm8J`UTrAzc!}E(FJ? z$0rOR&;Y@|WIDbk2vm06F%Vv(8B2344;7#ixuO8+5@H$wzZwuNy#Rz~7i!)4FPLa? zA3z@>4_ODGq_>#jegBoMu!NewX6Er_uk%bN7M`BP!MSq&`P>Vmwe7#kGXXEBDtnyh zDCdjUt;`wOK;{lG9^v@k1N?Lg_dE<#j`uyIzztRrP*=y4*F4dH677qBdvoO$09cgL zm}M=a@@AP9$ZzWsIaXGlePs(M>v;V4h^y4gv_iq2nZPZZCdaP51M&=)ykoK48iJ2@d~A(%laK0;!9JxC_|1>{!4-c1De8xn*Z9C2G5)Py&%V}Q(E{@t-MEhIp-S#i?Kl`4eo ze1?By6IluaWgz}-BqR2NI8Ac$1aitnn)~P<8*WkEwkxN%fMASO(4WB$C?l@HHT&r6 znx>Bm;1Kw|E$p~G@=D}rNcbGFLCOCAU2_d)hiCkbXef5NC|y|s(U+$SC|m-dkJ2wsWa1}eeh31wex_ zP?_HiE=^j!6>>v)UoA&8@6YM$1A?@*QNA%~;-gbUSJM_ONh0%m?^*@5VEBP$pD_^Z zQ&dDLMsPmIORc5DiYw;sOV#lcR0ji&_&( zdEO>~0Av1YZgwpEKd{YKrbSHEX@Ui1XJ=2jm9n&hG z#S{`P4|)7+g{~X&WJ_l`%}6YjX2D9oEp-%Z7gCGI#V7Fy)|)&vk?4N!-}B z&7dD&o8vUPZvn6BXVb&o7^CHuoab&4JA08B#J5;fP~=cXg>XwR0GTqfvRp02tlizl zp9A0?%3n$s+kW|~{**~Lu1*e?)z!lTXMyYq?6Q4Doy~y11j-{~;bK*2IqDi%Y2c|# zp&zRbPafnuONj(k8T>&xTP`lEC5j0Oy>3MJ{vK)19%;omb2&TdW^ddCC|d3SJu46N z+dv2C>jeJz{9)?(2E*k-+C}7S=)nRv3EtW9xv5REw{Vq9ne^d+5g9pt=eARVYK!32 z?s~CpDc5Qm{U62iG@B7(^01xZh1-SOB3B#z;7fjKGV{q}jqvdruTMPGP zm}g+kAz=2NyMwo`HuKD2dKr*4#6@8rDweX~bQE-x+-b0n1hgRcP9?7!>XtvWD7R!q zJ8Dq{nbtDq2=cT78f^<{@9%)hinFU2sH8R?#_r@MEE2I|55@( zuKzP{L>|76s%0QMBIggd0-W@M(MJ~lrxKgW1Mv|=HY z4Ad5+omRA?`%xM|T=7dUaMdYgF9NT~Z#TsLbY?0X3K0?qS!w)-jOyBgS3$C!jtbH-VrF$O+qh$ToSHS8YeJzh@*xsTNnIdFM7zDR_wDO|(JEjLak~P8V;L7Kb}hH=`h(zM z_ST^wQx8v++gf24eO{d*=5S8PVQfR)Yr)q)%2_|V^R_w$N^oiYeKY&)H}b`Nn^W3J zgoTheSMGHUJ82!B8DqL-8i2G~0^Ilx)rD9@*Ir$xPaPy^3`YSL_W_)~HL(f^%8P8w z{tN{W+oALnXR4vViU_cA3K#mvip5V0iwc{(H_`hICXCQSoZlY67|QT;UJ<=;cGg?t zpAWPVx01*YazFo246gsh0_K{acfKuV05%H=AP#a_4HLsmJiv4Fbh``Z!P52M<1NWd zeW74`S(1S-*pVBG8A)QGYH2Ea;DN)B9Z&6O74H(~CyUqmfq*x@pJa#VSrBNtY-cA) ztkk$Ny;$%}z*mWTeP;4`<5Y~2$b+jjU})ptxz+P1Dc!#*)di=u>L3dT=`_gY;d*{S z4W~~kx4kwl|BNT}FXsu>pP!)k!$tm5;)h>)^IyVK?sFyY;obeQfP4LyH~u|u>ihmB zFsTQ!n5hi#|HVEzC$t^ql0Ijc&2K`WzGhDJ%=2Ex?fY7m;2QF15zW^ZT0QI-$knph zpKfnKtj&trSYiX&w2gV2DW>2s_ba*J)Q%FTP)h8Llno?oMps(lpRF;_j zVnf!Jja(vZd18TAsDg^fBi)UUk7XEeSS(TR8P<_ceniHBGq#2l>^Sj9zj-O9Qnt0x zbVY7PjNl40VrO&Mm{FB)B}mD-bQH7Yi_M8P!!`G8}3iYcfzWGb>B);6TgMIvvLvzFplx zz*@AL)>l?;);?vDxZoY2l{alu{e`ZU(hSNt0bUD%g}CU$?5Pr5(vL-BUD{~ZEpck% zbzdi09-T*4>vq2Bp*|B9J!7*MTjU-dtW=@IDfzUO+j);Q0Q40ahf?-;k${ElmiQb=Ht|L%D} zb7e+54c#gqTcj8E_K=}puW;U#Bv?$dITdi18$6 zMxWkJBnj8Au%Mu~KHii6`WNCc9#~_rhrIx%A-37s({l{qGsMREe`l6HC2=9bC*1m0 zJz&&V!h*%o)7*x}!&41(HEa2>tkZouCQ%V-b9dU{bQ#mBm*b3*%#14$TG|$N57Ycm z=P9~2yOk90O)FH!9bP+WD=1u-wr$}oa7Y!u0lpn5obg7q={8qaY4+YPU)&?YQmU*~ zVNRTJTlsFw_ee46oAI@;j{4~$}vAbqtBpI-^HN}#eS~eR)Y!@GESWk{XAoK=|CYB{X)&WYfbnx60nUup%y~r6 z*YjVgO+f#+tY}f^^ElaOIr5};=hXq1K^mR@7oqBlv*95Xfa)^qEa_O8aYT8Ws-?^T zydHhr9Cc1g=eB^fb5O%qXJSey9%Ya-{?_C_G9bTt?J01pu0WFGe!4|cRG_%tMkPA0 zMTk1qV_(?jM6#>e_ihnvhejOW(a;6D61WX_@Bc!n4B2WmZ(VO-&AVQU^PQZA_0SAF z+25u0y53YA8L^h{mIRtc7X}1GTODQa(G7Rw^0@9jvS zR5$?L#;CoQoz2OXd~H-)pyfN0+vTv9SrYNWe!C}l2IjbT0>;@d}zQF%pmY&dLyPDn~NQwObv&UzGX*~ zPwFNV(kO%$L3a^wulregw9U7bt_9rI@}g0tBiM8R{Hp)&L{a$;4Gywz z1PNE!wO2Gen?JJ^HirI0&2iO9W6h^D-pn;73!K=EE8D6~Kn~IZae41MC zvOWF_OPD}>RYwh5+uXfR?fkAp#{3^nK8eY_nC&wZ_-PEk5aa=7rPr-b^3MGi?kB?v z#TN4BUJrUu7Hyvr7skVz&Mg_8UsAjzJd~QXm-#)CG)ZHnJ5ZiXUxfu=G!Y`M(6AFG(;7=f{f#nl})b{FN1l1 zGFHuyP@Zl;{hgVD7qM7|;hAJ|^_YTyou^&qsm&zbLk=}x#lZz`KyDbOdq+oN(F)se2932fM72RT9`-+M{pe* zKx$SsEP=C04NUq5N6w)s{M9R4qhysj+6dS|?(uhq@M{ zI9p)tA1&Ux-{R}KWNlP6H=14ov!o>TcN*b(To?9EBahuVYmWAY+6KhZY~~t8z{uK$ zt^Vnr#bjU)Ca`0Qe4)24D&4p8H#Py3z@kFSj9OYcXi41HS1dh4T&z5uJKRN5=%3oU zy8ECP%K<%>J-O+zt*}aXjC?n8!^gLlrz-c6pwLl>_V=soxbkQZ`UZL=!7YCz8|5{qjcq0Yw6!jY#^ zdXoPQ&Bei$nGnLq>||e-FQZ@H!hL;wN`X_hNZUC36hQz0n~N-SI;?xyt@hplwBy=X zV18m$cMz{RSEpslTtPs3Z648*1o;i<+&H&R%oK(PSwTpqGW>`moo&iXDKT{K6Iq4Y zEwVOiIPRioljhf-FJwU&g<=GS1xIsAeWlYuV)SB$haj;<5UK)zVUhih6wdtd6%ab1 z&iKDDruc&-Bo!26=gI$DPhH09Gq|lNPD@?C;iv7JMni+1NQ=Ymk<(z?q_N>WO$~+c z;?T;b_isxuQ2cJgoNujCoF}odB&(>RqA05($rLIKJsI<=DQP^(H=J&2 zptI#FE#DN^>KY_6jWC{36&M-%3jspR4NjW}wA70Wbr8o*13a2$&kMyu zbCVp1;wH0e)PCbuB-QnNGwt>M+IUiXn`X7ZX9N&l96tF^otp&of0rQcUrDSE zzo^yI)wsIbn|HUSF-u}Pio8&Wir&-C(T)Dpwe8HbjhKzrY+iaabRho=yxXcJ7fki( z)7>;NCczQuJsXa)Umr7{9F6#e$@8>wSsSIkx0y+EbDc~4=`2JyTmFQdb7-cgcJ-~Q z^Y7uF>&Zq#3Yn;Ps(IE?&pJ1!EWff*glcqNZF%h67O^eSUeuY>f6Q%avmCAbNM=VW z1WRJWV#`&Hpj2^Vxp2MPkb@7=(`QXtqU}$S9S$sXW4KcO5uAdgLTlvsmsn5X+M5kzm3t!I-yabktly z`R#o_49b~yyv?OsnMz%&DXjtueC)FOH&Mf7R&9^YCe-7&Ybe!zDp-?NaodjWl83Bp zku@-=yi3@rAx@auM%C&W6pdQjcnCTKh(9^oh1`i#=vT_v@obr2%o~pvYjnD<_vKTD zyiMV(MZV;XR?id?D^(3vcl?o-2C!CWFK<=iHlM7& zVmp3vUppi~u~_PAfVHU9%jHm*?Z~6ar)G#APrAUGJ-9+Q{Hl{5Aq10xuZl5hPo;8K zzG42vgY-jCYbX}u6SVCUoso=9eJSD_W`h6PWh{zijJIInuX7tAH98Cg^wzVXIw&$;H`wK?LAF@9b9~{P}{1(uB zX?v5)|4MVZ`4M$?hKbZ&YrZ6fewsDw;?+&8V|!FLl1hs);z>f^Rf}h9M}4VcF_o(A zj3=F1->LiS{YL5D=)O*eX(H3-i|u&N4#mur2D18l&p;lRwY6~YQdzB$)>xn4h? zCTL*3Hf6!iUK6Xsv4LNlD}mcY?%mhVgng-MIn`aXZ8_P~$z!q&nI z&t+aOqg`NCZ4lGWYNbV#B0ek9$pCJLTuw;>(pBOs4ywjjMd}Mud@W>@jeAHwe;llr-1E)=QrEs&t?7p}79Aq&Jt$zobVzZwT?mkMd z=lowRKrmHRKW|c{jbjTWvOKK%q}^p{<7~+ATuo)_xjIyG8IYL2JErGjh{(`nFOG*G zs13%$p+<_S5uIVYyN(it-^UyRfx4c-BZntLmWs5FzX|gzq^J=G9c`uLIUb~t0zeP8 zr!(;*&wW4U+V`Ep5GDQAua>2R1C?0k=xbXcWa3P?tpFh1@Wp+?Sp^o?H)-1ZzkD{I zr;8O#kgvR?Du1Uf&*;5#=r0{(8@af8>fVSEIkkCwd1u}i$uPA+TQ4Rh$NN7PI7b`o~(FF3bjPO+_wk6>Ch;OrL3$g=_mKjiM4tae=JnmWBqFN zW1pO#Vs@G%rL7g_4=l_VezASK2`>{>G`6T1zc9AvjE*vGo?zoU34E!+O|-VS=*~@o ziQUJ?mJ}7+p9pF;gGfyvJ4oGJ1YDK$+4iZcl8jz+;I+b+iu(Rhf~ohIm|LmXMow)z z3$hFg%_}C1E!zvHCP=IW^>-iP+xcaZbb&5fq8 z|6h&79**J(%Md={Hvcrnrh(R5h`Y5=4pn9gX?{~XXHf1`G4U~4&!C{;uupn*ELf7m z(B<%2jYEUd_hxzQ7FbjhWX4jrd8#HmhXOr)ni zP-Hj7^_$Y%m_)COpuUCO_vCNSN;|FDRy!ffQOpqZ+wOW(e>&2doRP0Z{iXaEv zw`8(zawY+IDaGdoZaT$r3$y-~ZG`dnknx+gR^;B?cjx)OmwkqwBF!Q=OMA1ruOB4B2&5tD>OOCq}Qj*6HyL&XHx}(`i@^FvZLm=d??hoSA1ChR50sw2`F}Ux@hE ziBto1WCH3~Xm2d_OBo&57|rX>(!OA6Y`RhG+5RbJkn^0otn>bcz=B>sTej2#CpZ$(I{SZ2TQDd4jYLvhhwev~)w|4WaCgm- z`i8jzD3B22;S=evT#qflybrDist?-=Q~&W^9b8aLR`R90vzcA)>)P$i4<@KMHtvd& ziU&s4hZXH+D5OsF2y$z|Ih#MSFKMu!uwhng^jhg6FEuT0LhdwAPh<{T(lSo9Sca`0 z1qgbY+K&UB%BS_fgqD_y1J5Cbi05dHY?ZjgGq$+V%Zczir-mJ0bV3g3rkzSN&wXmS z*e=!7E)|*fAvMAPJUU3+QVG7JSs2R*6cYNa+eKrg;{!G|8H62fa)KeJVneJf8V0o| z(7QXky9FxYF<~eZQ+_HJ$xzkV*2?F}^^>qu;T0izk)({3eZbidILk}<_q<=8Y(0fV z;pk_N7*YE0A7_=mDQ0bB*Ewr$$!{nxcA5G{F|5;?cI; z$6ewj_;L|#KR{o`Xez2Q*L?dH$Ny5db&K6tYqP4olRJ5D$po^@RePfqG%S}Wl_3>1 zkmI&#{IvIhhpH3>1nbuFh1&XLfxvNXgZ)L?8xhD0c2v5&HH-ksut~i3`##177GN~= zMXo7zt;BQI*W1ns#{SaB4&(1(?n3v?qwTN{n29f&Hmw*jvtUo0X?nycrJZBZJKpBwm}*HRv=sH=SBK%>niPj zMWy$-Bbr(olqScNpp5rVKv-*dxzeszsI7<{woDKAD#*j6VSXV|d;U0ssl_x--d}PN zZ87P#mwV=$Wk(X%hE%d&n&nFoLEItyNnl_2p?5-n?Q~n4RQ^oUsoHY*!?-+)R0fXUdpC^aA*(}y%yyMy zZIGcs%NzYYoaEgsc>4Y$t`aUv{dP~tU}#fEOu~Uhq^*d~8PV6aOywV9UoW!_ieE7< z`gN=yhbsvA|5R3f7+zr&4s(2$%jl5e`@7=qpeB1Ir=!qDyJlnkaEd8{_rz$gZ_zv9 z_1kRC$~BOlDc^J&YyKCQRIX}q9JLR{%rqy$oqj&__C}|@JVIbn_08&;%`%C^Ou)O) zhj5#^nMrrC@_5JmTX9HL)1HqKivup0=EAzk=PUGe7*WnhDSs-sLu_}Z`CH@jkKT(z zkioeJOv+Pp@#SL1UOq{#hmj;Hsb&tUk0==o5`)1?DU#^C>d4OEAw>qX>&PXQkSd+2 zO=Wgx{L0p6Ck31scCi~JXmWN;2AFMgKt z;h31i;pOS_Prxk-E(mHS)+iS*J+}3Vz zN^Yg3`4x=q2m+h>y4>%9eLrYDA2N-N5f1Q^E$5uny4)Q+-CJ9yaZNiwXF)fQubvAK zSGuPy33+`zVI!;fXtiS~CWcgS@_DDiZb0!t1`R@ zXupIno?YKOI;F?T|f+d?DdD*Xq z!XE@F6KMkDTLvY?hFIA^*}`F!08KUq=Jn_uinzE*o}wAmk@((fS#qHu&3N#pY{gsW#k!G zdx~6~=~PVHf8cr`wm0v?viyD^74>PVy2@qX00k2scK{MMNjT{jXQB?m)S8QhY7JAs zbRTDY9G3P(#;kl@;|??PINoEI3+7PK%}O?uRh4MY$s;-?G-E+{GR< zYx2IEGmDR_#B{t)`mG}6IWp$j9T4d!I;kZs^eMT1sY52-d5b~cDci=M*y|{qZ6FU4 zcRb&DZ`vWAS={4;C%uBrWdyW70B*Iso^1tHSMdjHd6w;t3%&HMKuT%e?Vtx;KS<_M zq2?@+PAsADN$pUw%|`C!AEfElH^QIek74W*9P*d=vb@aO)a2_!gtJGtPR_L?woCtKkaoq5KHc1VW6|!07I?+l~9$EL>VNzt`Bh zKQ6n?S4*IUGpAx_0J4`JMmRzi51Wo%W#&~AvJpI2+fMAKQNq`$@73%|dKG-A0qOJU zyW`_~(JGoNWMe&U<7^DRG+Y^(S%KZ+w3j%nK`2u{;J+6WH1`w`GHBzkS7m;-wrS3o z$WLTe88%BSosi`))lA}Ml19|;!!_ZVERaTO^(45{>_Z&WUcQLWHsyEST$Y-L=Ry`N zh$JFJwd~8<(dUe+*Sw>&#~KKBwh|dLLfHeXx36xuuFu*xa>gvTg20Ju2A$wXym-Ez|(xp=M5{W*l*{DL;y?2mr7p5GnNXKsEtFB-jn;O0d5t4^qHELN#`ZqLmI1HHBJ4M>k0+C zfo5*Q0-~T#*Sm@NMoJIEeKS(Q94)c3jD1(t2KneIQv9YBM6AwB3pdWBLaD=uxfTiq z6Zzk`MO%LFA!kPcZ?86fNmuUs=yP1>+t#;xM^hi}HAE7kBIoidjGfoadDHjFpV(gF z*|E{he2{1`ZLFIqK5$-K7(T=9lPKwA^j3MRg&Ary8wQ(4TxXAYlR%XG% zzcYgq1}l*$Lf^;e{rB_iN_R@*dm1HKQZq5s%{OnL^Gb~WJo9X4uO7l?$s7Enxw&IL zFm~7~nt8yf_bBvkY(CvoM7AZBo!j|wFr|L3?*{)7eM+O3h$f%Uvoq2%-EHwSAKzK8 zODXTF=N=(-4N*gqbu(4D$Bc1bOm;B!5p!3}kg1eSxWo z=A;)-kij*@qDU3{pM;SS)0Pj!V$7B1BV0x-wZG1Mnvv1@9+fMCjmaf<6BDv=^ zEhh+kahbY)n%;KQ>Rs(tlbW@%j}k+ox_oOd1zPH26sWeST3SLVnqtwdFm<{da*qs7 z0EpT3eqX~g>yziN^4zA2PISK+NoTz-E-83`^3Bgt*MN_f6Nu0A@S2Lp?{D#C#h43; zb`-D4?Ai-$Vx}AmO&d0=)|516l~U&wc_$q+#M&R-PizrUTl@zU-Ter(Mt6u3c|vJd zD0Fo>g4)7Z9bw-Tvq6q`E7hjbcF*i1o~O^>kD!nAIi8uCX$-Pi-#Iv?`Z|Moe~_^h zoZ>jJvpky~Ha;=9ny#Tcz08}yqv?i?^HnP}W>szDZrw~4ihg$WWx?=Uu2x!z`*F&d zS9FqHP!Nu`bE!wYSi((|?0ULu=$M>`jdg~-(MLJSxFb?8dz1GMUpZ&0r4Z+?m_HJk z&p-N+>xt=tGV#32vA8~jLo{toZ%( zGnYQw%>K-s~sQU`{8J`z_o}=$lg)LPNN9Dh9s1T~3NW`IkHY{1#0&G2J8p`z!a{9B0Q~2^U&l|_)mIJAxmr@fxoAgo57U_28 zeD+KmFirQWK^jSY)3+|-UtJa|0OY#E3 zLEUVspR$$C1b!c1+>#&Kd>_pQ*R}h5X6e6lmkGfSw!?3k<3p< zv1x-*)hX(Nf^IeI({yMRe68CVzzb%#!K=R#RfknxXr2x&SPQnM3+yl0(a0y7iLz!E zJk$SMyQM^#CWRUWpUSJCRuG(U*_Ah=)S()8 zldmSKTqYao_W1?}iE@M^jEd_#uf)&r#hC-?(k!%SeGPr2)`ROP*-puK7GF<#i|(9z9-o@b+&L5M9ys@JX`fxmMB3QS*w%kp zqhyGU7~_jaz;NL2Htjpn8`#SYS`QUzIK_(fJbX%q+b_eE!M4-ik6;E`!fs!7axFGQ z+!>XvRw(w0#^D)NPSJU9z}v4>Y0&~{odp5}E7iN1braF2Z;As^fx z^kc)r2-IraEdFghY?em3+r&hQ6N`mzkudxpk!1~&p%>a&l>2mTiSte(%Dd!4np#Z6 zDTyCicIlgLl?;`H!R*CADj8-f&iFGux)wx9?cnDC_oOcGx0_DTl{VgOdm0x@8AzYA zTuw{f(Iy8FOO5XL-kdcavC3Qx>AH*qVp$@LL9_Ju(uAy3U>)%S5Y~IvID7#D9zPL$ zsW$A~vjzjEPga_muu{nJMAP!I3^Rmb{Mj=?O06~*|Jrx3oiiV=2pbQ$`Ou57x6jye z&vw?2EswfdZuYo+FRdcGx+t-bx|j=i?vhTL2`yhbqyI$DME^wi;#3bv{s9&PPD$2WV{z{^eE^_)A2cMHA1s01bTHL;OyC)A;2VMyEJueosLaW6~ zn_DTrt*}!7okj<74cFrH`H_3au~VGj-Kp07zE9qWmmW%-MdNg-f5+F4&56K;i?xD6 zQo5*es^bUH^>5&PPFFy*R$2W|5rNnk55;AfVqgV)YAS&~Jra)~ zYGCGPTjyP8(roRZTz2LjjQb3tyl2zeIXd|mxOE!1^-WaB+h=uvcKRz&-+G1pJ&cDT zqeVNi&|$H?BqNX@@6uzZpc}xp%D!BbH1XUge2OI*xH~h$!4v7U|ziqigtm{Y;ZF$R7sv?VjT7hUDp5;X*p$V8GrLJ_2*T4pXX2fP~93HaBEyD+q}!Mue$7C>^FAuuOeZsIlZ)aP+o~m-pcN z5b06GwVt!H)Bkf}QWuERZC%rHV&cmin=(E8-|8dw5nsHN)t8T?gn{%5^-kUajGGwK z>!@N4{1^->nV@G<*YuH0`Xo$`KR;>V$yepl>WTaI^Lx{Ki~W-;tPeus6v0b3uHBUT6-BZ5Ma!{|5FY!Zf_C^o z?TL`otr9v#Fg*N6alQ!ZT-~_YN@piJzn+e&{49B;cO-xFLO`JJVLN%N#CCOpo8Eh; z4wj{F;AxJ_MVY`RPT=$XSL~Qsf8RvsDXAY|mh#3-B)O*lo?_t=^)>%$$jn%in*Pqc z{&nR{0d2A)@4qMg?tL1w#Si1)J|Xv8chpH#xiD(${Pz^-9?36#%4{yXgxaCSefJ5r zK*em20HGKEo>kt5_QJ-B)ZH5m9MPCBMhfDn;~uye{XOaTKpJ6)kd=8FwI0QBHmrT}tJE>ZE$A!Ub1qMh*1KCJhg9t{hM4n2qGj*xbZ zJjYHI5`&7!%2jXGZ00_EjQjQW^ZmFMvuC{cfqG99de+^+f{*^*1fo=Ccz`Eu%5vOm z%p==OjZlTDa(q0a;Pe4S5ClGpO8jt2&J$s~VGu*IX^Ci`ZY z>feGKF+Y?2dnAjA`Ckjg-UHn2-wz8Oup;*Nqx=9^=Kgz>!2m>3{~n3Uh=cxphF`QF z=-;0MV#L7sw+%r5e}R8n>HkX`E-wOYeRo%8-}~n;!TvKP!UBo + Document upload and processing workflow + Workflow from PDF upload through validation, SHA-256 deduplication, blob storage, processor selection, artifact generation, synchronization, database updates, and document view creation. + + + + + + + + + + + + Document Upload and Processing Workflow + Files are content-addressed by SHA-256; parser outputs are persisted to blob storage and cached under /tmp for the current container. + + + UPLOAD AND DEDUPLICATION + + PDF upload + POST /api/upload + multipart file + + Validate file + PDF extension and magic + 20 MB body limit + + Compute hash + SHA-256 digest + stable file id + + Azure Blob + global/{hash}/original.pdf + global/{hash}/metadata.json + dedupe if already present + + Document row + best-effort DB register + when user is known + + + + + + + PROCESSING REQUEST + + Process endpoint + POST /api/documents/process + file/{file_hash} + + Strict cache check + processed/{processor}/document.md + hit returns cached view + + Cache hit path + read metadata and markdown + rebuild document_view + + Cache miss path + download original to /tmp + prepare output directory + + Processor selection + auto prefers Azure DI when available + otherwise falls back to Docling + + + + + + + PARSER EXECUTION AND ARTIFACT TREE + + Azure Document Intelligence + markdown, raw analysis + figures and tables + + Docling + remote client or local worker + VRAM guard on local path + + Local output in /tmp + document.md + raw_analysis.json + metadata.json + figures/* and tables/* + + Persist processed tree + sync to blob prefix + global/{hash}/processed/{processor} + warm /tmp cache on reads + cross-replica persistence + + + + + + + BACKEND OUTPUT CONTRACT + + Document DB metadata update + + canonical document_view payload + + restore-ready uploadedFiles state + + + diff --git a/docs/backend/images/evaluation-workflow.png b/docs/backend/images/evaluation-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..be280c889227f06052b604e9dd3a8462f6b66213 GIT binary patch literal 90755 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%0DKgQ``%=alcrV|_+)A-4VF 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%0DC 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ྍ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 literal 0 HcmV?d00001 diff --git a/docs/backend/images/evaluation-workflow.svg b/docs/backend/images/evaluation-workflow.svg new file mode 100644 index 0000000..69af071 --- /dev/null +++ b/docs/backend/images/evaluation-workflow.svg @@ -0,0 +1,113 @@ + + Evaluation workflow + LLM-as-a-judge evaluation workflow showing synchronous evaluation, batch jobs, provider adapters, metrics, concurrency controls, persistence, cancellation, and result storage. + + + + + + + + + + + + Evaluation Workflow + Evaluation can run synchronously or as a persisted background job; both paths converge on the same judge-model service and metrics. + + + ENTRYPOINTS + + Synchronous endpoints + evaluate, batch, custom + + Background jobs + POST /api/evaluations/jobs + + Polling and cancel + GET job, POST cancel + + Frontend status + progress, errors, scores + + + SCORING SERVICE + + EvaluationService + create_evaluation_model() + evaluate_extraction() + + Metric factories + correctness, completeness + relevance, safety, custom + + Combined scoring + one JSON judge prompt + fallback to per-metric GEval + + Judge adapters + Azure, Vertex/Gemini + Anthropic Vertex + + + + + + + BACKGROUND JOB ORCHESTRATION + + EvalJob + tasks x providers flattened + + Concurrency gates + global 30 plus per-job slots + + Durable status + eval_jobs DB record + + Cancellation + cancel live tasks or mark DB + + + + + + + + RESULTS, COST, AND SESSION PERSISTENCE + + EvaluationResultStorage + JSON files for batch outputs + + SessionService + persist scores to evaluation_results + + CostTracker + judge-call cost and latency + + API response/status + aggregate score and metric reasons + + + + + diff --git a/docs/backend/images/extraction-grounding-workflow.png b/docs/backend/images/extraction-grounding-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..7d450f06981dc3e0b24f9de8d03a1ff7b0984469 GIT binary patch literal 85518 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 OsjbqB+T#mXwg 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