Spaces:
Sleeping
Sleeping
Deploy current Customer AI runtime 96b5a02fe7bbc525787d1a4359b7358d63d4d89a
Browse files- .dockerignore +12 -0
- Dockerfile +9 -16
- README.md +18 -7
- app.py +33 -0
- bootstrap.py +0 -22
- config/model.yaml +8 -0
- config/quality.yaml +17 -0
- config/runtime.yaml +18 -0
- pyproject.toml +16 -0
- requirements.txt +5 -5
- runtime.bundle.enc +0 -1
- runtime/__init__.py +8 -0
- runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- runtime/__pycache__/answer_quality.cpython-313.pyc +0 -0
- runtime/__pycache__/bootstrap.cpython-313.pyc +0 -0
- runtime/__pycache__/integration.cpython-313.pyc +0 -0
- runtime/__pycache__/japanese_skills.cpython-313.pyc +0 -0
- runtime/__pycache__/kagrra_bridge.cpython-313.pyc +0 -0
- runtime/__pycache__/knowledge.cpython-313.pyc +0 -0
- runtime/__pycache__/model.cpython-313.pyc +0 -0
- runtime/__pycache__/observability.cpython-313.pyc +0 -0
- runtime/__pycache__/quality.cpython-313.pyc +0 -0
- runtime/__pycache__/roles.cpython-313.pyc +0 -0
- runtime/__pycache__/schemas.cpython-313.pyc +0 -0
- runtime/__pycache__/security.cpython-313.pyc +0 -0
- runtime/__pycache__/service.cpython-313.pyc +0 -0
- runtime/__pycache__/state.cpython-313.pyc +0 -0
- runtime/__pycache__/v8_bridge.cpython-313.pyc +0 -0
- runtime/answer_quality.py +80 -0
- runtime/bootstrap.py +27 -0
- runtime/integration.py +36 -0
- runtime/japanese_skills.py +100 -0
- runtime/kagrra_bridge.py +24 -0
- runtime/knowledge.py +42 -0
- runtime/model.py +64 -0
- runtime/observability.py +37 -0
- runtime/quality.py +36 -0
- runtime/roles.py +25 -0
- runtime/schemas.py +105 -0
- runtime/security.py +26 -0
- runtime/service.py +56 -0
- runtime/state.py +40 -0
- runtime/v8_bridge.py +25 -0
.dockerignore
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.github
|
| 3 |
+
deployment-evidence
|
| 4 |
+
test-results
|
| 5 |
+
.pytest_cache
|
| 6 |
+
.ruff_cache
|
| 7 |
+
**/__pycache__
|
| 8 |
+
*.py[cod]
|
| 9 |
+
.env
|
| 10 |
+
.env.*
|
| 11 |
+
!.env.example
|
| 12 |
+
node_modules
|
Dockerfile
CHANGED
|
@@ -1,26 +1,19 @@
|
|
| 1 |
-
FROM
|
| 2 |
|
| 3 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
PYTHONUNBUFFERED=1 \
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
RUN apt-get update \
|
| 8 |
-
&& apt-get install -y --no-install-recommends python3 python3-venv ca-certificates \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
WORKDIR /app
|
| 12 |
-
RUN python3 -m venv /opt/venv
|
| 13 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 14 |
|
| 15 |
COPY requirements.txt /app/requirements.txt
|
| 16 |
-
RUN pip install --no-cache-dir -
|
| 17 |
-
|
| 18 |
-
COPY bootstrap.py runtime.bundle.enc /app/
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
|
| 24 |
-
USER astera
|
| 25 |
EXPOSE 7860
|
| 26 |
-
|
|
|
|
|
|
| 1 |
+
FROM python:3.13-slim
|
| 2 |
|
| 3 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
PYTHONUNBUFFERED=1 \
|
| 5 |
+
PORT=7860
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
WORKDIR /app
|
|
|
|
|
|
|
| 8 |
|
| 9 |
COPY requirements.txt /app/requirements.txt
|
| 10 |
+
RUN python -m pip install --no-cache-dir --upgrade pip \
|
| 11 |
+
&& python -m pip install --no-cache-dir -r /app/requirements.txt
|
|
|
|
| 12 |
|
| 13 |
+
COPY app.py /app/app.py
|
| 14 |
+
COPY runtime /app/runtime
|
| 15 |
+
COPY config /app/config
|
| 16 |
|
|
|
|
| 17 |
EXPOSE 7860
|
| 18 |
+
|
| 19 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,13 +1,24 @@
|
|
| 1 |
---
|
| 2 |
-
title: Astera Customer AI
|
| 3 |
-
emoji: ✨
|
| 4 |
-
colorFrom: gray
|
| 5 |
-
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
-
pinned: false
|
| 9 |
---
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Astera Customer AI
|
|
|
|
|
|
|
|
|
|
| 3 |
sdk: docker
|
| 4 |
app_port: 7860
|
|
|
|
| 5 |
---
|
| 6 |
+
# Astera Customer AI
|
| 7 |
|
| 8 |
+
Implementation repository for the current Customer AI runtime defined in the Astera Notion canon.
|
| 9 |
|
| 10 |
+
## Architecture
|
| 11 |
+
`Japanese Short-QA -> KAGRRA Bridge -> Astera v8 Bridge -> Shared Grounding -> 1 Work / 3 resident roles -> Integration -> Targeted Repair -> FinalAnswerComposer -> Satisfaction/Completion Gates`
|
| 12 |
+
|
| 13 |
+
Notion is the design/specification canon. GitHub records implementation, tests, commits and CI evidence. Astera v8, KAGRRA and AMATERAS Ω runtime bodies are not duplicated here; adapters/bridges connect those responsibilities.
|
| 14 |
+
|
| 15 |
+
## Runtime status
|
| 16 |
+
The implementation is fail-closed until concrete v8/KAGRRA adapters, canonical/live providers and a Master-decided trained model/revision are supplied. `config/model.yaml` intentionally keeps undecided model values unset.
|
| 17 |
+
|
| 18 |
+
## Verification
|
| 19 |
+
`python -m compileall -q app.py runtime training evaluation tests`
|
| 20 |
+
`python -m pytest -q`
|
| 21 |
+
`ruff check --select F app.py runtime training evaluation tests`
|
| 22 |
+
|
| 23 |
+
## Release gate
|
| 24 |
+
Release evidence requires at least 200 unseen scenarios, 11 scenario classes, 98% User Need Resolution, 98% Answer Satisfaction, 99% Critical resolution, 100% false-premise correction, zero unsupported/legacy/secret/unexecuted-completion violations, and a 95% Wilson lower confidence bound of at least 98%.
|
app.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any
|
| 4 |
+
|
| 5 |
+
from fastapi import FastAPI, HTTPException
|
| 6 |
+
from pydantic import BaseModel, Field
|
| 7 |
+
|
| 8 |
+
from runtime.service import CustomerAIWork
|
| 9 |
+
|
| 10 |
+
app = FastAPI(title="Astera Customer AI", version="0.0.0")
|
| 11 |
+
WORK: CustomerAIWork | None = None
|
| 12 |
+
|
| 13 |
+
class MessageRequest(BaseModel):
|
| 14 |
+
session_id: str = Field(min_length=1, max_length=160)
|
| 15 |
+
message: str = Field(min_length=1, max_length=200000)
|
| 16 |
+
|
| 17 |
+
def set_work(work: CustomerAIWork | None) -> None:
|
| 18 |
+
global WORK
|
| 19 |
+
WORK = work
|
| 20 |
+
|
| 21 |
+
@app.get("/health")
|
| 22 |
+
async def health() -> dict[str, Any]:
|
| 23 |
+
return {"status": "ok", "zero_gpu": False}
|
| 24 |
+
|
| 25 |
+
@app.get("/ready")
|
| 26 |
+
async def ready() -> dict[str, Any]:
|
| 27 |
+
return {"status": "ready" if WORK is not None else "not_ready", "three_role_resident": WORK is not None, "zero_gpu": False}
|
| 28 |
+
|
| 29 |
+
@app.post("/v1/customer-ai/messages")
|
| 30 |
+
async def customer_ai_message(req: MessageRequest) -> dict[str, Any]:
|
| 31 |
+
if WORK is None:
|
| 32 |
+
raise HTTPException(status_code=503, detail="customer_ai_not_ready")
|
| 33 |
+
return (await WORK.run(req.session_id, req.message)).model_dump(mode="json")
|
bootstrap.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
import io, os, sys, tarfile, tempfile
|
| 3 |
-
from pathlib import Path
|
| 4 |
-
from cryptography.fernet import Fernet
|
| 5 |
-
import uvicorn
|
| 6 |
-
|
| 7 |
-
bundle = Path(__file__).with_name("runtime.bundle.enc").read_bytes()
|
| 8 |
-
key = os.environ["CUSTOMER_AI_BUNDLE_KEY"].encode()
|
| 9 |
-
plain = Fernet(key).decrypt(bundle)
|
| 10 |
-
root = Path(tempfile.mkdtemp(prefix="astera-customer-ai-"))
|
| 11 |
-
with tarfile.open(fileobj=io.BytesIO(plain), mode="r:gz") as archive:
|
| 12 |
-
for member in archive.getmembers():
|
| 13 |
-
target = (root / member.name).resolve()
|
| 14 |
-
if root.resolve() not in target.parents and target != root.resolve():
|
| 15 |
-
raise RuntimeError("unsafe_bundle_path")
|
| 16 |
-
archive.extractall(root)
|
| 17 |
-
sys.path.insert(0, str(root))
|
| 18 |
-
os.chdir(root)
|
| 19 |
-
from public_app import app
|
| 20 |
-
|
| 21 |
-
if __name__ == "__main__":
|
| 22 |
-
uvicorn.run(app, host="0.0.0.0", port=7860, log_level="info")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
config/model.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model:
|
| 2 |
+
base_model_id: null
|
| 3 |
+
base_revision: null
|
| 4 |
+
backend_mode: benchmark_required
|
| 5 |
+
role_adapter_mode: benchmark_required
|
| 6 |
+
require_trained_domain_model: true
|
| 7 |
+
allow_untrained_production_model: false
|
| 8 |
+
zero_gpu: false
|
config/quality.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
quality:
|
| 2 |
+
user_need_resolution_min: 0.98
|
| 3 |
+
answer_satisfaction_min: 0.98
|
| 4 |
+
satisfaction_confidence_lower_bound_min: 0.98
|
| 5 |
+
evaluation_min_unseen_scenarios: 200
|
| 6 |
+
evaluation_min_scenario_classes: 11
|
| 7 |
+
evaluation_min_each_class: 10
|
| 8 |
+
evaluation_min_critical: 30
|
| 9 |
+
evaluation_min_multiturn: 20
|
| 10 |
+
evaluation_min_false_premise: 20
|
| 11 |
+
production_model_self_judge_allowed: false
|
| 12 |
+
critical_resolution_min: 0.99
|
| 13 |
+
false_premise_correction_min: 1.0
|
| 14 |
+
unsupported_hallucination_max: 0
|
| 15 |
+
legacy_mixing_max: 0
|
| 16 |
+
secret_leak_max: 0
|
| 17 |
+
unexecuted_completion_claim_max: 0
|
config/runtime.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
runtime:
|
| 2 |
+
zero_gpu: false
|
| 3 |
+
resident_roles:
|
| 4 |
+
- constructive
|
| 5 |
+
- adversarial
|
| 6 |
+
- evidence_bound
|
| 7 |
+
role_concurrency: 3
|
| 8 |
+
max_targeted_retry: benchmark_required
|
| 9 |
+
max_dialogue_rounds: benchmark_required
|
| 10 |
+
request_timeout_ms: benchmark_required
|
| 11 |
+
max_in_flight: benchmark_required
|
| 12 |
+
queue_limit: benchmark_required
|
| 13 |
+
queue_timeout_ms: benchmark_required
|
| 14 |
+
raw_context_to_roles: false
|
| 15 |
+
shared_grounding: true
|
| 16 |
+
majority_vote: false
|
| 17 |
+
japanese_short_qa_skills: true
|
| 18 |
+
japanese_fuzzy_threshold: benchmark_required
|
pyproject.toml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "astera-customer-ai"
|
| 3 |
+
version = "0.0.0"
|
| 4 |
+
requires-python = ">=3.11"
|
| 5 |
+
dependencies = ["fastapi>=0.128,<1", "uvicorn>=0.48,<1", "pydantic>=2.12,<3", "httpx>=0.28,<1", "rapidfuzz==3.14.3", "pyyaml>=6,<7"]
|
| 6 |
+
|
| 7 |
+
[project.optional-dependencies]
|
| 8 |
+
training = ["datasets", "transformers", "peft", "trl"]
|
| 9 |
+
test = ["pytest==9.0.2", "pytest-asyncio>=1,<2", "ruff>=0.12,<1"]
|
| 10 |
+
|
| 11 |
+
[tool.pytest.ini_options]
|
| 12 |
+
asyncio_mode = "auto"
|
| 13 |
+
testpaths = ["tests"]
|
| 14 |
+
|
| 15 |
+
[tool.ruff]
|
| 16 |
+
line-length = 120
|
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
fastapi=
|
| 2 |
-
|
| 3 |
-
|
| 4 |
httpx>=0.28,<1
|
| 5 |
-
|
| 6 |
-
|
|
|
|
| 1 |
+
fastapi>=0.128,<1
|
| 2 |
+
uvicorn>=0.48,<1
|
| 3 |
+
pydantic>=2.12,<3
|
| 4 |
httpx>=0.28,<1
|
| 5 |
+
rapidfuzz==3.14.3
|
| 6 |
+
pyyaml>=6,<7
|
runtime.bundle.enc
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
gAAAAABqej4dVxWhBqxn1CX55xiLiPisJ-EjoLwIJ38O6tel_cSobXI9O4B62cfbdteH7-C7OLFh_mj-yjSxw2OA63tSWFqmWjxejfsWunwsRIXJY5MEzIxfeWENy8ezDsi4zvi0GjP7ScXYHpOeW6aN3zKJ01lmTRk-Y4T9A8wINyxo8zirrmlLpRU8WnffBpTGqwMfxk_0bxqk0FitqYm7k1eYAJfaLNMJ_XflejA191v3Mf_mLNrATIHplPhhv9tnpEcRAEtA5d3qwWWphW8FsamuvVQw7ljIVXFsjbefOqRoHMsVtaXec5mDVuVlyexwR1jcc5GJDwZJ4Of17CcQjCbPfRPf68Hyi3Uo19irRpTiQrYtlBYF4AAB5JZAFzIV6naBpb2o0Kv69-gO4VlQls2ql2YZ-TtALSssH3g6NEGltyCL3LD-HbOimWHaZWCXZm0LR7XCnrEyPpV9KRePyJoYLz9UH-SlV2_TFyOHEytaIsVwy0c-wBqwSvjfA7BtYP8YTezrdFeflaNxRKWHMPmPFa9hxnx4kJmS3EZeMJpxo98YAq4yPwNNoi2Z7b6anptxSquW-P1XZT6frvQIUDOBQOJRyEB_r6utPaw5HNnUWw4A2wbcBnsy0HJHn1FCjoZV9lozyiDorDRyalx7079qo3SxBh_G0BUFoeKYwytCJPaEIRp1JQnFmNHJBEtLDDgmpwP-ia2FJOCGFiiCb9EpHk_aL1qbgJSz1nW2gBMG3IUBVB4Eot8c-hdvDK1TaGUwtplzR2gWqmBjDANLziAa5txE-0FADK5AauSpyjhEqh0vtCj0bfo7Ljo3fQzVpRiLORNLY19Dmi2O3zZ4RasDMfu6QwplIPELaTFy0CaA24MGgZzFgMPBLIHKjFwybGA8j4DKokXWI-kv6vDJkALv7rzT0LGUFJaV9ZjarDEm_5jZlgYrhDNBDoZ_0BBfZm61zDPwUpXgr7_9lEkVJ87IWvxSpWYGcFDtCaCI9ijmsEJUYOIlGypZlEhkCJEf--PzpsexTGHHKXjJT24DDT8-7w1fKzXRIY1XF0cxDhGwIw53HkIrSZpgUN2NsuC3w9qMKoFrzzgwcxXRpjQmZJwYeUCNKG4xlEQy8rcV-lTRqUlb9i0KsECoAgaoE_IJNZ3srAzvD7eLIyglLlXxA_a5Fq7_WE9tYtGBlMr2whCacIunE4Lf8WhKiCvLWdFiev9s8AmyHFzB2F0gVk-70uk17cL-wfgAWLxtRx9DrK5tMXf8-sF65nRHPYzNmGvoHlnuDBo7CIzH9iJMz6qGP9WOHr4uOycySgHdROmXo77ZQzhnaBKc8p5r1fTvBOFiQI-InKNHJv4bikphwc8hIC-9auZ9ULYn_OfrVJbuNUA0bizTTDz2bhcWx28YmwngKEhzIsyeGVmcryMl12Ln48Df1xwzb2A5C_UBzZZfS4GN5s_2gUdxU0Be2gXRhOFXjLkr93kngizUTCc5gFQOyCb11MQ1IqC9lSaCi3MhoZJiMlKXRkJpbUVmiRHY3w-wTsnLePrTz3SKUKye1q_iR3dZoAa0LFotw4rCI8hsNTrrE14L0uhdzLZkf_G1kQE2g_INfV2qI4Ca6w5BPidZ_CbpK82t102I4Bv8UE8y2UzraxtdrxtoG2v8nYEQpJK1RVmDdpOsG5g2SjZxGXSRJfW7y8qJxIL9jBZUnwGKAe6G7V7vCJS1joAYTWVnjObrBysPtGkJO6y1sWizSBK_U1558SJ8i6aV6kiePVe0J2by2kAizJLQ4XMfMP7gks91qKHGwZEISJeJZAZdNhTucicASZlGIivs8ciF1e9BWts7QU39bnrC4hNnrqRBEof5t5ME8AfKgSic8RZ9AE4nT9EbLt6WFsjaC6A-otn_617ZjkrgJ62Fkdydfu-jcHpP4qs-E9dQWDsmNH3pV4iE_vywJxgGr_Lcec-HwNSX3o7pJQBfUCtjVFLFV3jNVK008nfTJgIfgUwUpdZkCw7vxc7YqLaALvCZDn7hqwev4z7jsYcAv6QpKW-jwmXUMgUSG5amkyty-c7EMtwZEyzDsVxYBPJF8gYKu153tWsnGAcMFZZpmJ3_9ZmQIE_HEDGc-25w62CUJ0uogHErE7H0ikCff0BHOccrTnsnJRlX8rb1PyPzCkoRgnJa5MF-kkgE3ho4JEQMgcvIbghDloZAG9RUQXJqz0VN-cRzwdLzg6FVDLyQY17yEmJkKDWG0L9ZlTkEgRkuqKc92Tr6PtCCZmcYzBRk4OkVlBizUvZtn-5mLlZemnmePuPFvbUIdDmBpykjTlPhkTRH744HlsWMikxhoU57APXls6REEFaFXNQmvVNVOnPAicbqAYP1oz8ubhD01jAbve0QxeYXzPCWRpaov0Jyq2RJefOImZw8pxjUP3Y5rEyvdtLMCTUMQi0LA9JhvjdhB9OJ5yTxVd2xRPMXTGzHNdfOtHmYsvh-vJgGwuZfoWkJ5KLESDb4FHRGODdrm67J9BUR4SgFRqcUZMHeJBAmSpxS_qP4vkBYjiJuePJ0DDxVhcmerzyvQzf7kVrLKVn23H71ia_gPlXLZYNWbmpag9NwcNHDKM5wLSVRtKo81wQW8Jnt_sQp5MV5WAYuJ2zTVdRZ6dJB0YDuMuvj5uOo2CybWPluGcTSAO8VIUDn8ix39gqN9YDeRBc2Iyq6pkROcgSonpWlAwpVQH8GGGPPGqSKKYtQWfOKTHCwoOI8FhUdhuHobXHCJmUjlnxRgtFKVnKrdOaGOhfnh7SoMLgs2FLRcA07xBYZVH8HinONUeMc3nVRdc28E3vC5iJ4p9CIVv7HKCFvcKGXwjdWmJs_92QYemaQVeUzTNDQ3_2uWEWqsR37ywZU55ukGkjqcnpnDq1tDhrb1_auCfKyG_3vZJvrCgVz6F1BukubIYdxyEzlrCBDrx-QXzZuixI3m1WELZva73bQ8NOUyPbqkHvPxypK_GSL3ttffVuhOAtlySEwIzh01jlz6nh6wQH2tz1f2RHJYCyDI6SKEHvziZcipno7ZU9w21e9LYb0Z1wCPnDMx-sVHgABBqwMXgXDVYUbRWaYpRen0mdMQtRd_c9AkOPZIuuyME6tZ4ytZ34EBTg7HWdkESNiMEdzJw5BGFcn_PaFB8RhYEg50Hn9PAGDF0iTUHrKYTvr6RclBYFmP4Z7OmJLC0JplyhT344RMQjDByc7diu4-wheFlNetZTu3z-3sMX8CnDMtgUzB3MskSJnEle9zfcKfQsJRopnweWatOCNNUrL4j4vzRQG6uXTiES4BaZU1j9nlmhuyCUo4UZ_iPX3RppC_GgTY1tw67HWux3-5d-F9wOWIDB7glmvoygu5u3OqJRXnJV25p2CV6knBB0HxkHvz5ZjCOjVKzt9qH__ARhDRNJIyjeXaRfaAC7MKrsiydl58lQsl3X5HYvs7ky8lp3peyHivvKCaqIaf2yqU8FZ89UygOYZ4WD6Cksm3aSjaKGI6yE7MdOEGpE8dfKpzaHPJRelHj9GJW_59ecGLaAj_EVNBK8ugno9PJxkAe00fqwLEhvkaJedZSTKS1RKJFlRSf-UrZkni3_h4jMPE8nl8qx25On9k7JAC_ERnxDYZ8etjKKOHT109thGb7_94JLoBVIDypwKudvG2ss6n7BC8bLnCoAFyXeogQvNaCY0zsZQZbVpuXy_SBgUNsYgOdbYq2C_yDD-VhuWeILZbh5vPGLaCr-YPPnoI6c_EixL2pAmd5pHEBwwbYwSE6dHYXpgiETE7beITS91sYVFtwVjZd2_x4NdpFhXgMW6sPtRaXvNCX3r6weQWgbVEFVKYQIcE3wQSRP-oNdJAPYdZ6-sHimkEP8eft8lzB0mCWSUc33tT0EwiY39jDuDPdb_t9iCdaHvDK3bCWz3i_9OoKNMteKwQ9lm2K0W8tvB-bjKTxgy33wzWlY6ID7kUw-s1Z-yct8UL6uFQZP2y4Dys5hanrCH6VyEqEnY2nx1Ovq_4NvwDXZSr6BA1PqNPhrxRIBeRk-guhu_6qwvyORb5CX79X0faHDLphEhIehw1en5L3rIzgx0L7Vkm_Vscgn3wQvdgpJGKFc9W5D_Fgi1hf82KOUgra0UucKBYqvaD29fSqsGvOgMR_mlGEpBsyqyKaaBYS8BbUUZHyG7aRXJWBPXJjugOgZ37hNNgI7ViXYs7B2RhnOEAJtL73SO4WKxGcsZEJq0U2aYghcTQqy255Skeb_UF1aVlQVZG0jlmnKc_EdwHGOcgU9_dSR8u-6X1AxHHJKFoBYoLovJT9qOkF8LiIdsZ-7RUL_TNHs8OQvGJjQKV71FbuY06SKkEJZwoEvMPkDKjoxTa7t24NZpuNHIeF4elVJDXWd3bVpi2DzTC91kbSRgrbG0hu7mr5dRtYWpE5dV4RQ2xPidLaVdzkPRaUohoFHKkUw7Ibup4dEbZu1RR-X8AlJHEOFz9JIGFdPyxDr7ldaP7JxkQlX-Ddn2lQ_yDL0GM_XwU8RhMSNKDgz28vlI_HaUpwCAaGnBg702btlsqikGm20UTYvxmjQ9aaROxVxHETRDDjPU5qfh_RhEUgo5sgnVNUkoPQQEUY27qdhuPLjWkNZ0CRAGpnzq-U_73Em6RzX6fXEf5oIx5l8hPX5wxfv823DzDTkbm1quc8dHoclolLNm1NYxCI0wTf0K8_OrIOnuqj-n7j0TrBhpHEx0pJEF9LMqZgB9ABWhx1nNl67_aYdiBDyIk-uryoCrhXAd6CYBgzXJEj2aPkm6ILaMeEpgK8An1Td94aPqohMwdkv6WwTGdQv2Mo52Poa3R-w_TvEGfUhs-O4cOyStcMSPHnw0HJbDvw0bV6N0-JIKTMaB0YBj6xjA-v0zc1kS0ewjqzV1mYbW48N3VjiEdwvHingua-v_bt2mymrdkMD0lBX_senEwdugjiePjgDF5knbNzFRxYoSOzU6MC6TGDnSFRt6ckk1AU2NvsHwVRo4KT2X2PxJoK6-AACmtlL2dvHHehuVgPRFKCS0D7GJuMwMti342r0ru1R1qnT5QSxOdUmthkFO7CrLQYBy2pHgWpEKfXvWB3lxO33UoLxi8kZMGjyxJ4dvfm1P46dieWBr90k8X-AE2RthClzaNkQIJ0L_sV16YHxcYlv5ZhLYUskVhMBQKMKgGpBZB2z56TqJNZVQuePTTgKp2iXzNsOhak9e0DDoysOKXrRm0sYb39tZcecbvRmqPewYpt2QUiIDqG47uScZwlmcf7UUHF8oDhR-fmQ-XQMNR2awWdIRufSVnVIQr2BnCjArga8ePZ31GCZ3wSS4pMB1y13zFe0RxwI2zvTP1ZU9je7SlZGGjE82sPFTNH5Yv8YWzNIV7gSKXS3JBsmMdEsLOoI5d0jU8BFJXWhro_rtYZC4qRNuyNn8I9hNJL6E3xg6q3sxATA0qzYTG-dXCUwQ_GJHvq3bvg9Ud7fo6XAWu1lASwmFu93tCsWdEmb8JGzOy0sAFyk4nDmcrq_rqRhyNm2k_20Mkn3ISGjnZKSNNRNORg8XinaBRGSSFYBSwGMsnNOTv41ybaWcTKqZNJkNGG5gR0Acreb15Rnzv97fYDC2Th3NPd_P799xQAx1mSo0YGAtq5A8OKu7BzxbWNTicWwxpGKLovtLCGyOFoQ4YjJollRUOilUMGhHe3Xe31u0yAs07m_sU4cQl2QSmYyDMiWJaKtRpG8maz9tl0bADsHaCMHA4XCmfqRuQbfm8AhlB0Q83A6aF_WVQ4ztRL2VwhBamnBpsUToysg_Q2OEr_BV3ZB_eWvTg5L6QQ8oQwpYHXVabzg5OivyrwPIFX2xrhPkBFdOzl0aQeVUkGI1Gteg4__h6lLBlt-Bw6KWSaW1nUPkQZ1CR0GrSaKCYlZA4X57O6JOeGiagZlIuDrmG_TYl2Wbbgxb_KwM_IT1EgPmI-4y8SwWq45euQVxx7rMVwA8qe7B6OsOi_dxrsZuoeeKDvsUajCr5HHXqpjpk2Rn0cmVit2UPK3600745j6nSwY2LZCZobZpwbI1Y3Pp_NmkrYj8lY2COXWjaB_ZqBIWo9GqvKqXxEM_dyZkFIB5q9rqlXfaRfTMGZTE91_vvbTqpPZEKbb8l50tBr6HRSIqmSi-FACtXjPanw9sJRhLUwQ_d_VBldaFtQDE-uxdg9TOM_B8UJLD-7u6-5Vldxbx3_jYsCSV62mxOWj374ujoW8lZDvp70LFfoBh-tzUylZhoT8HnDF-bRegRETavYhEYW3v-8PdgBASfJLh1yE5welIAm3jjoX1xWzVk1vyprjLkvMhHlL7R06ERfBVt2WO6LAi0GMDU01y86MWG8fwNU2_YcfNv_l-_DmXbe69ba5XGeWa0N6GEsydEQsAZ7XTZ1naEx8aR3rnvYQKpftn19Qj_1SxfaNNqWXoKIdNx2O1PsB5QFFHP6eoge2gtLB-MoIuZbYSS4TJQWcWlqzSRc_S-gKBZxC1fGNQ3HN87UuUCEC7AYPXMRnkVilyFfL9SNQZxMaVG90JB-LZS1bNEL4D7EcXHe2IrsQKwBus0S4qEX7fDkxCnNPcT3oqaicIYNxUNJChSqthRCGVzenN5-HryxgTyC5tcOXEAuIXJNlYY66HK9lpt68tMXaIpmNetRXLwzZVDw3BueaGGOkjxKMpcCviq0gkWt9a-TrVRNZKIiho_k3pSWM3VhrU94gIotbs_JoZezVq7FpHjLs3xaujhg6l3vVB_-QHdjMuDs1Xq22Mphq8x2dt1ZhjYfaUQm4HwEmKMLyx3ei7zch16yi25RYl-cg4P65C9dCEL96MeQchXuFxGgkWYYK_-PCta3A5xMgvVvSS_H0PEsvZ4-RnRJxmmDIU-KMqz0pJ0yLDNhfeWmkIEcuIFwq54TtJZisQlHlYJZAII3kGnR5MvHBZ0VEtJlo42yf9Vxs5HSzp6XYk-FeElYeky2PKzrBibbM6EStyS8C21yYXn_E6u4SpYjCy-6stfOntjYNm-T298gmri_32bZ-rOU1RrEETsZyHjUoe1SnlvYk4p3D3VNv_BCRKv9chISp8m8dAB5BJtsQXyIDgj2IVlvtxl0KeqYtfq_VVOtX4-BLGROWYpO8VoG1qk_hCmZET_gpTeAsPzakVGgs267v_oqHrqSDvnPB2w_s0t1IYQkbI6uaiv9EHKRmlCrS1nFvqlxpLCxvLGFVkSsssviDZiaayxf8tMdA3HKtvB0UrO1O-tI-SvyQcFqMC35Zali_cj6IeU43yuoMJ825varqjDfUhzBfpbh_Ai8o4lcw42eMjAQYQlOUQpJRg6X-D4PvpO7292bIMH8aIHNpAPepRpHmmIYlaAFuhw193LfC2PhK45faoSC5cKfivU0PSa-EK5NVS_-8sJ6nPlMmuEfl5rlWo_gnrLrgjBft6EFbPa2Q32HPfuFC3p286nW1JrDMZXkzh-tuVRvzdgwxmIU87YZMiWnUCM4BXnR8IMFzAL56j0GRrbseO0TM90o-t1VQGZ8uvjnWWwWms28SZeGgBgR5D5ijaWUrmraCJ2EM4Vfw1L1h3cFnSr6nT0l26tMJsBmJVxhQnzCTHt7-piYOCIBf6MV_-faCfE4KvmbxmoXkjwTxz2bYeDqlcCMjZFNtmUeSQoKG7Kbxh8dmGXvr1LiOA51HX_QyFdgB9EQFnLRy0EYAwclmIQU_dhmCxfsVJc6YobOj_7h1cYa_By6Vd243ZMgjkMagef5-nfcSWvjIF6YMW4yHUrQYoJLtbTaMB5z9khsyHg7BmOxylvQEOuClOaZkLRck02zIT-7VJ-jf-WRc2siqpQiYvPbyfsfNn14kzmgz0wClu7qIoRMo5O9iOQXJG5Z-rrQt64t2P1iIFlOXyCJjQsCsfXU6zD0C_tL4BtCiz4sK_s4zufWq3x8VFKgxDysgFzToP7Iuu-ATb_5LnX9ZiDTl7yOWncpu9_FD2WvQKXTsLNAwr_p5Nuvj8jvio9wriVXRgDPJduFmBW8fP2qYq75jzMon_KzgFxAKjQBEWJhwIMcc2yQW0Mx8N755mti1BZ6ebHVagVhIjPmXmP9xUBE_4qvEWSYM_acGzsIwRRlUtEA2Q3R8wARlzoyBWf6qnvegaTwntsW9VtDmFKuRtKul9Bj-LltBcOUKumfXZMgCmWEvpOUTvhkLzBIS0aEyZjv4UFEEgLI-fGdWvYpCteTQGbMXwycoTHukbPORup9wD75b69YYDqx5k3_6eAXC7odELU6g9fT8dYb1qJpgYULaHm7q2ZprZo2JBczxNxYAKugJqnIIMkK3Nht0MnjGXB6IJj-IKHXCgJ7gv8VM5Ik2PfmAzeKpQOIJ3tO4YRj2QXSEjwC5REZ48fbdFDfWGWgHDBoIItoAJiqE7GcLYt0BJzb9Rfemy7exhEUPmZaWmJkaU1Gyhb_asFwYiUHmrYtPy5-woM7eC0mNIRf9Ai8AFwwc6P_RhOG2mNT7kDqedcuxPi547lxr7hStpGRC-WDfrBb3BLo4k6k1wf5EqplrGxvBDH8VbXr8QYHge-B15BXEDrdCnYCYVxZmjLE03SHU2aN4EWUCgSp2QqLJ8HEfySSlErs-GTF2zkQMHhlrnQK5S67em0QHoX3rtFCNVAnjoy0YEWqQ18ay-2S2nRbdstwCMdCRxV4U-MbLc5aPNclJFRdat8O2lE_2VrQOb6LXasNUOwDCMFjBsDit-2vbdXdJ_edbx7Im6bK5d2Qfuawn2TfhtLJzOFnye7htVEp6_KBUsYLr5OESOsR3M9PD334ps2fPcnkTk9vvVVQBDO8oJdKfsOYnqFUQVTU094qj46i2LdH7vjmIr88P4-MDaEFzdMwIhw4O_1CapeVbbd3PEwGSRHKAHcwOJhPicJLSPcVtGaO94yjrF3b3VcodO3sqr01eatExCJPugurxici6cdzmRpBBHa1Z7YL6J9_tzvw1YBoZ7EQT3a8kiTST5edgmKnpbejtM967wvZcEutZbtDOdepCew3agtC1OnOFlrNtzBhOI-uqfJ9x3vEqg5imrHUmXFNsEtn4VJhWnL0Q1qq6_XaMBqfEBA9L7-tnnmX0QAuAslHgpbjEBTtqa05yjSg5VWHN9A_dg5fjqM8b2ipuZmHWAhiYVexzcGb_nnJY9CBaUspnUePburgc1Mo90UrAcR3UwDT35_Qu-XvMhGZ-xJRga9aAE1_R5OVC2kA8FvWpqxrvYCl7CG13a2OxUKuZUpqLRK3h_kNTIoDYPm9_21in36gEWerbjRIBRMp0dPmxeQ1JiJOf_dyDZ5eYpj4qXyBU2H2lWZWPk5jalPyCm6VpgytP_xsn4OioGUm4NNbr8LCPLpoG4FYWBkhjV2Lcc5GKLhwiFHbBjI2jaPgMry_AkFJPUoe0WvqDArqVdEFkpFSVCTNpzUTieJ34jMy_l9TTcPDPBLD1zQuSZXbHdnA5bOVaFCXTgUvPxuSClvtjxnYe05ENFgKvIcOb6TPBRMzLcpU-GwK0zQ2GWZLpx8Um2TepxCExRydTme40SUeeD0bqZKzPYjx0ddRi4pJ0OyCE3-KsSqJYqqSeT0E1o0xbGsrfKcUs7tpR9QxssKNZapzEO2oH0tjs9n6zTi8t1bMQkJun5u8I5zpvh1qfYfzgE_0XSuAUF8lxZxSO-hEt6Q-j9uI1wOENgtdF57Vi34-PIb95DLaAxzABXmSkHOKc1yvuLzyx6hrskrsuhaAy7p1ODRovkCG4ejK6_1aJX1gDc5qpdz60wVhJR7YlYZL7aeycfe8Ru1Y_KT6OMSkh27CS1S2kiPoCxCcxabxE0D1bMkB3w51Rhyg5-ugFP4jwgI_GX_Z5Y8J8mipOY4jVhfXZukdjhpzQQtL1Pnu0VM7-jhYIN5OAymuXjqutBIcSirXK4glEXTFH4d-H_neL0lODMRIFOH8iIvGNU5xrB5hABl8LTvrZ2p2G1Q04jnccM0QdMv8Ndy5c2bydMbtB3HniSSFtvKKvKFKSj_rklP_gdCXBQoMCzVH_ZMO47Gdgm70vk8TU3hBPTSVI3c3SNlaLf96rYjOZDfrJx4KeridAdv0ln_LDqCwlL5oH0SZju5efxLidVQg748XeZtyhomlJq6wXr7O9UEfFhVDRqgfeDkyopoD_DuTHEh8x8DKbdUHeXNXubvKytxIQuGDZwQShHtCl4Tp6Wd4u0vKTtGCYBvhXulVW2vB-WdgOMvRzkri1dviiJwQhpWaHMouAWgVQT9ti9PLav-hVnuTNwlIcghCxLqd6Fhoy3EkkAqPABrITtOuMv_WP5sbJ_idcPwtf7NdQ-_FdsRv-aQfGlBDZi5n33GMdndIod2HA3OKR82_bqGaSFjsXngByNmlHZTjR8oez_x8cVXJ5hWSzH6o8d0AQm3wjGVS7-xH8ONj-5HMjSX1UbZAhZGVLUXhEE8kUqZAsrH4K65XJbLXoszgHPvH--LTGhyxWHS4W3joZmGaF4u5Xc8Hu6P7mWhmm64Mfm-CoiSrWWUf5fES-ommtTPchC_sC7fHq6DD3faoChMRDPL3xQE6wXf0m3ujhnO3D88xIjT3vAQJ6q_KHR8DFBOIYhyUtpzQdkkKCC-hcZmfoSaf0QKlX_Rdxr0ZxdXF8L1m_FDvl8c1L4OF3FBEXN8mBxCVz3B3LapEa6UucwzhISwZsHalUk3VW9gRdbapQSlcwLY-fOdOX-H1LZfe6cPhks8-Q7vlINjvtmS83wO3QSgwj1JjELfRFGprWZsyoFyriYVWTXeJjF4ReidsBktgPoRzkKvvp-77ZquhUVfhNQl9RYtBwwYSp0FO13SVEc3KmAbkzPYD84JAqiGW8_3v0AaxuUw0-G8XMxrkX9TZZa5lygULoIrQO2mAQxMOTBWeOibTue3_KXF4zmaFHgU0yXuHg_Eb8UR0c_hDVPuEmLlJS_Dplwl3sF8-gk2GS1tg55J_vGhtMT18X86RDQQGQWDXb_dB4WG4T9YC0_r13ZILYBzsO3U6duk-qnQdfJmj0WN9GIliZ9-uEx3qkCvUl34Nq-lEtlefI4y5_z9AGmDPlGwgraYkKjrefFEohJkDU1tAvo4RJCfT3Qh0cOuB5MmAGmU51lNP0QQGsQZvc2rK2y4fuJwXjj7h1Np4YkTHV0awmKxrFeZll1NhhCp-26I1uGqSfju8Kov5zmjdwewxJyp8b3A3MZOhFFZQdmG1VIMnY8M141B21oJSt9K_5rNda1YvlcyzbiDuzzI_cUErHbKA4hY0-pj-Cp3S9oZKIHHMRX1JZVk9e4kRRO2NzUzSGvAJFrpSfsj1ZGWOBe4pgLTTL_oF0bftNY3SAnv0zRH0TdB3oJ4Ny9VJq9ijCVnANtueOst4YaAPYxwTXdfK2e5S21S5Pztx2vXuuVO5c-a4uvfpOxmM8djrk0WiKAnkR3xhoOxVtQQIqS5iaSEAkb_kqUx2pgL1eavvl4MaVM5D5wtCz41gJjHIgBXAvLv43hhVZBUcv5iUlLKuI7l1rmf_2h_l986BVSVZ4rOEDOhgrBqUS57CFPF9SI2L89YOfvW1PG31HIS5MLs5gLcInNOf6E9lBvviEAnMWPKIS6X5H86S-qG43RHFIxQ6BaoA6E5fmFe0b3iWwEfYNEXy7JugbZImaGrhzKliJvd6RcIezuAsYOPfFYf7QKDmdM_G4u5xTLU6Z6Wszi4WTEHBnhJJgNgtIMCVnVvbnSjakh6imrcXe1e0rfOymNlgADoTL_TX9gLo7kwECBoOfAyeiJbNm1XNqstquyqjhzs1vo6NUNYa9iqkEeMS7pT0dpkYbBKMByqqBUyltgZuxBHDAESh4Uti-d_wS9AcINlAcJJlBU8AUIHterZXnqPKcTp5n9vFa7oM2xdoUWhZYWIA7mhez9LqN2Lzh2p0KAAeORJ1n5FDPXgljUMIeigEm8V6uASRMqdNRsrLHL1PDBmQ7EU0lrxBfb73RSWk0-8IgtPnSsnRZFgmRS68k-taEhiZji2VqKeywikIpRcHBMJ0AkcgYXJMA2LTtjvvAnO9o-kidOG33irJlb_2IqjgvHVrUWrt-gG_hZM276uj6am7-DozXmT20H1rpYrEIOYmLxBtWFtmBYrE3qYc5Sw8BboisitKX0vL8p8YReGazj8OpFxjoTOszAtyvIalfvQYXID-8KWtCl1X0TaTFurtC8bylOXeXOq26qYWnDzF7DI1FLUnIJb_vGRHKD8R1FiA0TyvNtLdeX42GCi1b2gXbt7jkZ2Ov9vjiKE1ecNdGqFU53Mrb13bLuH3UaHLhhgeq6Y76XtOQr32A-AFYbOt8FUzGL31LmOFF0joFQh5825-AJaabx9VGXEE8d2ODjpfhDLC-hRQFm_9bwHM4NO5InwoznB8BNQuRrw3mqKScxz4j0Do_cO8ovuO4IXTHqV6przHLEq2dAS696pw3PGuIRA50ICZZDrjLN93C9uu77FluUPJVDbkyQHzxoTYNqq7y-6RwcJgixCs_4a9vyGrVwd1J_g5VEx9qWfox1cLtxTl3EnJ1QJWifP2nzcdw14_sKhV5WxLu_L2JQjMxB1UeesJ8jpUnAgbbro1cZ_BVBpoirGPVZGJ9hLPjdPV1d2Fg1QnRtexpRLKLdNG4ApAAxJcdNKf7mUwgU8doh743J0nc8pRl9Ez3XOaK1lr7Uk7tPpmiYgPQLZ-NjE1xhsBjaito1Nem_ReHtzKlzxiS1iVC6n8J_NwRnQhdBLghwqdK6_Sfgm4W1Tg9W9pfH5sWbsIEohsW1088z8b8hlfkW71mUhi4zHm4lbP0etJQ9i6idSU7Yru6I35aZ9JKgqe_VDwMcJOwbiKaM17J7KpC3FmY6izEG2HaCCsTJ8c64CccE9Yoc3jWun4G90qCbCeX0nvU20TZEZKi5LeWjuT0N-fVWI_perIrYM1jA9zcCzFLy6U22cxhePq7BCeICPkd-Thibq_2r8HylX2iqhXqRr5-S3WW7LFB56v2Y8toYnKMB9bx_AE9kgJQPiiGfzcxQ72FTnYsd7OlLp13S7Ec7hLHdkxZUw6OELi3spRStqyX8AG4aEC_R2zKwRAQpbErJVuAq0GgyoQAhIjF7XwYyjiuRSyMquwkyNkKZEbTF0dwWfbtqp-K_xFRVMpKTBqt9eDa4ptGbLyYKPexI-EIHj6mn9LL8dRHgrTfH5pbaa_nePZr3aoxm3Vdl9ukijgIqGVbET9cjPzY31fQFRKPrBqI1ttvXNFewIeG8IhTlTYDGtSfKO1_JG708Dv-NmicHl1cRluu5_m_TGvrAZiPFIan0gewfrWda1hmQ77Q4H1R4idbvS7mwowtHK3SRXhq9zXpIWrPYjBS8sJkT4IzV2Q4GiOUdQwFLOboGoIcKoUEPSt-eykNKIlbn4NnUKDN2CjUX06OJJKZdLdYgXii_S0S7dB_2AAZz3mwNn1A69WJnNP1CWsMjV70rMBNBiD8WIOK5MC43s0mf2_iWMpmVUKWu2To-8tQ2c3UoJuPZJ55Eyj_4lTFTOHYg_9K_asM997PR1anIZ597Wa9V-Mwybbon0bm6VlPWMKR_MiTJXMePZWrWIUm7_8eBcUo3sWgOiBUxvKxx2zaDC3Knil4u0_m5ivwAhodHtA5Jd8GLnyfYiYqVWqCSGfdq1UMS18PTipKw87MuuIR-KfI6DjJW1G1WcAAmUZWmBxARF4DUJsxz6Owxau79yOKOWX5wovDjyfRqpZF6KTkDpu4z0dy-yxcI66Aw1U9xLCW9j3Q9JRECxJHg0AlJ6qL-d_MA7w_7vKRf4aLJplXnO0RdPWJpW5O7l98tnmT2QkBmRAFyGXPnSO-XqmQXOBDlAFVGt5343k4R7HJTbE_nujoMTLnbVyDMe2ePSPZ4bSIfbrRqo2FvnhgIBK5lcGVVzl6_3pubW3N7NRZlXPnNA2W0ImZ7obESuBeyXNRbu9FD62IPyzSFxVflaHw3alMhwjmT113XHHm-zOkhuYm00d4XDqMTgKqbs5ritSDPIKUTMFsACMsnbW1Cm_VzA-1EuUt0cO3sX5_oMrKc1ZaH5GgZSFHrpGwzJpNyQm4m7rTfkbDiI34k-2-bBXOmgU9YjklmLZ_zu5QhMJygFu8NPwfGi12Kk8bCbda-DDOEosCWbMCSclmWytzunMsFUJ-jjte9yNh7zLvtdQCj2FCncplW2G4kImbqK-J8Ov1ElHF5rcYTaf3ULm6GRkbnFy1j4juSd_bD6Cln0n692o9SSow3vJvqTVw8qDRucHZEvwnBc8wG0EnzFPQs2orfe19FurdYckkun4ej34pq6XA8VOsthfbRHJ_ZM57FDN0WugdO6XFQVQdhkNygfpi3HAzAgdxzymtnG0UzfaA4vFhojTDUKCnuPLg2sqK_i85rt0zJ28DUWm5eRCv_OpY0VSBbLK4-NmVMEQ6A5_Q0LlOY-8tbdkQOi8ppmFqSqrUPtg-dXey31sPmC-prJkURIv5LSaBiEu8X6jtbJy7XWKX2AiI_HCwCLb0-Z5GrFqM8NcZn2L4naIz-qyaKBuT1vETtGGNfQ7d1xVbgiAQ69N21ClvggUD0Gj7h351UM5ZX92iRaVik_glgj6Es3VYVMx8VmJkepzLVSonr9m_Cwvf-r9Dw_nb38uTUfJ0WFTHkiXGvGYa69getkR9kfdpCRX8lX5RDrIs2xbaC7iWKJMQWZcqjV0Ygax5JVhvd5pmvtSSJpnoIlAp3nBuW143AOMOjBuO6n2hhpYwOhadTNLB2VQxTBB-hQxzs1yqzEl50AuGjENFXmNCkVfLgC0Edfkzx1qhaIOdJnDdoOH2HZHuegVCWwGFZCeZeYlds91Zsl3K0mSLV2-IbNFd_IBMCqhijQY48_SQN1SbdjjXcH-RMNDymzokk6uphIV3ZG8bAs3muSrMUfJB3WT3Hg70Ih7aEB276rLMw6R5CAxjOGCkA2FiIMa2zOEy-h696XMbneMBd4IkT_ah2BkVPsglq4oYkzLBW2ym2-jUN_PfojyVX4Jkx1hSSNUEMqWUV0TrWm8H2mWlGTManEnlv-WU2uI3tpH-9XJoGC9gAUCysZlOwU45n47flJL7sEGfYGTQNfKAXKgdIVULsraJmI3X1RC1JR5Y94BoIfYa10tA2le2yAcqz_pM9y6ES2Kb3Y7-l2kDTGfMD1Cvpj22TwMLpUGYGyTaFCevb9el7VWhKJocPpnX1oB-gJXDLtk5CO3hF_wVMGNOlDMRjWYEfEZs1-MPrGRN7NWnDetszAc6yq1U0kpAzH8FVcv2dn6bgp8YueCOyvaGt2D0o8XVTvlTYhS-wp73Fnje3GSBZ_wGvfQ2D2q3BKhPPWkWOCi2D65PO5Og81Mp76OSQ57PUPR6yJJeJwD_FFb1Q6crigw6d2Ct-JDWGBM-JrqktCeDQC9IktqVUfAj_6ImMGQ-1oexkhRIAloVgwFCte0ZVqQNSUNecDN6wSLwytOC5d1Nxn3MfkpPmIOkxwxzemzFtDjTcxwrEugVWldv-kZHQhcV-zMR7Sb48T1h57R2gtTXcFJuOQ1r50UX-EThA3ypo0FeUIhYijWqvLYoieSxnsKDV1935VWfLqHqWPjWz43TjpIxJWWGkstIs1xvmGA-Bp39ltAIqOz80vA91zt3DWD1t0d5luccP1Grfuza-8VYZRez7ni6fiFZs4Vw_5lKAIr0hlbZjveEC-CrfBENZlCe8QXmLfdzkuNose_vpbL0sAFtUbFqkCiEl4uMl_8u_5ZCMvbhC-gRoD6709MkPUBv_RaB3TxrvVqr8SOztGCjk4Inhb3SOIlVJVGNVWUknfpi-Wv740_oON853yzC-TH7YU1rEFArObbN5QaO71cWfPC1hdsR7ZxyLnAz0E2YNbtgSqt2HsMo4u0iyKHvz3Rb9d350EvGpaiKlInCsuQ4SWNSF4ia7YAOqba2mtlAbi9qip9WQrGfIbDlPokrY89GlzRcPrS0txR5cSc3i1AD_B2EknE1j3ddxDWk3_mv2OryPR7a9jvg9eWs4jGDgNGOI08LHXrBzezOW9825iSZbjf7VrM1W4dVFtIpHD4UVLxMWk3NkOx_TQ7u-hE6zyHvs3UhMQiq6pXIAnMaY1Yn-lw708PzxExv6gaeDbI_i3Mr7YVvMuwmxgUO_qodsscA9Mzx1nVoeCmbxY1ixfQpzecj88AN02PJcWrn0cTb6lBIa4jdNeyEWgHpECpjhH31OQvv9hQkwlKYdnsPxkY-X0WeYvCQLz64Pzoop-aCoPPbMSRIRTnvS4OEwEiXwfQb0nLBxcxDHPLDwA_GpWH4oP94sbGWtbA5E7BnN_8h-PJ-m_ctUZzW4QOcNqyiqe33jNcihyVVQ3vBRaMcxUUjizycz5_xkEAdlEm2-yQGfypHc-ikQNaOvMRWtJzqyPoegscyMMS7VmXD-MhYP6JCFMKV9h6rm-BJ6mrqzPhnD5h3rRCe1cdZ_hT8y2aPjspzKnEwM6Mzqwv25li6QggwOYMEpWrGAngugzmFeRghH-gixhee20iDLdVm3yrMZKwcT392fQhyhAU-fnTZTI_HEr3xYaFZBTQ6v-oBco20Rj7_eMFhlwjEgvUwnPTYrF5sryNI4DJ_x3-TL7otiaCXrvdj2lmHBojevtDxpt5b6Amc1U8u1OILl-bYtmoeuAPKfp-NhlN8mSkPM91CgNwm-gwpJxrz4bcLrAMZ9i0d4BTpW6Z8Wc4hs0Z5JqYjXy17DWhpQrJuXIiJt46mLFbEdqlku_UuGp_S95z_N4zKHAzB7hUwHTIkHo95aKuwSkOQ1CSIDXFP4txL-ILMHzyf8Hzd9r7Zejf8IFNAYwowW7pSZBKR0nTHrLg-NBhBHh2pD7CyciaJculgFwMZauYH6Y9i547i8kyN-lTvEp9vD0j-xKXl9KCkjY_MypuG6Y6Nnbtz37VBIn3E5qWqLM4V74_tW-X3bUmR8i_fC4VanVPuafRXNq_L6dWCQ_PZ_1Bvm7lhI5xek5Wf-IUXYVnJImVaCZyYxa2kVgrVbndl2jDurkp8VrXG-7UTAe_mGwPR95amJIa2uq4HHDSUk3Q7k9jmsC-qKDtI7EfnrsIQYYmVEpF6iDRNXxxvmGcieRlTxDyDWA_9AJsFC3iszllLWh7SaRJnaF0e5nPA6n4a-KkSuf_BqEnvtxdfKxHTykh2KiJ76rJFT8pP-lcpTS5OV9l5BfR3U7q50IROYGtFho9tCSxm_dINxNTMeOxZ2obiQSGOsc8dp808JFpidoGUs19HfhXd_KHxwj3559j3oyqYkl-7AMLrgqzrmRnw9lbqvsIIkpMs7FvXvxzVSIVIBit15pQZbq3Gtb0R2upCnaZJBs5BNDbmIv9EaDaSgP72lASz4UQ_4Y5ljGSEZTtTi3EcT-BgzUMmeHeLY6OwyXHEDdiOr-AI0e4uVXZ9K7oieyHOYuzkoDVwVpr3redZyct_2_WYIjOcUMyzsMFXXZSzbymH4gAt9e4YC7-NYK_htxomdXJbUkbRvZfUNHMCOVIS1pVXuWfi4qhlyWppM1FBDqYso5y7Clnm1yhT7gVIy-0KCTfQqXBHbP-wsdHkCZ3h_sovrZNGIX7pbZqRYStb9jnHgf01ZP_7qPioXeMwEuZ1shzgb2epCa6V1moaxiA73A7BBsW8KUwvt5Aka8vS_KpdB5OXqRiozXaRb-oD6NRc4JyugCGKQ9g2ERvkC3Pvvjit5Xl-L-ZWITFYBaPlxD34WAny9xtqKFuP3GgemK077ypZQPU4CqPMk96-IoeBfZgO1E3sPR8G98n_2VhBZ0E7jjNA4eW9hndlvWNn4wxnzNcrmcTeVgQHZupJnoc2_0G0Hx7G6aFafSLkcSNs01pTy8vBDpB_5iXiPuECi7MGybNS-65AxOTA8v7cK8HwmrbiPwCFi1ckcnYtBj9j8ThFJk2SxJ4Gr46auLoK4k6-sPueoMnFd2E-1oUH5JBf8bdI3g6YF9aQAAw0ecJ1mgKd-pCv3UySfIZfgyPSjv6zD79eTtCdZOR3006ZXpxnH9ShAK2Zyrdp9Y_ZLOjfFqAkk-yh_70Dohw5TQdfB7eR-txYEvrzgm-jOZ23psSPLxH4aoZ7Lnd32iK6ZQCVGwUJo09LuT-qK6EZiLADRcL3FqDY6ZT4feQZbF7ri3FftqX5WWuUh_6oVpZOCdxSVkZloLtpHPbdbajLFxc7YQgdhouAZO5vZF8n3ggLCavi0sQvdjGRkzLyrS_2BlIG2pHE-iY4ZsK5A4HeC0_brYgWheBXNQThltxsu4nNk4b3xi4cMkAffPsnw9wYQuK6VQ5EEkSHIhl195VHyn0TeJixCQAFFyZa0QOJQocZukFILKQL29KXhVCXVKVy90ho3k7ly04jlI-ttL8Y96XjVgcca_koopUfuCHg6EiJXv4dSHI2fgD_HGumxTBcyUZaPfcV4YamSPzuRFUWRHINO7_Nc7H-jBiyFn7xxfIR9NaOgLieId5ay2fVptBkiWcI97aXgjJL6CWvhrU_9-XKmShdudp1tJXnNin2YEAeqN6ODpZYXpqu61_yHwr5AhXF0ECuKpzX-mC9KAwehwtcQSjU791usfisewJAMhtzp5D7PlG1FxWrMNLYGhpR8Py8szsJU34nahiiqRdYCdvofKVssJWcCAtDeVeg79bgQtBWBTNwyv7L8gs9KhWoirOfhVj-TXKcCAHOJnj4QNQ4J9cl3lT4JRhOyPIMQqp4_qdWaap-nF3W2Eb9fosehmeOeJI_-VS0O0vSftj6pKQKNNcdYW3qod24wAYbjnaYCZLy6qXNfXhbThmbofYLgMFzIaDc0MGgU7FmQlnabf-NOVqXeg-iuFYnekuVHAVb2jtSa509ngev84m7-iKOvqi0-PBtA9je1l1BYs3lBuX-6KC-FFciKBeA4aSuAHOfsj0IzdCBEceC8TwBPWsanvab91IRGnZvD1eCf1lbtjS7c1I7fhWVjxLafqtWiZ73YMjF27Nbu2fQijR4HTayiE_yo-DSgYTgw3UfizYTmZLts0zo5vIkLM11-kh_ORVnfJrYtVstABoocPaqbnRzSUrEn_Ct-UjX4hhp0cSplaqPJxroFYtwO07cFwzv4lzene-U6PbqrtGo9xNbuIKUJabff7T6ZKmxnsqZ8PxbCCT0D9-ta5ecWiXyaylLcJKt_9lLZ2E5P9Pt-fUEIAWE0dWSfE--wa6MB1Wuim9Z7Z1k1aTJJNLuqP3A1qsnZ4JKi1Tkq7VSEjr00pZj4gU3ghczHBS3GVEQsfMTHTxgTidJtVxyqCmmsz3kDuUFgMg_5FJR3bySYYeZeus7NEuJ_nqLzAH21IGQ_PHaQ7DvzyFcTqpXxwmN4qx95K6TxiyYfDdr7euZWqDszJBK3AyYaTt20-koOoyyFXG8UHmqVQRv-qkxu1DQ_cnHmZzl6CEzF0WITkepj9dpENFhHeBAeoDLnZIx-x3k-uXzctl4x3QauHlXiqzIGK8JCjEiA1FTyvmNaD-dDsojkIfDQGBstRUNtUgfi_R7GgPa0jre6IL-jiXAJPGCFtDSZ3WNJ_LeFyt-sBri0J4boumatM7-E_gwO0nWLH5qJoSJuD5Kkewrterc3jlJfYAFPWlWU-vyd7IV5AUq68TKjhpR2u8Mu5QiQfBEzi3eWyTGnm2J-rKUxJhG1Zdu4stYGR3aQuxAjXuFZmXrIUCJAe45_J50rBzMrTpdoF0ae-GX1joYPOYYVUXhgCxo-kQkIU7nvYOzf3wZCk1_QOLcYIlCy2tO_kXPgeSbsq5WkeBP4e77c-L7ltBYlRPZIM1RcbQaBqIxo0lHtZ7YvWBFkpRISugvtWoN48nCSSk9wChEZLL7DFgaQmB7FpL16quHDXIVPRtKta9Rv3qWbaDsgyuMiAclW9mnLo-Dx0l0baNbbZlOAXGzQqZIHOXZTB4jCBnXNM3X-VW0B-9Aeogvn71h03dFyw46NzZbqS8F-9FNruQlNFro8xOjSYjlYXeB3JGwHNNtqKKpaRV--X0yeYHh_3yrIbTQi6iN-1qkZUdQPF0J16AxMoNQbe2l2bnAooK8xq3Udm19BDmvrkkbg4mwB8ltF3g2y8pYChvs6zwFHwm6K6-y7uyzGeDdFRAJegwYiTRUwpSEnbjDStLzNfgtp_9JBZl7XDvPjQxlOx-5NvpD4YD59NzoIIWhuTA3YZUF69ZKJhAwnKhFyLCclV6u4ayw43LJbXmkHb-b8d2b6_wY5ur8_v5yov9kdEPoMryh3pPpVYLPpxO3vwTbQeO3B1UZgH_EQHKKB8PPyuDJYLzAFeen4o_PTgpVwb3BpEveAQXpYVSEGzVdRMO0UrtIvQ9dzF5ca6zJtg4Ub9D1Hccq9uNFZ9WD5I-_B6-Hp-2Rq8F633IbXnKvrdadmewdJLhsQ7Fv_rCoFMd9ONa9yW4ndzndaCEx0lE2SrRDfJgxcSiaREY0zP9amwq_cREAt-doUHbjuQrvP_6HBvPCUzzFqNInparpZU8gj1gCzk1TewpJgUoeDWHqKRl_3GWtgJStkSYEdMw5vHcoxyqxuvqPrLR249w-xfn6rYx3TxReTvpVwYVtmeZQnRKfZeqT7SA7kBCpqjf5VXDNwNYfU1hvxEpfcPSVm_R5uIEXJGfTsUITUYhUh0m8l4KXlwvl0RyupbLbAHqFmHlZpidDW08BROia1sFyo2_I3ahRV_sUvpKv_J5pQnZZlPAZWyJRRpLYC7EQgiIurHbFNrHKCT9laiBenQ14S5iNyi7iZSIUqam2oWI-M_K3Tpv5D7xT3l2hpZ40rC9hdmhXCFUZs4KrE3UjGdDAejukCRsVlVDGaibOJDqbOroCTGmHlKewACQvdzOciUP-U0vySnIqxF1FPEO7pZP9-E6Tfdt9wIFCT-K44DLGM1Bf3qxCUWvaoVfY93_uHuIS_edi6WdpzpaPCf-8ejdP6YCp50QPZsmntiz2mYSN3GsHZSa92wAXMvTZEWiyPX4tjIE0rzDLB5iqpwXO_qaIND72RaHyaMCDZ2ZsaTtW_svFyVkjmOKbNNeBUtjUDpu5Lm_e4vjpAitpA9KIHbI_sM0uF6TXrxYK_tvncfKLPzNQAge-zoJ_vII3Ht8atHq4WKjDJYEbzJjAyxfVykHoUPfduKfaGclYNS-vBie4IRP2dvEL6jCX-YUt68OjyrjwF1GnZNeC3zLnQ0UjjkpV7KoLLRmKefg7F3gySUxEGfOFwuPrbDJ3mrAKkpEaeMXs6oooN4JfUigqJtrFyvTU9R9JeXtia5euYGGEmI0Jt7hPpIaseJWzRNvQcFBjYo7aLDTn8GGtTrIVbpACaUIxRUssrNXrvbYnr37yelA7nVOZSnZemRWSQkQBzr3jVOk0aLl0zlVzoKbxrz4IfLk739jEHaafh7bdIWNJ-TWTBRAzIqS8EGfJlfMgvVFofnE-st_0H6jxsLGmC7R6VuFE9Z3JrnMlc_0RJTcfegUFUnvQmInSc8Ga9qetG3-Jlem6D9mz68WVf4GL0yYJ2vsNfP5cbnfmVcAMUv4m7NTlGzntU_U8N0fkP6WdNvUgb4qGK3L1kWQgnO4GjETPqN7XYQJME3SYCX2Dxpm7Th_WB7VUOC-5FI4l87zK1r2Isdns9WHR09G8dXVnfeNa1gEAVgfAUKK-hpj3-tI-rUIqRrh8DZRSjjPKE4dT7Kutm3z-HRuB7uswjPvIUMU7L_0fuhraPNu_UvUhjoJpBLBaB-b62ahvYdcArC5GHwpem5CmB730StK6QatkR4BPiWV5Cs-pe8q4M_mdsomkdQ6Pszbc3maYJ2y0OLEKCRpcXpmzg1iFGk6pUQfIbuG_yDBks_BVPT8_3QnuWyxNpv0bU8tjNBp-zzTZf1qO0WUR6ZK1vbU0k_qpIqaC7g09z3reFd8h7Ua3iRdGyFvlQVe9yBH1QT_ABfIcBXmg_Ggr5WIRyRt7w3_oW3v45aaY4Da9YEswwHPjdAB2jli7FcstQneNiKEnM_PFDMOR3hXm81wvn7ylD5Sor32wF0A1MuMWYo0apron4ZRCcdz4ISAK4HqKYRsLI59dXzBhc6mu79oor-o1WbX4h_IxvozLMghdsPOJar6MweQXeoK_3yAxWXSd2-dy1tZ6fxwSFrHWaixHtGXorlQqn2NtiU5wdN3aYbpXMdECR3pFxTAtw9NSYnR3AiJqc_DabNyr7w-95uq_fSA0DITflQRI72TyVI1f4UuWJAplYoPR22VSuJOkz9EZIPN0A6VOKeeOlv0WaXl6qyCYdwYBNzl6NxTLTBG6QYxq36tXgzC7bLooVL6ektSQFaCbVQC027jcAn6F8zQ8cp4SPNZojinmj1Yn1xzvLfWMTaL_BuAmM9vAjkLj4QXMLP-9_rneaeY-Mx1VdZfe4ffb3gB0UUeNScNkh1DCN3Bb7oiQWkU5u2EWfZSPtjCbwFrZIxbrVTTggoxDt5ubzTc0KuffK6LcLLUAhvcCJUa7zhlDzakD3zg4RntfWcmcbfF-dwwY-osLEjFbkZmo8KyasRRiFGDx2CoOnNbsfU03RipN9f6NPjUHgxQEbflnc2G9h9BG2MSL63rN5g2HRIGmZR5mO7roRLdQDfMQiwiNjIgVSBLTgAexb4Bv2FrnMoYQ-x0pVDRcKo1zvgq0jOSrj98MBve0VDm_4BW1g37D3ELVqxw2IyFnvKdwgd4qaJL1ZfJ3_0b6KBRXwUvtH8KGxgLWJPQvWbKvAy8GUGi02g-5OS-7ZORn1mutoBQzSDEXhwmUY-cf_nbesQCF7qP5xOj6lOevvnJCRdBfol3Y3TURjunY9Gel9SjHPpU6BCsf8i9jRFuNnofY7ofnRwgDZU2zshnrtjngZUhNiHCcRf_-EtAeobVq2oBpM0mALvIqWkn4jdZMPlWkcI1UDed3is9oswPPlKahr9XVp_ou5QnkDFOnmXK3dQBKLyo3hxRi8yaCWMzwU1_pdFfS77M0xZVsu2XaIHi3gbVtueZ7GOryuSq0JKw06dRDko9x1Q8-1XxP57-eooaoElRagkEMckldr34Ynn13Nb3Ym4LhVzZDqQ9U1UCRcQehjzlRgHKULKx6lbwQBsUhL9ydQ758y7vgn_CeugG-aWb3aHKq-2AQjmRnxcIP8428uRLYm6t-kZK6xT_xh1B-iMvBXumdOlq5wDcRiNJYZyTyjDmfR4utICahXnXbFknRm1Jyfq37O2HE5RwUULpjxh5xpR-wYS0ZfRkbEYA6B8Hg4LTYbvcA7cekw9DJiRWBhQxGWilvovC_SVwLuJsTyONH7k1LqzcaUDlZ3-LxnE7A3KhKQsuU-fYbjnpANz_EfOoSUreTRXmFAT_Vidme5IPoSPpGifDAQoJcSP7vt66vxJ39q_QwGbzW-r1mxsgOcJ3VEGQo9jNpY_uitSJ5fYWvBjvjBiTqPow8rmq28LsrEbspGFZ3-B_YaNytfWDaUKTvwfVC-64A3Nxdrwzn93QVdVGk3MYwfED6cER1zJqmco6Jh1OYyWM06wk-vpwvwKqfn6wS7Ko0TEW62388n7Y-1pTlE6eHnUL9eYio8JPB0fqnktMxZSo5m5QozExZLv4wxwU5-Q8oCp0QsWoA4frQDMQOxSCguoonat061S51ovxAr6ol2vbb3SGtwDEyFGdpX_e7ZP5aVcArbgaPhqoQBT0SddlxBg5UFjKuf_e1lL9E4qxYi5bInJiWOtdjPz7zJgY_RVYR8wLYx0M2NWIhyufCI5MHIfMrOgpDU3m30VV11BjF_4eYQUrvhqCe7jzN9N96yCT-AljviMhZd05VM_GUjbg06w3AgqQ0vMGBVdj3ikK9n82CNEjBL6ulZ1r4_SeOAaXH68LqUuye9MobunI04gm9vYOjaXWnmid8GkSc2K7OHvwvH7E9vwTx4ajp_DAcYYB9kg7FuR0GHlUtALRIBWa7czi34pazluI5HlDX9kHOYFiayYByEAW1fIC83wz5Ker8h5iQxT2pn-Jos4PcaWlxjKqUY6WtwmJHij0tgjVKFp5g0S-I-WV5bUdyeB_X6AqfR3Z5jk5rNk3rw7u6-bM1TbeRTzE0a0OPqh9wC8-3JjjQGWrfcGoYTmaTNp-7BgSUX5qpqJcSF-ndn3RMJ3h74o2sOg-RUMAQhRmPSq5DlsKUzWKNZgKMMiGsrcvHI8f8p7YOjaozded1B-zA5BR_aRjegyhBwFbTJVZGCQm6xnm5uezJymPRR3MWxMIyauaya7N9w1ju8ev4u9b3e7wBXhOGYRpaPlCU8XFFnxMiQGQHYMhfMQoGj3P3PxpQIjQGZVyQJkB-pXQnW2gAkduMpAGTUzrWQ22hPv8Mdw8ptZltqIMgM5M3aFvsGgahKfF2unnyFID1OY_6en5JYnNBAJ4NpyfOQ3aZ51VSZPn_auA1e0M8Gm2Tm5w8Wk2xWX2T3TAS-rovYjma-XveCa3eOX_PvJVAGwNRzVQ9j76Y07obRst-UzAvqRKSQBUJELVOMY6qoJSsOUzX4sBrZjoZybdD9UVMYUpU91_xxvaCeRWREnYMJDk9G_rAw_x9zbA3SBB27mnPvsrnONyryqkdbLSiNhL68FhhS_-woSkeFmzCjd5DpvD2u_jJWgkLFFQE_oLG1yTBGVcBhLQlmsFsozqKWSlaNHarW4pu_G-lZB1Tlkmum3VgU3tuhXKDM-fHSpmVcjuUF3rSHM_qMzGZLXAgixD3X8O-7dDun-d3GSBK0eIqCQ94MfbMlF3JnIRipufG6xbm8Qr12VtWeRpi0pAQhwV1t5hxGpXno0FBxBZoInSt3krzrfRfG6iDOVBN4ypH6GDmitsAW6w5_1L9YmdB8JrU12e9Je-UThHsnZymRbUH_QyMcxIuvks_JHIul-KQyDciJp_3i1tEepUXEkp3UzHDPmYVcCyEOJTKjVQYVK5XQeHm_YmMhUra52PB5ITTLxrowNMoDZHEzCyLzstXpkNjoDn1FprfDHYXcb36DmplFxOR-auzCeA71LjG66Mx59ZahHqk1QzL5DSxOU4zRrcKJ5RJKIhVmMUjEv4CDzlz88K1g0G-mXF7spTPwLxHCKlbW8MAwWeMVipcDYVJB6I22TWRMs_pzalQEryrChJ7RTzDEpfbFa9J15UcWSDKlKudOW2LNAlR54vFryMa4_vlZ28XK32Fv_yS_taBA2klnMRyRXdPhbLPW9pKJHeBBc8prMtm1DNJMiiibmsRxpBJFYDEFBxT1VXGdnQhrltQVSskqoqCmuPTIaJN7k5rKZfSqD7pIjijxCmCseNUp2eny7pJN-urFTZGs63yV9jwGc3hEqxOrhd4N7y_rk_xN43Gnadmi9OZTBGhRl8_Tico-wm7D9qrBmuMaEAb19WPROCVAn4ZAAsv0weZ2SiqN40h35ehLiBkyid4sO9hFcpTrLGjSBxA4pzTV9QDAB32p8ixaFIWilyvz3CyYl7Wx0FUYbXd3L0Wn70st0szgyuPrgYv_TciUWeuUL34CrNdaZnhetao5AnyHS8JtiRGpnIzet6mMJoLDbqcaqK87bl_W3vXUSY8BNubMJR8U_tm4FubquXrmo05xCMA_bEcmVcUbAB-HNYqcOUA8_AJg0zNGp7IoT4vgE5J-P_Jt6gnLB_BNI2RHbbXIbS-p-SKACcxcQShgMTrwDAv5eaTojlyewzSV8lsfjpyqIFo5c-G5sAUTn9zeYJQulrf3-Itjhn6FtUJxDgPzPVIGOAzQXcdv4hglIML5KlErYYllH5Jn8zxt3od7f8lXuiqmf4oHw7nZOQrNEfeBNu6TaAB-49dXU5AuTIETVLCqbqLCYNEkoG7WP4Hhxgb7m2n76d0sU7Nt3Aq5MnZqNAWhh1ntEcSTWYUmYqI-aE3Vp0Z3jmtzBpsIvmNgg0piteBCFcVN81Jt8wD5pO4pfrTdGt1G4iXz-nU8od1kNBE7ky7hQ0iqts7S1kKz8xaEau9-ltl-swYT2UBXU7q-hMUqXhEPr7MVtjtKi91mGWOthCYS6sN_UUvpU1WgssF-yACqxpLHM6_XIR2CKYoL1ieNAYtyb2poWslWMt8ksaV1FyBDpXOWg7cVyZvnac9wlNsoR9jyPvbnUTFK70IrKxH74cuxDqrvlXwSZJVPS-rwxxIe_L-D6kVLFtmDDaSaYd_k5NQPXrL2Ua6lqrhzYambdTHP7LtPGPE7nQLeBCakebKRrobVFnKNnRSoYweuvGe8NIRpgSy207KUN80naaZE7MWdQFvU5WHhZlIQDRqunQp1UQObA7CTHysFLZysyQ52ks-wqQORVuubJWPw9EFDj1Sy_Hd4Sy3PIhgRWv5jYB9FhztOq3tR-y31xBJXGGTNmxX26qJgYTJGCcLoktrelROBtgRKvVoqF4bQc4irrYdHks1Tmt95ZtLC2MX5XrwfeHTZhasqx3COzmVpYrMXuTUn7go4nZB4TAeTfzfOLGZ-Q8qq9AHf5LqeIct3IuGZhWCwEHTQBQTOpsVKjUlkAQRKQO6QhLGZSjm_JaSLsQwYDSbfloGEHENdPkYrmQEDQiAZK_1FIHSrTUKxABA6_evyQ_D6JZshD7i-aKWSydoLNcOlHernib1g0jF3Gh9PZXsnEU5ZPC1DXWTipPDRQZzltYs9vzGaO7tghHE2wN-8h0JbCvfVoz0LCzCH-t0lZnEg4vjpvWlpNW3-geVNoU_or4UUosyCg39S7OVtu61HG2voNcdRKQjuOTfuSBuR9eHTNjpORXjNCrPInzhL534r1lLGbvtcDRQj9fVbc2pH9y6Z3CsT5kjBLXe2Lx-k-dRUpf8FXAdgekg64aq-L5i5Ci10oPnTvylcym140Yiq1I92uh_QiBcYaEKPwTVYDGNBehEXnY1J3C98EeLufsGZ-TZ0SrHw5Wf_Ba7P4vbqXsH6B00SygfK4DiDKgIXml42oF5XAiamlFb3_tjSI_j7RSjLpt4CyEDpVgzr0rvPTwquZCHl9xgk8OR5JSzZvibRToYhJM9-y6z00zi4xaRjfByKHaCw90ZK93Z0BSMaYlfCEpqmwpZcHrXSq2G3Ss150GqoOVav3AR7SBUcLYPeyuiaSj-47m_LnEgMifw89tuKeZez85D3lH2GCIfnQq86ZcEwJqYY3Ldavg2z92WXgLOnDNa65wTChlPeonTKrE4uuarkPAVOADytXlLUv0euZxdDfxYGX05qtz3G_qTe8IzUkHwFDruvBpT4D5SL1VYJtG1ft4FnH7Om7YiNdrYj4Ux1_FISfcB9PWgzQ3CmURZIvsyGlE2jlDijXgoZtsWh-CCcoa7wC0_hhW1Wj92rBHvVD6CObf_xuBEc1zcdJTh30txWmw4NrLrn6Sgqphqss9dsrXMvKpeW67mNfHTkX27lubymI2aQyweeBnj09UY0HJuusPqcZ0FxcNapm76W5LBUjbPtZC9sYg0WBkwDJ1P1siiCa2nvzOEZbsrxRUqbFx1bTpVhgFcDjfsJXZBkhTYvuH8Y1xRl9cnwcJae0MA6UKSdjGcrNx_XgJ_iY7DHbba8yqLhHy5x11896YXPJlv3Aaz9-L4VCcSwvPzmO_oZ2Re96RycIoHgKNyHAvcKbiq0zkQI6wP1To_7lkv106lwAqC7SMaxe_myw7K5Cr7WWwCpZsneGE-LIkEVvZ6w1-Rh_-weB4RTncPEPnD2wZdq2nFP25PMNGWX-OLq0POBXpwzRh4HXh6ZeWKS1p4ULCsa1f8BNUzJb_KMXlRA0lvAAjcLoF5aoRLEPNEUcv8u4i6aA4dOJB_HcHh9uU9F8gLWAw2GVbSQmZ3xvhs6fi6abGrctLAuUMey9tL7aZDPCWy2_nBHUYNYui3nd2itcipWBBCc4kbcTBddhgORK1C3ofYff9y46Vbh9z5EVwLNvwOcQwh-CxVZ_gHxxpmaspWWpL0dCjiWnFpra4-Dt7s9KkGINU5Cgt8UIKlt26Q885VonQYuOCRklOwPeBo-eaDyQsDOQCJcSdKeTWSJDBpCNVNHAnYJqciVkCRzyoWmBhW8-wa4YOvUZdS0JwLwRA1UbykE1oq5TQULtoxAG-cappHqUf6ODYDrTSdZTrNr2Ppwb6w1RuJ15orfZh9DsXCLWHrqo8lXGCLthB6iC6IZu2woK0UTZR08TIdmo0KivtXLLaxYwNU-4mA1LU58Q7Dn6xVlgh6Mbh3-dp0cFJcDYMZaXK6fdOOttKC65IAiqZJzbmjICRpqDTcWIZT4D3T_uhwQKypdx-CZG3FvhXuRCo4xIn_WkVw8iGsfJnLpO6jMpmZmajXV_4kQvvP-4d1PEhM1krp5fn0EjOYnwGsCFH7SXLGDfXQYHO48eaKT7Ep1cZ289wLILsX3uICjaZ3NB9H4wq_wBxlf-iB2-C0IwZZt0BWiI7gafP9o4yn8-CiFeOmc_K1ElToU-VrZhb8DU7m2UXeyZ-_wY4nyO800uxmA5xZD9OV3KEVaef2Qu7eVQyYXNmB5iFDxV2H3BNU3GZ015m5HsNHZ_gXS66zwHawhAu6ZCipMxDlujwHWtiUijtg2SaVaoN6UV4vfiKVB0cNbiBYGiJKta_3X4fHTqruxY4wbXny4qxbmRVZcGUmiz4jXi9yl9iDPEjDEAs-pLuMZVCedvR6G1RB1mp_miiWoNZKneGyBxdPeHUbSsO0PotfAP4lxUmsRJK_JuweEv4Cb7fc0P__FWFL5S7VZSKUVTcGSmL0ZHuY2VBfF1YzAhQi0uANMLxAIqPxlU3IZC5mI8dmhnJTtKh1bGRQs_7wA2fkfkSprwnpYJLqaQ8lPoLQD6wr6LsSSt4a4VSQn2bET1gop_YJWpSjFJctDfziZQavZOf0V1vCcrVq3KxeFgH8gBrbmHRcIMTIW4FUZ_tEnGIJTaZuFgix7ZiOCp8_zCtaaaST3YYV-p6sJhCn3078mm4_db1DWD9W7Zw01_MBpNxIjVV9F_HldM4l9e1FGjBmRH_Eh_tXum9zt8fM_0BjvLjEAM2uc_SgBQ7TAvM9PDxq2d3osgTIfPpEfHuk9gQW1LS9UWu4Yrb_912PF2PPn-ZIjhheWH5a-Fc6L4wUbRq3utHDXlH3d2kKtK45SedaU-Sp2T4w9BQvDuLg0gKNnf95UIV-MfMzzbEqkhCbpGyGlwM8bE8mM6f2A5po6Q7P2mvXbHHiUdzk-kKTsXDhsuP83qhis8AgRRfmDNJrYFLgxLCC7r-5RUBRW5NpiUe0nIO4SXRYIdMCK3E8k4_qpCXV2eUBHHVdCWaN9kyraMSjonO4T9UBTp8JnkGK_tjtBR_dRHHNYyjjCRASi59eElg-YYCmR_sz9FwMzrRHayjEmXuFXNBmn4e2i-8RoMpc6WWN0UafyNkHf_jw3mJEWGgTFVvFGtACBA7dh9wDbHzRd98OudfiXxhqTOocFGQN25ttOZ0s_DObWiOnb8UivSr1Y8hkUd267THSYDeViJ0mjchRcrhR1CCOLg7gzgZCNgBESDgHrTzNDWuxRwjO9FAyOqjW7ZE0nITf2bSl7zjCtpOe0wRrSDWAOLnuhZzdLYSYpCEuP0tgRC3uhVNkJedgG7_aWqy4tSWF9VZvj9TzqZ0F1uIZB1erM4XpkitVH0fFO8RN-rgRSelwg4H3w5eXOoksZD0pzOGHtNWmfC3BPzssA94JbMwl_CUZ__OCkkYvEkTUW2tcfLSQy1cTnzHXuYMXEwFjIqZStGja_kCSiF3pxjqwtvnAoJ71AB5WXx7Z8QSA5UaxFA2RAlwespyHQTzW9sUhg66gc3sIlwzqLlWTyyJzsfsuDUCc7wOhqAjx7AQlumCknw8eH5WQ8PjwJVUk0BX7n0XxH9ZlwUzKJrg5-Ord35aYPLdz89_lVWXT59S1w6_FuyZ1Ky11loZmEGf9yOPuUhaLBT-YCxPxE77Yi-mJpXZnOS_iNiM14SsdGT5dJgliUigEiOhaxz3ndUo-GcsZ1WPhxQZ9EojQlYW2GWLjLPoAnwNxegJMRceCHpAmeTQ6Z--imz-iAsLDp2tUQhYwldzpfDIGPzS05dgR0KGxsnv8csR9m70t1dQ6v4DmRiOylvE0Y6D4iaTnwgaaJS7fXztjwhk11pbSpvcdZVTLUkDIH2-oW5pE3QdcDqjrhfsyQGywrKsdPY8IjL7Rdj7_EhCd6zeNzc-hU7EDYa6S_DRI9ZKruCINtJh9QzU6wMzEbLbRszHJcAFN5LEBLUASUUphMcPt4DXg2ie_7NMrA7s6nGtpf7w-eIUSuOFaR7zSiaUwgm0bIbswRYUVkswqM9q4W4G_yvCTSRW-lIxYs_oqxeZ1F79IHdLmjuIDMhPdF6JtRvCKvvGcxgO6PmHWZNVImP6SX1jGjnjgMqJhM7BsjXDFjZiUQIQJJUOIqO2DLhHGr04sc7BSromjVQZu71JRc_Eq0s7ZP7La3edA9WaoYVeZIBwMkESyM9ZpFZiKEHidPRDZEmlPehy4kp7VsD_SdSGvl0Z2zxkdkWXVTMX7NZzKKns2UwwtrLIr9OkkXXg4x5pdtj7ayAlwlZFJOxnXFCDH0PqBsDmUMouHslbKpxbQwWCYjdfYak8fL7Pe1HSChafyPpXtT2sAgSpUOx44M6X5R5QbMPOa0DDbA7PRJDXgUU20jmPlDa8JHAKRZKDfM5SIV1kYOdqpJL6m0Qp1ph8Y4OE2FIN4EhsfWqjJC9Gl5nIT9zhnMeugHEmIbRdwNO0Ku-Tr64dYrsl-vNNhJS_eT2CBL3dqB9qZNv-zmQAQW5DHmNRaNUG-uLCFR9K6aMCZLF9jwGZqzFJPpjS5nO_8hzTPpTB60I4uDm3jl_8fiNlam5oGdnRoqHWD06BwsPvHJevPhtm4JE2m5XB8o8M51hxUOXEZ7O8NwLvysqCfW86USj3WPp_fPeIOIbAn9Guo1Vr-X9BfQl8sVnT1tnM7gpmydO8GCI6QWNZePGXVwLHcv_29rlKOCdDISbaJdMu9YLbK0_B91QPQkMm2mM_3IN0gR_xUkZxHWHQOgZGnOZ_rgr-h1SzNlaj6oNS6ODWcYSuGYM-nJ6-ruMR8-aXqaL7f1i3MUF24RL3kBMQAE8f4kPu2YoD74zDEzC8bu8QePsyJdHLxIMUsd28EvEHdbN3UEiweHvHGDwOoUw769PLIQOaw8odti8awOSZ-dT2qPrAeyXd2x2VUIndYJ59JXQJS7KslXig4h0XlMS-B0SyKoff1jdZD2-wUxdA4OkVGoin8bma1x4xugAThh2gySuUbOYjCJZtEvRhbpVX3FCFxVXsF8ufhTijihThsPYUyEa6EPjXEPMoqW65Pcl6Q0vVSmsqqpXBprZ4HvZ9OV1ESMg2KTuHz_cOf8ifrotzmV1JhD3yUXXpUoVzSWO2ft0m6fDcl22WPFw02vqrke6nKa5cWTM05Y62rpXEiTUG5HSEd4r0FZAp8Jc_N7k2y2vABCfIs0Yu3QMsSnFmhJ6A4kLDIgfu6qq8j43vxiH3NJaNL868F-m5ygqA-zlQ8zP1WptASVw9dQvtvqPH-JcxIuSF_Swauipx4p4BpcqMToPDMDmfYg84DPUVRttFDBoVC5EXi1H9GkOhVbKKEvEQHAqDxbPliOrW07IoF0x5eS-VG2nJ-GUoS8pnEGpawTW07HIMvDGIH974dTKuI4brfbrihgqiu30bzTzPvS0uYHlZJFecknD3Rw7wD7xLd-j5rtGhVYL3txsG1Zpb9dUGsad6QZkOSA1VmNx3v7QK0_b-18NSCgsxtvpkIi0iQrdATUXq-jdkA26oJPbaYRaBYuhHd1GpI_tNo1zjc6aBpUoo9ybeOC-yrlcjQvjGKHWYdvJ9v0eO2FasPPbQwlL2bLbwIf_4HfzNtW8KA-AxGgZFLnaEUIGIAfTCyTE2S7q4RAeJjoq1BD3ipFxoqG0xi1pbsIoyXi0CkG77m7v_xGJf00I_qawTLw4dezgwopCYk48lozhoXVrb7VR3Bog8xE4nRQTzElTPLdeWBR3vvCz2NMAaN76zMNLlvQ-vkuDXS2gyGkDCDV9eT5bKxE8yEDNJ72-DVWS-RITFL7uhpsAFxgeIGA3bp22UdDuZ9w-IbPSE-f13a-qvuQyW7X8YWEbzO4w-_pLSt3xXU2yXKADHLicf8U4qkBxRuU69QdXOvYOg8GDafvOTdZdyuQaqV6BCgp2P5w7q8lDxFLHEoPKil-Oq_coD7AylN0CPsnd2XnyaHSMZ8-Nbyb6s1pJV0RMPSj5Yo3YNnxKgNKoseOiQMBwHMAsQfnQX5ex3tKQGKhDG24tAhvfte1Tj49fJrcnLGyKcEA2DqM__LDJoW7loXnkm8JCsi5DHyxH6PJUaRbRkepc2o-qtRGCLXKXmdMQfK17-oDoflh32wuRv_RiUpEbeNMSvB1G5Kn4aqb0dbhf8OSePhjnGUwSdSYsLeV8dPHzOx83PRqAD2Gb0qrXgv1nmo3VSQqZuFdEfDR7QtZT-4ruDgm9rlgmY_fLphHDl4vhiGY5vapIUPVXiGoKOMLQN74On4aS44sIuWio7eWPraejseWH3Qs6Ac2v-KN2HPuM0LFqo4OTKK-oClwmnHqJXDKwvds4_VYWZL74jLZIZgr3rBKKdl85Su_TxJpFG3W3gl7sXPz5al88-R9YEQ5js2V4AmzouxCqEzjedvl5F2X3b0jyDLHzWjMBYCFdtK2BhlpTUoYyXB06iRluevZQ7lrDgup-AoP6w2Q9bGrczKJbWU2St_dMGdqRxCPVT01xlfK8HBch_0yi709GmYsx2xmLolHQsIGjrnYL0EyWlJF50COzaCykVmsSHFhdc_8vhr61-fKsBUWIDLwr0XsOly7ugs1fFVWTnbT7tSRUHVBONbAqhBXV62KJJxBuzsxpSiL3lzlQXjZ5LfiYE6yH5Ll9rSRo2NSfFt1-GolmTr8SGDdX_L3pbINkcXK1DFqr1IlF_y1NlhsBeSXdRkrieXL0-phFnrf4c6FRME0cdrC9lQtkWjhBPOLRUnE13oGSizmJkw6fVJYdnsISvqDsQrXQ_Bha8Tv5m74zKBMO5i6e-OGIGvdw9y8ZWrYiDQcwqmI9h0MYS-OCc7YNLi94uRPV6I7FDAqzOkidKmWCcI5RQB88lMtcYvkIlcFhfd7ByKTt1rxzeBjQm7xIdmKvkqaXSU09uax8g9ylvMvi4DkmnXmr05dR0iqYif6tid0yF4g6ltzsR36tRQiyGXZ5zEl8g9YPy0KaldMhgP1-EQ4AOiXHJuTMhG8ZrnI9HNanLXLc01heuckQEv7DS4W6fIwl7-0SChTyLa6FofZd4PGf_CoV4U13RK88NNDdtFSMY3V_EQ4hWuzih-pAxyx1tWL2fEe12SjZtp5GZ6x5kclynKALZ5ltDS7ei1OeC-TRYASPpvEITTE2EMnWytdGItJfpR0GlQR8ccgu-PwMjvVeeJpi-Ra-15aYbOAag4pOGtx_3zjpYeOgg3TC19NSHLA44FfyX-rH1KOD42d7eO57u--Veu9C2bFb4nqqs2JPgUEVPD4lK3m3vYwky4xbT7Vyi_f5gr02kBpTCApfVoMb_gZFMWGEEgc5u7Ag5QFCf2NB_xL7MvMJz-IFg-leB5xyl2qkn8VNM5MGMIMvqb1NChzepLh-bwkLNvg2rYHMCnaylYYDKpq2an76v1IY3oRIXNbEdurlmZISmCnF-Tf8ciapHHY0DV_ht9np2jZEL5YH_exmLY6P5CCkNAjjmvvpmQy3Qes4XqtOEF1ouLW2Pkl-DmchCJzdS5mKhqfPd4Z2a7rBizr8JjCJPyqNmyrx9RBuXKtkHc_4c4iwOQQ7fFnTz2SfXTJ0YDOl5LDVq5apKVucZDjtyusINAfXSDKwilEsUK1FkK8BICrdX4RBo-NAHulljwgrodCiSn_NHdV6hUVMaNpW8MOM_Gp5lpalQo5xKE6JVFyqpglVbAc-Y2Tp3ghHYDJMI0hSosFRefFqcYku84QZ5z3Ne2ayh1MxOEwE5t6Gw456sQpLRCswcXVkQjt2ipAgIo4uGc20_1OwXsIIswGPlHxLPAsYVs0jpbFtuk5IK-I3MoXBdnPOXJWGHFCXVCXncSqJY10Fs9KJVRz44sYPZ-lp8Xjwyph92GAn9rs-5kV3Ww9ylUXs_ulrXv-0HtRtngMSdb6LRoXSS3GnzYCF114fEDr4OLBY4O1T3X4N1jUBlGGnVRT7dcNaVx1c0xG5hXDzEYPuSSglJs_4brQD9yRqZYAU0yDhS7T_4VbmZKMYzv9KN7fCika9TruEKBKpVzus_vyiH8RJ90_NEVda6H9TK1wT3fAtmoqb-iWAkCebA_LtMUFkcLIVh1A3ATUEB9ANFaIBf0blAnxQWB5tgZGhgHp1iqlT01EGZsAJPCYt69jIz6JT0pSW75wCzzMl-zEkxh0ARCCEEfgO6wi4RC2wAUkRWCvsjtCordkOBPvF9fHiKlhIGqBdKCeZonpfKudm75WUYQVLq5Ant4yXvjIirwRnUcHwksIqs-bSGMG69nr-wan_N0EcG6DJzftb1Fl8PKuyNeKSe3u4-JZXXM7_UMPUqLMwsJIWIA6pI-1beum6I4AChHgK-S_uVyvG4x8iFbWh9dQPPolqD0jDu7zWpDEOMuZUP9gJ6ErsV0UWCx49kWonZHmTXrANozmxRMqwGY1FKhxffqPfi3tg6t8UZUz1PbsSC_0V3jqDsEqCCBfDZZHSaMdjH_2AH1l4vap9mpNkh6hQLZGtlhCuXyRpwQ-pTKm9fzbgKXDg9IuIrx5JtZcK8nFaSo1_du2C4wi2DvBWr9eG2eJja0ZKu8k3Ij3QHe65AlST8RavZuadkp-qTBQ45nodzZo5pekeQF-aAGzQMHKuJbl8tWzrVUJODAlua7lX938vh5pJwjGwqRiuZvdKzDMn2ojfTVJ2K9-LWv55r3OaCj5RXu9uUwyPPOLf_qmtZHprf656c-NkNnLkTuNT0y1Z-7mVsfQB3EJU-EPpHErmaXy4euvu611Hw5bhqBJqgOmqmRV0yc-aXfA7iEk5ivwjHq6m4KV1i-wlOE72epQafML254NREWfuUyZcfYvvsBGzZLF7uE3Azv6bK4VUJ4ZaZe-VpK0lGPafjV2q_otTRGc1mixJMaFGzMk1psItu8dpkc4wzvkw5Y8VVj7PEAr-wgthTXrdNz0WzVyCLfo2B9VhewQ5qFlGeLRe8i1xfP2CetC4fivOZbex4ZVolIBWNnCI8uEJEqE0RckyTG4am6GYx_eQDbAxvYp5TOejxI7o8C5I0McImY-H0xcQU8-X_AT8d7Z9x3x4VnW6Rcc4gA43gsGT6dcL6yJg2ODztMmmcUpQ3x4U8b20xriC_bPXzgmLixtKtZe0bJe9QDHDMCdirDOMiEgpI4tN4m-a0uBkotc_3IxFA_yzxyjIGawVF-llB-jOTrbJP_7exqMcv_IxUmtjXpplxQQcVt-9n_WW9-EWCT4iYdIGO5Ca8sE0Q6hpDIwk2egd6-S2y7eSkqf5_MhqDwxRLEQfh8Y06FxAvmkg3oxVIWlaIM8_OfW6IJf94KOspGnISmvJ3CujISz7btVasOv0TWtKiBJj3IsGL7G2d1Yt22MhyZlvESz_GsLjbEb0zCvUEHQgznp9GN3P0maGRqHNUe0mfk3ihoxx7EhYVeYFC4gG19aoODvlfLORw_XLwqcDZ7mWlJhLsdOseKqXkA3-PfWguJz4FR5o3O8M4WQc202frAS3CBeX7STfg6OkHKFF2CgfL9THOXJT5EX3ouLdiC_lEe-0Zxt-T3gdQzswxrqxoh6JlAP8OPeGRq4VBGaEqRAj2WXwhfbgJryH3Q9VcA_uEIvOD15j_KENougeJdWjCl-mbOpz84EswI0PE4TQ3H1kITkOe-PMzys1FDGxBSfJFx0O4wxDkhKikQ4QnlUkyEGE7xs24HMjs_VLt_nXx9FSjSeuRGCEUFQAhIgRe9ZlGRUXws1T17010VaPLfIfTAPJu8v0e5z3z09qeAPRj-VSt0ZI_HRYFAGOTff9AgzE5d9UEMTqcAdmU5fycBJa8Hcbge93hmII2vprnMyycGDiaN2A911djblcmuI9TYuEuJixW1ULCBR5n2K63YsQfnBVbItebcU8TbqMIu14oeu0xPEeZ4_WaQplfdfBab5ZBwUH0nsjWT0utORqDIbDU2m28MbSo64tA7SXWzjwR8HhS35PEBUYajn86GvMTaQChezNPdmnPWN_WXXbrMtg9xJtM8P4m-wH6mjTo5j-hffOFPivkI8PGiShi3_P-eOqZtFLhZj6HsnDm6xMeis9ktPeRcaXsR-lupIHED59KwptStbc3lM_cyuOMZkKb0khUkJt5YWDs4zPggRkrS9EHLqDbWEDoZb0VyBx6TREMWmaJvY1XJ4SINI1MQ084mCm1oqYDJoVe_ZPIvJtejU15Q4XK_vHTFFGu0A8ISTt4ToQnqg5n6I0Au1tcvp0n-95iCce8lnIgnd2r97GLb60xGsnnc3A4RZxkvMZ5piuK48WZecQ45CvdKbCSROZIC2T2K_BBqQzPq4L-KqSerh-zvGXKFqAu1C1-GvH3HZKxCccYdUVwuovwBRDGcNFwGtqChA2BAIlLIdkV6Jvk2AX0GP4OZlcAQtZJVanarA8190_42HZaAklO8hQ9p0FWAergI_e9nk00ldK_TfWm3JYgP0eQ8POfEp4ThVSaclb-70l1LUiOyxzi5Ad1s2CoFktIMtaVwQOhBACUy-MPi6eYzM15HIrws-Y2Vy9K166SD8AONdkIFmriPp8XaTqr_WY2VMKBlH4yb-2UsCp9Hc3bpledQdOsuEsC7YN96p4rdQXknTvY6XZ8DxBtuSP_MeqcHdcY3BQHoyfhbDwYLSjl1v3sTWJyOsEJNQrnmEejGZ_hrHnCM7GrCefVTLPUYabq5RRGMvoyBEla0XgzeN02YOSk1BK1BWWrShAO8EmJYownssw2QYnB-z6NsZbV3YDcConkGgmdLhCww7JTswOCk4r2p8cQkkcnZ-mVqAxE91IyRPby12xCcec7IRQ6XZuT70Z2ELLqPswefUvFdLcpXvIcB3Jh-qLmsH11AV4VHB_jx1SdBVZgqGWA2DBTdBDMkKJB4FBdp5N59oaG8pQcppZjUIkxnMbAEA_1RItubpLMz-_LEqBly50nMdResifMz-164pX6skVozqwv0DobKHkA2vvD79aZp3PjgKRDLX_hOa9i0q_aah_KTa7kPGLso6a0vc404dJcwraGOAyQ3oPDHEr10TdG5cvFREkvKlTGfpcDXi3fcIRZfTHo4FoGJ_gVuiwwx6UQpP2srM14uHBgTP72ZDJrTrFEd259vDuiqEXwIZMNaaAQtkkRGc3gv2M26aqezlfU2xiiVjpy82xW_cx-pt8_H9mKRYfZIK64M4iGBNgx7A8qTt-wJjyKN-_qwBeOvTH_dnmaalj233D9G-O2wn_sQuvWegA-khTR806Ozy9gnypMTX6k7NJ9Yb05AOpNMB7ZKW_6SfPjg1K3C4D-VzhVy23ChoMenMMwXGjqYayzvFCH8peEVoylVCDMK8epNrOhrrLG42PImCvv8VRvrhnENxqQCnvlyUvhY6AM9sOu-G69RAVhrT5F5iGDDr276QgSbvX9P7_KQsu5Sg9Mq9G6UFd_CHcOsy_F9qRz4W0CPuL0u-7RQTcmAptEK8Qs-sBkRXVoREyZypKZpB73wmVTmVO6N2RqJjbt8iJNifZ6ujJZVvvnNN313WH8JsQcTJA2kcB2dndubgcNVFhog4lQo_x6VG2vzEIvRChruQ9U6_X873awipSeF6CdSuCeUYsNiEUoh5oB81imQmpfOoWd4iEId3HBZw52J--sozun5NZBstXYPNRupGYmnmEBt0AS4EJI1aygvOZTTKZdyFKO1IQXRi6F7QrEQo1KKQzSZRCjxCcKrbqSdhgcr5HGLRZrWMC8tzWnXblLIYIePg74C0NBRrF5DbD0TCpK6g-Wv1GN1npzlgI176QB5c92Dn88qzyLOcg-7exGFuSytUkXvecpEnyk8_ruxsyH3CeVa7Sxr_4s-FoEsl_dtI01hX0lD5kJg_H8l5wNTm8zoGeUqzH5JtRoOLkEcjBv3yeA0GP0a_0shI93wnFi33I-_tCyTOEol54vRtHZl-opSHxCKNdyWZW3krdOPXANqexB_b0FODpyB1jSZI8zQaXIOTA2lOYa8VLgDREaWgxS9O62HMbcOIvK-BpshG97sKZda4mJPcsULu9946lM6xXNQiM_0u1T74JdqS-5D1tQH4REpiI3aQPG0ivlDpb4Lu-2xXGBWiOl3jgbi5RuCIxaFpWM4KURvR_D7m2RriT6HYd_JwGYT5BUxjtQJwRuQPBY5ZB5iynXCfE6UYwKSI6Wt-gM-9g97oTlvu_z8rqxUJuQ0TCgLhkhaGTaWjDjhB50M7XwMIRQ0vAT5FMrYq5p5-lQZmnxzm0XLqFBldAfs6kw43gTF7jWWUK76QH1HYLfWr602Y9gkuUJHICkdztJao-PS7CRhEU-g14QwlNY3mfPo7e3MQAYNlXtxfp4vr4UFntW0dN7qVXjJuL46-WMuw6XAlVoMBCwUlQW9qXpzHEcaPt5ZCPEPsf0oRsEAD1oycU2IhOXr_XdD8q66vHt6DGyxRfXPIY_yRqwLBcIiNVkW_QndwSUL8bcVRHHNSYbf-s-K8Kq5qe0ZKp1iMvxUVbtIfOUPjKTYW-ThxnkOsUTuydjexBE7Zr08_L_adpRdDRHaMCxn0NQcQOyVmqEVqVkS43Gf3lREHd80KNXJrecmYxIYGXq24DzOaHGqTeiI19lL7R195CuYOfrwtOc6NMx8q9_UH6P8cRF3s38F0zaDv9_4weXH5o76vqu60QbwMNnSrd66SN3f-FTeeI_97KcpGNyqDHFSSg336vI5kNyVV9LR1W6XiwGohvtn3kiHf8Y-b4ZzbnBbtpN80Ct3tlKy7XSMfTleUn1ZS7ePk1okRcU6fPMQwM9oGbWBZKxZ9uLtvfg3begh53bpQOCgPipSO-QcI_nnin1CmE5MoON8Tz83fxmzEiwROGB1vFfivOOtPrl53dmdGJA5Xl36oBSbqTJ7CMr-WV7Dam8E8aY7woCxZYrSc3PsSg5y8j6juGktCtyrHbCtE2dU7JwGo-pIOR1-HXzT7O-VgZOuyOq79sGOYOkXydo89kLY67VPL53LzuurGKXheX119zaQ4FhHmXfxDboZjnLykqDwqwShCVHgEmkj9sKmiFDTx7prrLv0xt2KgDa8WyaDUx7mF47ptNsdckvfXG6NJ5GAPrMSE6LWJeoF2K0bo3ORiKbYDloEuZl2yYpDR2Q_wsFq-vTPRgWTl8B7yFpuDlVTuUJ9dphAsY12JMZ6I62I8ydFJDVVhjolLLgw_UuH8NFnY-GuKRytm0A5qWY3q4BZ7dUe7WbGwSZoZqPc910KxUMt4WMaT7eAQpecxwzTrVz2-Q2kvTBOMgArerRkGENTIzib_a_0m4gs7MHNJt-_oHvdT_PYp3C-WANY9CkhGTWaEAKwJxEB0lv8B0cQ075v_vkYdbZBPGJsNbP7thfTExhQOhErXQZR-41GsaF2s0uHmikkXh7ZarGB81SFEAuy-Bqj9kT7fG-HgtRuKv5h2Nd8IZgORs22DshmKRwI56IPaFAaFSKzQqhTd0eyaCC6Q0E07qeEZb1VyBqCgq9p1SmJwSYpHXQuU132f3xy2yug2xscrpFe8mCO5_226VxbS03qcAyw4IYVNW8JAgO4JwfdF-fTo45xQ8QAoZZctcMY4tF2lyoY_kREhSaA-FAlBM6wqahMVzSQAuxqtRfqhjTCge-RjUXetwrPU6V2P16Cw4ZHioFNmPB0yM1Lml2EdFJJqMCkS6MsZnX8GNu3ZJKy1yayd8N_gRwKCNuEKzht-hy9IXycs9M4W01aieTIOVmuRGMkUkCXHjgpDB4w3LQSVhPFAVcxqB6oXNNyv2mQKhHG6If3QxcaXpLA1v-zoSO0BvjCIYDO5swJJvB3F2AObR84Bm8ZJf3_pH_0Hqq0yGzTEyTqU8zO64Y_I3kn7GRO2vRyN2Ho4duNhmSPjeFiZe9VHe3tbYL_OvU014Efu8Ib7gpaF2nGJgNY0G2kTwa_T4zVK7jeeX8mUEh8Iu_P_1QLX0A1mf41-H3aBI1_Zl6OGhgMiB9PBld-bfYQAZV3VUgN59C5oLL_PVLkeBeah0NtW4pZKkXe8efdIZnkDIFEzwtV4ckwVLcJxVV099PTeRiOQvSLeIPhdILBBjq7IKfNBKErmU2q4uEUNrzH5lsFkzY3Xem4Z0oTk4SdDdc4kzYMdAFAbBCAlhWvW-gDmqOo17c28pba5W5l5NtpZmxaICkKObffdeuboLT46kLZdWXW1jgtJBzvlGG5qfFZ2vpcs3-KMLl2MQWEawjwkT5Mo-34YTreXu8qFqKuAumL_gZP4WCXnOe7_-LL4ceIVT1-dpd3OSwrNYX2nsH2dNgsd8J36LIegHDj-JdPqcAehS8l88LvfLIsH-p2UyVbcoU8Dkd6JSnWnDPM3a235hH_0EAicvjpl7-PfTmYLL-xlqBZECFKSjBBJcGgkZxZLXXOddbOeAlbzjxXEEm1v-nhCTySNPOCuEtf7Pprht-2IR93s1JyNrLcDUy8PH5W_IWWMbJAEUkE36GCvnJVrTNiOjrAHaKCehCkHzXrrPBJUBaCj9VNvC1DnBTEaJLMe04LqspZJYn277aGZuNqgqxnh7XbeoYo-iCODZTHb7uqKSZ0xCh6gSPNfZIXi2SUghC0lXAat4xusW8a4jOXB0r68z0I7lPwL0HpD4KdxToMLgg0VMa6QFIR-6uRQUZj6d5TSF_2EJ5kDd8pGlnHJw_M9aXSchJz1hW7cGQiWs70fh6Q6-na_iRGNNyKEckX3mRF4i1akj6DMni8YN-U4NRJ9EIJ5CrCaq2lOOQC5IzhrP4llnnAyRGtuPvZ8B2Jk728_swnSBRDCTDNlJuY7I01JNHx58JDtvNqx7J-HkwmeJJB-OyMsMcqyB8VMFQ2j7TlD8EHVBMY7-1FqybbMMB_eVCTd8L5bh1az6Zrb8cYy6_AWr3ReFuWYZreDmZN79cRuBFh_kiJt_nXX0XMdJzP-kkJY3OH_IzRJ5tUrpjMLhfFAGOJjPntcSDw37lVASofrm8bVW3qGjCUQ8y3oKEyoyxi4o5ykE7DDPBBNIyON7-28FMiW5MT5AZWw-xKY-7_Z8ub-4riirGVDtRY4CeYc-9h83Cwl_3G8I7_gpYA1FWPHcO8bUVGt-S4TxrwQw8r_Yadr0dv9eqW0tnX3RAudXyeBboDygHMiRjWkE7mi7_K6Int-R9jV_shhIg11ywxwJEuX2H0kOIJQAPbe11m_3rrC0Nbyyzuej9LIOX9F4MQM9BhLvRcmXCfzjOcAa_hucZnSwa_WLOWcymnl1hRU2FdmNC7sn0rRWm3GDOxpBe65NEM_u0GhWgG0hRWLju-C998XNIUEkGKrjzrrBSy3Pm-INZRQ0LT1-BkcJrgooP9HTGGZlnqPEXywN2R1h1fQBhJeXAUIMxfRXnjqHkwY0Lf-lKkbL9be2z1vjIb79VVYQmgA8Lba4hT-qfsU5zDt831QqndX0gDZ8iRhuJifhWCGBwXts9L70X9khl_4-Q7WHu5aK6SoLKemHuEkJZdONW8f8HLqDBMhWUSWflwKA1ptj2kJBcwO_D83G-I0AgfBIf5xmFIGxoLkKi_1UG620p-KKpSwNcZ7FaJGXjlFbKalnxwGKgkOD0lKjYt21oMGNe5zN3JuccGXMdOVjrUaYdVu0FAGOIhj-GmTo92d-4dXp-7EYC_M0F4uMnXYTtpCwe_YG8LY5umYr84iV6N_fv3QtvouDgca3QKVnM05UbG9nvtQsIMTdR6S5hnLuUfvgJIhVCMmMd3xoesbUrnGUyd6jIn5lw9Pln_kOEFpKpZJ5oUNXXtMwCVZv0nH5ID4BK5n0XB4LkGOpn_PwSSzwLbkqAxG_sXqojjxEJxE5mHMJTJufDdLwsTXVmK98Q-gpH-Z1bd9mInFqqWiu7lZDP67pmPszMiuuH4WNqVjAKq8TY5OzPJ5ctjnZ0s0m5taYH6h8TcmJjmzp5Q3Ndoow32aVDO836YJYhtQRc4oDf6Emwm3cjxG4Pu_9-xiIcONeW5k5remoo1wahWP6nlqG3tmRNVyI0ukfLaim6jjnDqgtWaGuDzHVVnkQXjrgk78ICL-gqgBMYP85wPyWTXl93LSGLze7FLVy-oba38u21FRGein0d_-XRpf-FVnXT7B01po9ppz_i7hxCN719gUGR4TnylX-gisSSCXi63c3ZpUuSIYRLh_DY45dleYXuOGTacuSjnKN1AWrgreufVwaaOt6Ac_ygR_8dTy7E_5mFZ7FUxXcTujuxnVNOcyQnLmu8c81Bencrp6CoOPMc9pN0kzqS4ymaIc9RzYblXJgojb_IINH5QI6DXwf_gjiuz16wqCbCV53n85ZvQQk7yysuWHuCK7CdZVrZnFggomzzAFre3j_64Pntwt_Cw388-eNsgDKrMrKkhuLUy5O6hkjQ-4aHggAgvu3BvcoZVq1HzNj_rfxHBBDtyUxf0GPffq3UqPZyR4k_7IZUErykg83lzZRj1psl-mJuHtYpCusEuD18KevkDIuiP7KKDys1Us2p9vZ-Zre3BMd7amFgJD_lhx2A5ti5HinDgj032ngn_SZcSu-YUIuPvL3lGJutUn8QvZ4F5v7pI4snNjGcskPlmyBfkeRuUoKhDCpb5hitFvORZ9-iSGzEhOtLeYAuTDYut0C9nSILU4I13Nhn6320CD0nPipCnE409YeQj2egU4jQ8el2wPHpFZyLTK8KyOqTvrLa75Qmyhzdldbtk2bojYU1GfdgcCtZqHS8S6-6dVhSlCAslkzDjWTWxbken_9zCilQS0AwVExyv7gieiTqrSGNhhECGcfZUhzSSB-vYz8F1iRKBryfUf_wx52LeXVtJri-bVQrHKFyg7Cb-sC5-nzptDa6g7v_2XqYUNTMZ-RPc7i15HghBNcwtEgsYr2y8gBSvWljtk9kk8PvO7ddSPBxB36Ubr1QzNadv1AwkqjOSDho7HnBmqLNp-u0KpPx72_qLTfHkrLr7vHHiy-WXyYolYbeFDc1CTVL8_hF6jaFXTdcmhvyPqXTRQKrzNkPNM3halPmCEhUoEzb_4Dxirj_jgZpmcMdQyryyVerjrAOiUX0mYr4kbCFz04Sp4bxNO87EJzPJwiFTqgo_tIWeNhCAh8fmodsWywGAj1Vfe390usvsJHu3OxZvhxcu8UNT-4k_aAT_SaKvaTdHkx3sOYrkUPtyf2EjbyORqPs-dC3c6LEJbILodP7ewq0oaJ1X9nCSYuJ5J-f8gHl1SH6u2cTZnYqMdYWyiEcvP1qPubdyrtuBWbMJ0xdSZHfSWc0r1_0xSMov0_NNvQm4Cwbp0ci2M3tet2x3V7qzEPLwDQKYdIHlARHgEFn1CNRwzkumAVc-ZNpaYelBupmd95NhiZb2dOKB3lOzLOwVHuTgMboWUjriVJvpqw0MliQImkn-SC9CYZ82ZEFRMfwm-qOcJc6ImNPdzhX-Z6PDyRK5VR_MJCz9abw6_gY3W6QZdDrKG5zgYD8nV7rAYApFqelENDDkB1hOOtr7Vfr4921w3TQ0yk9YO3rJ-L55bu6hPSMUgeXLctTFetD4Ti4pBsieTDrqMfZdLgTOOHcjNLt06Ce2aotCWwhU2TqUDhnT2R-_NqSsrHWDuexEv6bEAMV1po7O6Z6QIA2VcIStAeHNXxT8BtfDi3nJrt2CSIMHOfsaHjJT8gJ_4pJ8FmoEp5oVeHj3hEXb2CEGaocuTRWUFmrWavWcOJVPRuUV4u5toD4H57csujIYnOmhiw0yzOVzsuLuNgsVA65pyZYomiPrXCw5KoQCFb2uWTOAJWyuitJopBRdA3Zp0S6jSsP-cqFkdjLauB2qvhNaDMrfROcz8ZmKnLtPnSNA0T-vDg_4ABBF51dS-i7P9-Sk0m6i7Y4pSfG87WPMbo9hqhRk2f6TwMC9Q8fnnlAdzEAfBWXnIXiLNHUA1koUy5ZG2v8Z8OkrwjsIWPLsk6X3jk28onSPbixgl93CJJXXlmnJ4MCB63Nr-5i8kJNBVdOtkcrFgPtavqY-DZ4mV-sKeG4yh92GHjxVeDU2VYddTLh0WxSZKqIdb9Zxk9iyBNqNdez7WBHAPV9horjQe7JcGpRTN_ESNOGiA5OxWpc_kGCk7xRr2MfHlr3K_SsrdLP2ednSrGzAkcqz2xadmKAas8cHLi5OmRPH5_qgN5tO7fPF9NZMrUF1baWl0YDKJzNRlkdXrmx6xBxfqw1g6bLXH6BHuS5oQm5yKz00DJ9cayIuHOnRCMO2P1MSlQuKC7CRAwhfe2sisrR7MhSCqgiwlobg6pCzeENHlnVmJJLvy8DHIea4hYrCvfFDf7jHgvRZ6oOdonNp5kAFajqSWMWxHVTTt5TMLjKefy5IOvpe7HnxABvRy8bzXNQWGdGQuQkfY_EG-_DRHQ5oBoQl0iBbt9JeZWCeCWzGC4gXoGBea0JQMjDu1Y9ylrPdmqQkxIKJpmQxMz3QcOEtShXAvescIfRiDX00h6jrxuqu2DbMOaaCdKFjG7oHsfq4GF1SjV1_EJGszaNKcohi1smF_hyzN96CdNjU3fjAJ96AWG9oG9BJLA3U37NFMepSgkN0YFUphzilS-kGATQOf9KZ8KbWx16ThlxYXbVM_Du7WuAVZZjjAenOyWjKFPwNH5ywomvDYnFEZrPkuThZ_snkV9t8HCgQhMOPgdGAJOcwhfYY3ILqzCuNaWeDIWB51-QrPbDC7vnouuy2gGYn6atJ8_RcMcutdrj3BdBVGcBSxNZ65hImetCRKKqo6CjSDJJM-WJ4TSNGOn8V5E5LnV11mV4Jl_9DOONlKR8jZBnoUMbpsQhcwTENbScVcBGdCA4w_qROY9zrLYnW051Eqvd-ymC8QEBPRe46ZfYlE1jV4qoGyAgMTyhBskQCj_mWEAkuWNZJhQJE17XqC8s6OVqlwuEEG5vz-abjcTAjZJCY5gmMXH1aO4LTzl45lfho_AayKa3PdOhw9vm1KtFuolscfF6EldKnDMh45XCYKnbcWBg_Bg3k4g3Sm_Lopl61DDfKosKqYO4ZhVknn8pA3zILNQB9CMdTn-hBjXLeUdpd-UkYV84KWipBGycx7n3JyWk5GQ7Cbce43v_45j3h9RCA78GeJa4CYcaFcWRhTJrQ0IiTkgDNIE9I5u_LOs1JrF8-3iKjc1dJC0ZqNHyOGDSSD2fuouD5QlAQO1mz202I6_7PT47TUXKjIlS_BgQ7ZENxhx64OYivCPYvRjFEMbHjyg1adzxBCriR9pgEmnRWchXNGhvg0_Y5QGEbST6GVJHnpnIy-TC0FcHuLA4Ko69INN9UfB7oEBmSkAeqkI1sqN_F3PP8DIdpAhv8OE_qAMOM5cwi1_sFrghZze5uMSfEp3jDp494gNkJtCj19UtKPySBcydYb4FDatwOrDgxWTu-JPPCazCfgCHbQ9Wy67MhmAh7L9tTAxXr9RmG_37PYugVXcRsoueyz56eIolD_UtvDDGx9d6M7DOkxuWP2rLR06L3T7zzYmcZTYsvPk_L1W-z8btUu9hJhtEa6ktbNjkeksZV94ebdzG2NN2DNB4HT3G3JuY6c4Q3AyZuuqj1LnWyIPAkw6gzi5wCzQHLCuMrLBDHAAFV9Bbo3YG8Nuoy4igsUJrmKpU4drtA8-zQbKa07BC_eG30eZLw_VbBiSXuZnfQS4hE0X1apyL0Lrwy8XEzOXIRbBfV_ugx8dpQPnc-sOdlHYq3T1gH_Y_GzJQvVCaTO-qlcK5mVpedW94sCkNJBSm_UoO5PFpg28mzq1SZXt-LgTUPoPZSXMcwucFGgNuz2l63JHDgsmNJ4hWUh1NFTlpNt3zeyNrGZd43ksziuN50jL3VFL9KRlcV7EMTiI6M03zf7SN_ZVbVHboCxEYJZb0RJBZhjULxQZNq5fVxZd-6bsNXaTCemfNDNtQOpE57Kq9tOkbu6ybCtiIp2iZwzGHk5rGehW5_Y4Hzd0wJP7eyrEAYP_oYQSgZR0TlJqxOiiOqHPPPe6JX7obU5RTeKxBdU02ESyDWPy5MycUC78Lptahb68R2lN3F-GMXg4sl6l7nRHRKvD_kk5Dfn8POz0jC72tOKLYQ4JeixU1YH5l3vju1KM65EOZaOfOyZySe0kORSz8i2e2-DmIpRKOJwMfVIML-wXCENGklKmUvDs48TSKDTEYSO1lOh64wc88KgcpE-73CNaN4sqiSic0iMl69xunzgnQL2HVKRInWvfg5H6Yg6nVmtw0RX0aEq1jYf9vslmqvuHptafk0cwf-wtEKLgdrJVne8qbMHMn_OkCJo6-Fo_DnZTzWwQQx3cSLSzXft9Amv7uJl1Zcg3FBRhgtZWP3Py0mVips4MvvzknLkiplgWZgmMJArUlJ2NtXNTs5_uA5TNvdQC4YrraYuMu5lgtoV5-l_K3lwSr9AnuADql6QbJIeYVfeWBtj8aVw1paqxiaK0T6600WGay-qSe1VHntoRzU9VFDKOJmdbOO_tDo7gADT9_C3vK_wkEQWMM_aQ3Cz7ZvLfwv9wh9oQ7xdY9mcTmCH9cfPC99MD367CBR8N3jnRxJ6eFWy6Kn7k7Q14xSH-pKQmqz-fLEw_aMVJINTItLSAHW7N9FxyMyw_IAd_g1y1pVh3-cHAZXLRABmi6bnw7yiGUPGECJ-Jzd3zyexAaflTeaZOW-LcBNMF6amY-IUaKkebuzqfu-sFDJSdpp4GlVCI7PzEg-hM8Ws2sl4Ln04oGo_-S6Cm42a1Nap8wN38TIKPEzy8A9XB88bM1z7WmBgzJsySP9GofwD_aaSGhHujZ0IwUhdJ9AOe4e5jzSfOofr0CToi4nVZHzrtqgYZSFWKGrGxrs5h0oDVJrW8SHPGn20go-PF1qXAMC2Rhm-6sjvKdefQoKQJL4tMfh9KXKDujzkZDJmwea3sVcllOMSFLaCFTvzsF_li6j372AImQyzsfaAEVwy7swOFrbz2R4BRskM0bKSw7GDp0d7dXli3fPW7mFRZ0rK64R5mJZ01HgU-i2ZM8F5J0Q1sq2RnJpF4paEVK_2Au0bLOIwWEx_PKh5XWhAhhOt0g0CHjsT5Ecj5smFCPwrjPIFpF3q7wF4E9cAKp7MTVwdQpks30UW2OaFAelwinAU788fXoTuBsZzrtF_oLxCAH3CLauKLfRPuTTS0KYXmmIWUB5O3Ahq1EIoCZ-e6ru7BUfYpppIso1zAOztf5W7Gn3qJQ4OG5d-kLsGTN7iqjiRYVIpDLTBDxNvlEFit8eYYfzvQDS-Af9reyJkIyW-QYC2kawi0IlRItzvpWKv50lq_23rzHr6OMBR5R254vfJ7LfOggXdMedBxCJhnPDqEO3qy-3SMO-MhJpZbLSgM1xiuwkdb_lmEpIeWXuIElZzrqHRT19aE6oOJX4I1E0YtWOdto_q2aBnM2CdIVTd_Ca5VL25Rm5vWqB0R7DyXcqUCrKxkotq8rl4-Dd-5MPBHL2LLvaUBQvBA52SifepwI3R1eit4gg2Ai5VKVOKe7629cCqerrWoAs6haxGrAPb7ejAS1AaqvJt80ZBOVCLUwoN8mwS0CJQnY2-OkuS3zYX_HDBjrgyFkgNJvmIdstI7uYovnHqLlA62h3XJqgFrUr6hH9ryCA7IzCoGcEzlkp_cSuVf4OTYpOeKxoENglfT-B3yg482bbvgis2_DTftr3akccbQXCuODtG3YFdtXbMFEStJVDO8D_4Ot39hZB97VdeIn58J6N2qOOZ6c0nFyQbCIvDH0_-ZzrFGBYp0HsONqNnpL_A7ZLb04zkG3itT6hFldgyMecUUdLXWl6qzDF7rDdQgVLM3kTfaQHy6IK4r3gv8tRUmqHXI8GyBiv04jF8vt_XvYTddoUfPh1d7OhDexCWEMJkhtTDKVL7qnW5__pqJLqa8TeFIXSZQWLSsF40GtTj7r6Z__rNnePYhE8k103QAZyzTrz4C45jrSCuPht20YSTrWHSBRhu7B8TR55c3eldl6yPbPAKjWmEx8T9hQv4lZM9m2_HDU_kY4r9Scz3zLpp10E-Hkzx3c2SpUI1b0THHZdvSDXjh3g5HFe6QUNSk9_iaPn4sp5QDWa6tWbFOAviVtPyhUOyDNG3lsUEExwogbKJZqkf8TpNVZQOG0rnq-Peng47H18ncC6OJ08XnIS3AMW9V3zbLb5HbbAzV7esg4i3VSuYtagwdAHDxA-c1wgdM4yLZuTGtb3HFnq8_rrx_qZxc1V6vB6Z5McNSVc5D3KXzCdpYF7XhjzBoqFvK7nTzfyGxqe1AKGRlttTtR8Ne2MeaAluDSB4d0gq7r74zUZv2x7GckHswfmRp6ppGJgcDnqSNjyXe88v8V_2cBVi8XGvgetKgmSqDVRdNTFBCcHv6ujrxNDwf4n4pIMDl-QXvpmZcuo5kfbdUjLHNPTCwMRTEwZENP5Ul6xxQm0Sm9UGVV-Xy7d97TNmSWpdhyGMEWETEoo_slnJx5XeHS8jMP2sF3gDCU55QeABchT6YKT__wN_NKQzoUxJWVEai9K4GL000C-L1qeoqjJKCfrm0TxXIZArt7wDhZ3bFC3MrnDkuTRkxa_CmnAEXV5co6tR7v2OIwXQZ46934C3iVTX4Al42VGVmuKBNgGipaVzZSIDPV0neVJcGXHixhA0CUBpogKG4p7hrT4K2mS288Le9Fb9c-g37C-zj83uxiVNbCKEInaa0kYtbYWmhxw14lalu5J3qc1sopTBvN0GGD09P6DhQDKcG7eW10CFJ4-_oYm0OlcWlKeQG88DexoPnzE5wk5dmRw4sd51_0ErHitdI6coHM7DiAo6tb0ghKQdZzdMVjWmyvyaUhDXvfvVDTxaqI96M-Y_CAZIoELm-3be1kfiJccEk9Tehah3VKwI4lYDNRPZkDWHq-ss4q8nax4AJF3GXE8INvWbZqJX85Yp_85azBPON5Arld4mFbhrE1xAaNRHqB45PDa6z4oBaQJxmGsyWwyJ0-8cUM7FUsgU754zTBr8MkS7QNWBBrvUdKt6Kzvz8pcpylBGYqJ4m-0bZx6pBwH4DdW851lFR-TD6_PK2UeWNXlKBqCgz_GQ9kPhdRHjmAI_U-Ep10dKjOfGzKZSqeaJRrs8HTYUIKQ4AKevbJHhat-Cdj_jy4RSskCqpkSkrN_tBrYTtQNWEgemJtOYSwqnGJaIpHyzubBCBhpBxovKdaEhieJrd41aHQy2ISFZNowoe_0EEJcaEd2zlcmKRwICWnMXDLr9eG3rxrGfQtkZYVysvi57iRrJ8og-AL_w3L73kP1ombGSAlvo2bp_VrCguNjiCTUwDkZwMiDeeJ_f2JeoBW-QvXk1yXIZFGLGi1PBMzM3ecqFipwhKnsfv7292gEladJUJrSZ40pSBGJQnWF1CvHU4CDQ7Mor6MqPTlQ0NlxV5tw-WAACA6Nke_cCUjzKjRZJTNyJUpBuk0xcfsppzqhqGAY9TTJCEOswVreBIz--boBBXnWoTtBPOjuQMqJZ2X4FeT6-Vx-4KL07hFpADghR4y0VBzmTuboZ2Fepyyj0x0SQdQFF1YTclGD8QdAwO7Y21IxxvSD_KBV6sD9jGudzHVdgisr4ztBd7PTz2ivB9BewNApAI852PLF9dcEZAPxijZQcT99POquOqbybe2l-t0YzJWrhzSmBXGKqnFy75svce83qWrUhRUAfrheT-rnU0UN--xT74MnlL89fOa7a1E9Uj7PXtPjeQQdhJTCgu072-Q3lSyfZkXEuj6ldDrmGDprmfYO5fR68Fla3DasZLrSQXLIQdKBUhvP5j8_vz1-57jCnvnq6Q0qFj9nOVfY683PQ1CcL5OJePblScG98MDhUobd-_AalOVj4Shtt5ijOzDqqS9CKwS0Izssbb4k0fuAytV96DPtgHqyDwmeGnONLmMWA8_QRHQP7k5c4bOFkkvxyaTWK2-8-HUTqZ4wcwj-31TVvd5MMGo08Z0mberjE6Zhycsl0W9nTSDBY5DmjLQFymx6uASGC3CnxN1oGnkaoyGaNRXyOzs3N7LKttFro4JVWfjuDLhUUxCwgLY30H45ThMpCX2u15iLTDBJWWSJYUOxzQjLC0_5U-Qgn6L2BPDzJx8HG6ehiUfy1I_c6rMbOUgUYs2zwcv1gOQZ0gZOOeRATGpJ9UuntU0GaBSlvPhtXc_zU2xZ0QK7VpZf9oSMhAxznE1-tz0PxxMuvNqDUdt1AW8aITW-6W4dXwcy5P3aBinQvx_YwG_zjQXPLrMNTRJpT79oinCtqUP82C0T18Kfjew-Q7rh0nVjWoXT9nhF1ToxoPCojrgJNoAs-bEeXOv50TUywhJ3kM-Y2cSR0OadVP5ntjbWOQRMNSj5kZPDBgGIluVVOmJrTfAN7PayvZM9o-1d6Rg9V9afE8Jlr5RJSFhpz1YiG0jQ5lrAvTNU5bNICmF86YWZCXSq9uMeyZ6fognjih2k5FqzobKE_9d83bnTa1C66B9PfUgGolsjKSn_7rvIm--8fBl1IcA6TgFAN_QoQFOq6Iz2cqvshmRA5UceQHWCYvc0ACxdBAMJ6btoL4m1yq1XFoZX5BMXKhKE7V8zJkJeMQG8Ip6QZKw5UW-v8Hi45GbHoE__LZgecqMi25WFAF4cyX-C6MrO1tqYWG5tx5NTcZFMGTvgqJA3_x3ZOUbtwohyujcJkCSwz0xSG7XAABmqGcFWfyVHGwRLOyBufDe2UV0wE0OzJpa4vtWdnpGVru9zswKymM7c0_6wzr0DgasWPVyL3Jl3kPww3jlDK9J9CiDZG2eLx0p6_T2ZW4vX6QF-r57hN7AOjMRb42GoULAmbyp9LR1_Rg4RczJQk9bUgd9N01uu9WtiuPDgmpAo6aA7fDorxlBLLzXGvpezwNxadmsLm0mLWihvq0F4LZY-yIztc74FMyBWkTsj0L4JRXIYK1nsYcvTJWBrAIutPcR6Kef0ilhOCox1P0ITl_nmAiSb7beuYFi4I2N5XFWnfYgy6J07WSw6-8EuIyfyLNeF4r4nLavZyBn4YNLJRjZA9NIsylKhYzdJsBMn9vuvARPY7kZlOdkQIP3mHy60vOIf8lZ7Id4t-MVRtrQHRNes5yzPK34SKyJfNqwww349tFMZpjcxs2sSWNhf0iNuV71FrWEqwyQ-9xh5Aj37qDzp6UXIP7KWl_ml4DpCB_BsMinskLQX201TTR7KiT2pMK-uyjygckSFb7NStRXFI-wrPqVC1hLXzKI7vSWccGrXaR-ftIhR92T-PjbaUk2D3o4CBVkmEUmT2bOddPOv2xrmfmspDlqCM0DPvCv-YBCwrtUq8w3xvLSGIuLvQQnlYSo1XhRt7l1a2NVvJR6Nunb2WhgAYQtNY9mLbTZMFFyF3uLyr2xKvwzciB4yomkYpk-esYN5ZhfJsUaH35WzMyncziARG_eEu9l-i2jr4Zb_ME8zKrD5fgMj8BzWWoEWRkH-qGKyGBod8rocYjRL7LqPffsHdWduS_B_Ab9_LdnaFLsxc2-0s1teqHCA7eQZb77D6m3xvCWVPmAlZRIfkxWehJ7IWbxO_XbLaVe8V7-wMMOn0e2X5tt-eA4N2gAmVU9h4GQsieY_V3_vuRctskxld7nZSLoTUV-DDUgJdxad7p-uSl3-LRIo5TII_9jP4tskmnc3mVGmOddau-ErwvyB0513Y-JSuMQCTy3MHwVNT7lMzyMNkMjc6s8E0nFDUZ2lM0Bnb8rnAEiLUEExLy-ZyxjeWaCDq7qACox-Azssx7LFBXEz8_f5DLPkSVwx0pVyF_rx6zxMTJgS2ty-T2nHyWpDGIgji0-vZQs-2zy2Opud587IrWcXpeYLeK5XGH0tpT8OtE-gkzGHfdKOySmPXhEtP9V6_Ezb2fGDer9CcNqcUug1_PC1vYgCT6RoGAtVJu1TCGDMxnGIdwS3Zfz0vpDv02me7MAS5hwz6zbuo4NDZTEhXjQTWKioRRqEOH_cALiOu2Y6Obv7z7zNBEWNnOrIpP4vu6CvFm0Z8mTtvH7ct0LAdAGQ8zFv8P6FQ9X3sleI4Y9F379fAaO1pluhw9fBwLHyi65e27trICV5NkHayBAqOb_kWvB6W0pmhts0pPL8oMjYG--wULbtWoGtVXivKRgHiPKnSNcAeWVnQVfru4DFh0AjOgr8PQ8x6WMJmesRMEuqi6_2OSxgc-p85Py0ALHcS2JYFSN3YEHOKuH3AbA31RMoNlgRC6Fm9Og_ls7CUcaehQBdxaKE3KI28D5N6o4TDdkpWvX0qE40LGG-gnfLXsfRAEyt4fAHpl9xYgRsVaDP6ux3vYYr-pF3FCppUecW0V_vPpOR5m4A4ychEyuuo7PBBJpxsRe3Pg5f1G_5CeyUVldwDtX1u46ybCdJF6jWJD_SkqvA2CVjXM5MqRfWAZYJJEyTSTsVP3WNWIJjJWpstLu9UldXmXZEsugRfbETFoHlD50c-E5Sl9rNX_PHolPXyIlhLBdKvo0zMvFMi8WmlTtJhJiU7T6caxxHLWFfr8kUiQZvoAMZGX6XdqRiH761p3F88by3mNd5ldm7FpwkvL5vsdThvDIatWQNTFCwp7mtTiCeWgnHOk_59vRn01xzLOM_65DRiQ6ItGiJGHYDO1_kpl3WhU1-h-xueHgr0OMkZ8ZsSA_wtHHxZQWlblhybi5n0dee4XsFNNwQDRx1-qNsl1MtglGVrJGcu_0f6WokBHxKPnDU3KBqu__ekqptwqxXc7w4mxLs0OEP1Wz1_cv-ipnk-r_iiHDihNxxBF9bXDVXBpyQXHEsmfMWwfAPNWpJjXdPpC_ltIsqcvbAlw6qbQ8Op0CLYakQrFpkJFU7axZk9OkNyCMUPwVlg2_ZnlLlCr2yTL3cacSVTfopvYHDZS-AErjtUK6svgYsiaobYWNe5fIqUuJNNz3OsMwldnC35HYhrYNJyionK_BZZ5-7jot_orL3Y15wKLYLJxXYhAww8teu_a17LHJp9EwiwnlAHQRQ7qblqlQREuoDzTy-xlv_iJshPqNjsUGuvLF2buONH7oa2CitXoKpQcmqkxc9mQXpAAIdG_QBVEzkAQAyXSrGJmqFG4D5Z86VpkqzoI6u9GhmT0lOTnxsRiV5Eo7_ts1mIcauZCtpdRT3cyL6nQw_UaAJMd-gP1u3URziNRqLlX765SQ8lEdpK3I1ar6u0Bu6jDms9LCHVrtDez2c7V5JzwMaChmj8cUNkz3Gc7ByGdZ4ghMkwt2VTYCbMxC2XY7Jy0evxcR0SsPZ9ffSnwzXa3rC2qbIvnPVnXDEllS54a6aEan0qB8LyKY3503FF6xnrGrlG6eRM0ygnWPEbNRs5GIQAMsiukJZpr5Z2haSn9LMN1TWyRDub5IPnP4Gj9pQuU_9toiivaIrVsOSnW6Oe9dVqLpXcRy3uWejOrxg0qGGBO-4gb8dkAWm3gfwRVyOcjVbngWo74ewnUA-Rc71GMbDXe68Swv-d0jJLNvQE6aC7ScgjGAmjhJ15zFpvb9-p4J3pA7fJGE3D2XhBADWUnYRtxqD0DCJOjsAT8pvrMNtG6m_CGjFPzQjQ_6bT9x7ZrxbOP8WIMJ3g3fhnWadULL2fu-HMZ_us_wliRQYyvH3WEtZVDNkz2Ndxu2NQcWmIXdq_mon7M0Mki4pLjZL7qkWtgsRkitAmgp6ruV44MfWnMEsHVvNpOFFcboCqk6rHcSHYNVXzT77w8_GIOIGIwf_ihZJqd30S9hOrcv_CT56Z1y4Jx8p7_Rh_SFBb6NlLrWCGfGFLA5LK249XZz43UcSri6J-rWJW5lijzms9kgpFnPXVIAVlJLDklfpHAh9kYPMnDbhZBMrKSYj2pZ5dMUaX3bZWbti3lEvbks0mpHdeqeqy9ILv6ZSA58gfQCUu3qj8EC6aJS4J0-ecynjvPkTV43ggAxA5C-w4eJYJRgksCuoGsPaepWhcd7nla_LqCK1O4WgU7r98cJKWAVNSAVtQ30dBHTtFuHl0QJ-rHWchSvQowFDcOhSiRRHGN3n5jugnYDbWMX7a-h-v3dyS5YCLK2PRMAjv3ugTamXKrW6tpv8t-mZgoMAmlm_bjVBkV91mg7yXlB0i69qr9ISS3yPs_ZSZuOi73jWkARN_2qhJvNS_sYd6sXw9jCDtz4UEE7qTujjAF0hEZsxOGSTnkIMnRvwiMz3CMjZb2mKHxd9e1Vrel0pV4rEgRMRHuGIg1_-MJEc_8SZHTakMfQiHnIu2gWJ_OugvzPV64ZATrYHCvopf11ZVaW_SJUyJhk5QWyBJm1WgCpbGU_tw0TlAEIE3apbxT339TFVXLohYSoKgwd00of0ICtOYlfJNBRI4Zc7n0F92V2FCI-g19uhqGLyiDfwpFVH5eQWA7mtw-cxTdxEgj__Zt-WCQ0phTIFz33zrCEW7FFuh9rSWWJj93QYxuqhSFFkNR4Sxi0zYCF0An6h9b-L8Gyaa3S7p3a580u9U169V-qJSEZk6UH1gf1-wjqK2qB72ezuDHMnBG6CbboV0VW3cUGjgV3XHdx1M4sw17IQJsc09K-mOun354Iu4LhbyREkn1qrPrCgVAV314HJuER4IZClbEZE49OPSA2oTTjxbpwBRjABXG6vonayHaac6LYWwbtq95ZAqh0afcu6vIqnhGrXzTamVjH2f1S6KW4ogLub4JqL3VSSfG33jpw_wsAQt-wVuKRmfGUwq_mk_U1KBUwyKf2EqOLGvTTzrdHdFlUDe5-gFiNRn05mPLHG-uSWkCd4qf9TwQbuCMoxRUH92JvSY-M2XD6oZ9LGool6eiVK1sYD6iRiGPJO1zJNiKOXofaHso04FW_n4Ka5UVkKnZEGpayhvb-lJRKWmrCPEL_vbry601O5tuc9IuRGGWGyoh5W1cazWSteXeZfyoRfBC_fuEA_zhjYaeVgkUxXU2aNor5bW_xjnNIRgCcxGBbAMjbHt9g0KXjSzoxNRdlq__VwothV3cwLaTL7wQdSXmc81NY4m3Rs5qVzWXq9yoPZGaY0IXICqD3CcwqRfINV3fcB6uUrZrC5PpHunkRj8P7RNx9QF2-r90YosCQ8D5CmIbVMcTOj1O_4sPFvMdnYm_dlSPkrZwh0wHxsVnj-NXJUEg-fC-8XZ-0uxlX9UaoM7pacr6RImcQGKPNIQeMU-HbFP9RIyFLotOoUt6lf5UbiiJnueUrCIu8_43erQZOXMqCo9ldEMeVC0iNa9TleotVU0JVhfUimn7ZG5rVGSSLBhfP_w50_vgJKBfM7l8cMYS6SsCeRGsK59XvxBXegw6yQymNKid8Dqq67xk6ASKOWvrGlpmHaE2tZbHAXbhj8-x-1HUbzSWJNAbECRB4TgRp-5yM67gy5lcR8qmhaZiY-BFj8RQKZOkNpPmi_e2Cz2P1LslhHi5RoGCSaPJtHRk3TeHOIRR0sI9HUX_y1hr-EkeaijOOMlhG3Ygzvy8faV6GEahhm7eNmLg4A246EIFthExih15OEuWcLo5O_jO6GzIZBT_KdZl2sPnVa7kXn83nzuG7gAw-vP9ixYx-ItUaNHDF2SNYcARQUhmbGt2uNzaS_ozGCne2tW2Ex-eqR_If2rEikSInMf88RpXI0sJO4etz6u7JdRgaDyvLg7XnMppImLsz8KyeZUG1Y1FFBiku7stmR0HmrVuWjEtjd0m8hQSgBPPAWDursqzqYPG0IV8LbZBtbvuJrlH9zwIahg4nCFayaXayYJPAv8iRsbXu1xKcEx2EDx7G6bvsQw0Oq7cR-SNyHf8sMzGuae2KOjAV9Mqa0vgrhJfYEvY_MdcdW17u99KITCa7rQvGShMBuYpf-hG5AxbVXbCglpEJtwMmfeZ4qu7jyEKeTw4FE3phyAu1mXS-5HSuln_Ml-Qlh9LVzOAtH85u0_WyyOn_4WiUXe9IZD33PVeTuTEaPfmS3yS35T4rXzUj0pO0uO43iowKhTUkk2gWVxB7Ngh9bxzGy2HEtddt4CMnlboP4av-bwbQhLav9kmWxZxjbgUrVj2wQ_eodpCumW8WEY_H_R6F2PhxDpqw8MQzkeTABXSaWC4XcE_v9xgyCsHJZQxI11BMVUbg-cxpUKDtzLVKgCLRkfweqwJQdsUBfocE7v6mIGQ-ViLUPrg-XULYXaDRbA8N9hvsoB1kjgBMSGda9WFeWikC-_NriRuzRGAe9Z4p_9xRhtmiyXosA8RIw5Y8ez-AF-aTVGZICFLOP2lQ1Uhe21YkjxP0r6yNp_2DqlbpZJAKcf3vUhmbW8HgGob0MJh9jpZ10m_LXk8sKQw6RGMuJWyM4PLaQFDTupXSnFnMwzI7-Ye9mG3YdsRtxO2En4igZgmiCMCSBZQAnUAi3maIqCY-5j_phdB_dr1_OweIfa4uBK9HBfl49Z9V7Mfw9fGMff9GJQD40njwupJ8ULn_wTRtML7FjTcPs57hCgpGKIjl6ohAClEHTvtseBADPCKb58AB0YspuaAh8hl_W2Lvk9qE6x_YZMCuSNJy3cBjXILN8-Np3J2wA1YHb14KrSjGRHQMkfyBlGtm1js5zHCoEZp_0EEyMY-24fczqQ7UTz69pN-5x7UsZSlYY43HPfX7Q_gcNxbnwY-PysxGdvLa5Wjr2yC07Ih8WwjYcoPJ6ZMBzAMdLqZaWFz_8OjoJ3Ny6_DAiEyV7ELiipIa4TVhpKXY24-DINQeOPGQIS5delIP9PgKvP2J44kvIf5zhc_q1mAycrfLd-OPccCKuh379xFstPohUPKgPwNn5xiIsTCvfoEUhzwx3NUff8lZemUmBKHW2D3Y0fKfiy9k-kuXbdFjBQJidGDDLTF64pfw36l_SnFi2KGQ4aYB1zVvLdr2sgutqggS-vQngGQxRRyAI0YjDcYaKSZbwhLThL9bzl91yvqjZYgnmmE0dkOk8mdMYql3ZS9TM-2EjyOEi5WEoSd9ClUrpt6wE1vqKM_18JgQONNRio8aiFrxeXn1MgTPH3KKvU5t8kKZfrr39zUA_55zC-RQddVNa4KoLoyEz5oXxf0_byEUAsLDLbqI1eaoUDEpnqxl9p8MYUlkyjHhrv0MtSsUKryEtQEqdoi1DJSbp9sBIiiVZWHX77B3kothh9bAQw_94EENVakuBdibPxo-LbqpVb5DV4UXgSlzomV3w72vsEBHBIKZa2rwJkw2Qb_IR4iucLknNMbFi3Mee7sV9bcAV8IJDBOndI3kAdzmVpLvSiWTzgr3L8evMO8ax4Zz0GBv2wdp5CjcnRiiTpdv-FxJudAFQtxMAcvmXiAV1zkGaTl1pKFYRjTBtErty5-v-01wQUG_pMh3mdRb3_Bhqor62fBZwx8FmQWINfTmTSbOGA2PKOixAaDV3b9O7RPK_x2RqEvK13SwJncPM4CQSmL6ZNqnwJnH0PI9LRepqVjXNDRBNcHjYYljrE26GKE9Xd3rJryrEzY4vkyAyI7royYYcQM0CKDq9b6Y19ILl9ituQgzowJoqWehdEKb7J1JhxvkjFPTWl3ufq0JOpiqaX-Qs8aVZiFd3_w1xREugtSxXDOGPO_ZJSh7SFkRdC6PIN0Ii-ernFKNlAZyppMdftokVV7QbRkwqpCg6J98mAERnzU7VlKaGvy2zmVbTOFRj4P6NwZiEeahqRykE7fM_g9uWE4N12I5nzmZtodIGDidSgQ1E4yuIJolL7WEGFnBXzPTQdF47FXOjqXaOk2lcUwePfesjdzrOLxf7yGt8u_1b5PCBf5hogyzQNzyxjcynvMx8l_fueGx16Zxh-e9_m-S70KVnmCAhBm7itFQe23hkTDT5cJWW2ONwIVKNTsrFiyYvPRcIsbopd4P7pLWvW7sTLxFREpxaTo5RrP4_nHGD5xWIvPLMWP-9avcZW9TnFMf16AxHHTwRCLvB_2QZJhhGOPbZIXOgZtfrWzbeA7yZvZW2YDAVn5mnlX8scATZjLDugeJuNPfsVIpgAJYjvkpcD4G7mqiSWUNAfS4j2DVMLBh7L0dppBFnHOBqPiLHdtzSrj6Q7jj6ZYRE66kd3ZsgR23MYp1UptaLWq1l01frwAyapvP8Lh7_IFndUlZ31Oe03JrubgePty2h1qZ5J44ZetDg9sFCOswx8NCxQxINple7cVmVp71t_4l5no0Jw30xwHOxe5I5xi8k2JvhF-lrLwbLWxneHHNkZ1x5WMjtrVFdSkiFBiZ0VmBp5QUzXMd-LbxVm-AnzqH9y_T0BdvCB5Yr47kvBOMR0C0uagUiuVt9It72htuD10gTV1f3TKXdHnqaqUJJ5fw6H555G-xkqHn2lydcQn7xh-8kEeKTYULLHKuIJiWtNvB4Y5nOO6YRf2sYiPKkN_xWh9wieq_ryEwYz6nJAujbBggxwK4w3-L_YIn3v86Qhz4yTAi86Cjvz49X0N6ZbULKP_L1gOt9K32IRZJt5Ua5RrkDe2cH97i9ZvZhytQPAcOJ-VfXiAhuryo_O-VS_y7i2tvOi9D1-aZiee7H-HjBl4MHcvLZIXOz5g_01UWuAZntxJgeyxPSrXCeisJcGIbRBvl_UjeD6A9d2beNekuYDZUVl42J6XZtm2CYAlww8WH26_eRObJSfbCaiHB8jN22aIgjb569lk-U-TFoT7Lihc3PW2vVxSMkuJ5bP21b1jefwyyghKP4sCcZq4Me3odilFoNAXK99visES7v4oLjQhp6hT8-apR-7Z-xKZntuYI4vqZ5g3KPf6swN5Z14JPqO43GN37DeCm3GoUZdvjbhBOKKwA9GJdhNzO6SOuwxVak67XK7bTnZEvoA0tzC5KLv-QQr1ay-S7LfvNHLHLRCj93U8fs1RjSO-k_69KgVD3QE0XoPaNQWprOaAwbk4tPnqU1xoca1LFl9rCTZWmQDaaa2e9uS_MHHbjNqmj0KZMpg6Y8HDXKe7ml2kqHwk8-uzo10JBcRAMPn-Hd2fMxbAXeDxUE-1ytIsVKCnDULEPr5h1MMG2P4gaEIiexikT8U_JGgWcrqTg3rdt1CUFUAcXUVe6vd_b3rslEbJ7YofFkGYPqAjuKM_F6OZbFYTzlmJByI6z4G30OnHCZhGatIID95DGcpUwCMT_c2V6dEGIx1QFYSSRmEvyYxk3Di5cL54F4Rg-6aQADx1j1xUCNeD23pXCuYwXcrWBzyuxbzM8zKTm4SCS8qfGnvQaOKT2dT08k-cDPjLt0nq97iEQc7xQHDJxj3ExJJz3krjErmZmWZqo4CaJUfeHBqeO6riZbShnWxG4EFH3OxxdAg4QCLWy3IkDCHD36Vj_c405ArVfjj6yVtQT9RHJPpjrmU7jM_hZUdGyufYs7IUXoHdNkeGgvgR7UCEQTuMUSpTfHMpuv6ofVGbRLDXndUKruVwSnOAOQw0tZYxdFC7rbuT0tkw29lzixjJg3ljxU3sPFt8LQf4PgJY2Ry7TEKukiBDbzJh-IMCddwS9icSpfPXu-eecRD7fc-wBWxh_q5jxyywTiO3UPIIoNfNJXAUQayOW1LrmsQHYXj8AFXA2vOS-BAMx0HwI0SVj_FSrDWNG7lGSx_0LEi0qN9fVuuieTFz_4bQjbsUmewOLh9yKMEZQe-F1leXaoHuJdpPIJtrjSP2n8DkqhyeCgdgY7Vb47k1_RLM3c4Qk2RNGfASyB_x3UsvoMHXq0L0WwxyUd_XI0r5vhLxJTSWvQiDaF5pXOoQtjXeuYPHSp2SV2BOXFYEpaaxsVEcEMwxz3ufh58hc3gu_SmZNjFRWC8H6uoJo4mdouKR6sr-hf9IPF1-yK2DqqE4D5-5H5EeTK0Z2CbmtuzquM5RY2IzNfXetLQOip9MS3DlyCCe1AT1tQTNSg0_OZ4p0bj3evIRXlbuotehwdkoYMJRRIum4Tw8J_r0_YqNn0ljZx5sVxa4G5FSFV3plfsGM46o7F75ZJ7qKDzX9TXTMFfMe-4TMABYAv1dXSQUQMLLGciqWD97s-_pSr7shiKalx3ToQTfDsWrCynD0Y7MlLMFFor8rG8jXfJhH3ZJBUpTm2Fd_FatE-hqkZz63YHv0dOKmx-gPevlo1pGv2KvSIYduepB0AS1BYCnWk_aQjoBwgqRVzBruzMPJLwLArbhih-VuB93M0GeITInQWvq7f8nothr-KDiPvpO4muPyQjqhOda_tt3rhG3bSnOLZUYhsqFFOVAFMVSYPSnFrd7cTPIS3BPUHbe4uOaQs11Te49gaaEZi7HhiFXYBtsBeek1aj29Zp7W5hY9Ioee4WCGlArth6geNS_wDvvRmfFOAuQwMqPZw81WgfLS09anDPbDBLrMP1-RnZgwrh_N-13JWQttYV6w6OUAO_qmp7qtYGW87OGhju15Gr-49eGl9AMeeJ_bf88IE-Pm0Nz4JJGjaF3Bm_ue2JWHVfPreT90hmU4UDNPgTE8mGmkBN-FJdbJmCyZ6ROulkHHWBbP6tipGXPXK1KXWtojkG4ortCd-xjGTnzNk6ukd6lnksI0tGyNGAebS34eqJd9ds1jl2jZbCyRnknmB174a3HgD0uMo4nefmAwnDxC7FChFhYUn5a6oKZ7trXetouNoOJ31IL0YIQfpyVgpJleBqdGn-jp2aU7iDRzpsWeWjTc7xLIAYJSzAZHzHOBS3IIY___rTLWsHWA4DOyK2mKO5cKo5qfWs_DBe61FTdesuanjeb61r_fyJIEp9mpcRTR7Xv0wbuDCpMHHj9Ca0NeOgbZY2ymPjvOrd7VcoB_9g-rnHn_Cxj_aqqo5VFIF3_bOnhZIDWCp9goolcxontFzSOIr8xPQayJCWZ_aECRzYCqbg7yyKlkwF1CEkx0R6X7VAtYqaMDHnL0BF0jrzWG9Y_7OAWO_OqLJCpEfeMK7A-RxeH9v9hx5JlFNWUiatCGNWuQxSWZRMhWdHp1Ndw-cd2ccvbUBMSKD8wWFTdrDQShzbd6hNeD3vWntX39957JcVvi_uhumyY-UD97khgqktcyQNQyO6ZawYRBTIvXByvFJ0-PWCtphHd415Iy7ijqnXQByGyF0Ur604TtVafqhnm5Hbm14JmRxhwZK92Rcx_4UVY23mj-LpwUyy-6kVLkFTub4wbpzJy6AsOZMQmMW9BgaBJwQRaTWynfsK3gK3M5dmY9LPxcmVpTqmQLh532OkVhu2ogj3zAlO649z6adr38tf3Yz0V0FzE8YX5etsMR1fqXKig-7fHv79zdn9vY32r_oxbJPtfVwutZXgyuHD5tNe8uupGacDZkQaitViBUkUBbAYnhvEzqinePJ-At1ixvr8_8EOFSF1X2L-QxlKIMsO5hpLgmoRibopgBepG71AMTIoyYvHagzfgnOQCP4YBf7IB7VTuO4yNMHPSeGPPnb-Hdo3HcGWY5LzP0nasBx71sfqBaHUr9ORAD0lQR5zKKYnbl9KxmrXv1AhQ5EaBzTPSDlfuS9jKzzjvzy60sOBK1FLrHkHjQaO8HWmx_p94if2nVe4JumgC_BlDpBxwWzSo91spC2Uy2LRbGJNkMIjBAkwVmC2mseb9pPET-udjIILODsxb4UgXW8G2o-uyHRJRAzcYGbb-Xdh1epROkqN1V-87NGcQqjH6VEa_WFmw8Yf7LTuzexdHKbmq2LS7F1OAI-W9NTLAJ7eKHRh4eMk40V3ztT7J7lnJISiG4zwQcmETF4h4dhCrPgbEk_vRNtsdAsvpal6orEuA3geT0Bcq9nTo1oUIonhVaFc9JqTcA9Bb_3Xr5AwOWB1qnqLwrRdvWEgYNZwjHCixH9o__s79hOvNoDAi3qUfWl2LV1ZjKyDkQhVRlTCI-TyK3tqoVayHfUxfq97czCl89wSApLlQl4VjkwBx9y1lFIJ40xJUdwO48yV42cnJJ2gqyldMqPJJf4-ToBY0dBSI5_mnMqjCh89dxucRNN4CgyQQFJGi9JrP35BS85xUBCYVYe6dfzbRnnTCHDxbqcggEtvb9cArmo7I70hhuC_BWlxa5J-h5q6ciVZ-hEgEv3xIMxNhOJ2gTod4b619uTjhorkLRVXjrrxKv1glpJxeL_WIOdOjS0SrN_AU6lgYkAQwDXCHErAI6UgwtH8e46roUZnZF3H3Wo4zXBqDtHh-a8giy_Ak9Nb9YKzTuBLxkualVMTrpH13xOvghooBpdEWRBRcgOUGcJfNGRyRtUyOMugkyQ9Cb98eSf74pX5iKMeXAZdSFKf7vJzZ0Rqf2mp1qFjAHbMu0kckvhrCMulCcMf2PWo2IqnfQVkLNgnU3f_SmjzWl9XCBf32Pdp_8kWtVvc0iybq5Pba7WMMls1_cS4cjO8UNWWPeiQcUGFW2g0eas-Zw4u6x1gbkv33cQJ1pMhmcf2XWkk3B1GPIepwFNLOkypjq99CfDcND0FN_2OyjmY6Xokd5BekxfwuJZcuhHY4CI4DizqZ_JwgJZUdT7MzASZP0hZVI0RI-bDcwgeSDh282t_alTswGkwjMx7z8BsL-_Ngo-svjfSw3_zc6ddWVWDRpRzFuFOCf6ZyecnLPYikWY8i2dIAwszyhlffqb0j0cKLMIsHwNtw4W8cB9d2KBaV0nwwqgb7Z6_WMLUuNruE3HlQ8uiDv4QwkzRVUQVbZunP03KyWe0Ra0lmi5iYA4BmWivElvxcy_4gZQE7XnSZv5f3NbfmJ5xp3TpQUOLoxv3R7mqXbffcoPz1QyW8pfd6QT9HXLQBKsVRKLch9MH6UGhL4I4NfsRy0WkY_BTTJ1VW9kfVNKbb1fGy7g88UNnicRyk3zfXmGpr8yh1HF5qLRHev-rDFUtuUPFvYjSC4AnRdT5QWRkL417EqmnFkYNBKfVnWeAAYApXNJla9KLLzW3xgsI9DqUxbj6FgY_EuPuKdnkD3Ki0OPEyKlmK2c8seGEM1dtGIh44GiLcspKPNRDSAVvnnalm7z_oZItFcl0D54bEkx5tHwuYcXS7GgHPs1lW_8pFF2fy-QkLwSXGuh62jYOFOCzQkxf0rE7TQ6koicCNlt-5T5lMJXeyX0el65bFnIalMz7ZscHwiAozeZqC7oGbjy_kJPbsLgY5LP3di88AITnYW5Hm2WCKg655C-QUQ2iZ8WQL4EF4F3SCt3FyU5Rnhn-P0T9ZeLVdglNU4hMfw5TNOrUVdZjdoVH8NL3wGWyGHFbpOQzdrhLCL6Pc4XWz2AGkX9JM0FaIRjVb9dmTmx19wNzhIxEmgcYRwXR0ex2WVN9kTRTA-oBSkpxyiYerXjjjriNbMF0-NA7dO3T0U_ABD6XwetepsoxsaXeMueM9TbWP9eg6CkEg4-zRD91xiIdQcQpsH55DtMQ6pQAuido89q_xrXwc0UyZIBVMAKL3d5kxD4ldw6p4uFXJ1uIps0vOWvWbQqs-Oi09k9dcZ5xeNDsVJkB09FXFkDHH5qeIrY09tAlmcm1yIYSuuiQOYkHTHu-4fMCewpcdXuWvF4v0geHTx2F_JIXkNdxG-hqx_lukIi9UbSaC9Y-MQi71O918UHXjwKPCFDNTxkrfOk01Y89g_V_dM0Xx3rddxp3ld3c5NG04MyJ8MmC-lfx7FhUIrd1VIy0zlbg8PAGoK62QxjA8i9B5gf9kcBalqO105s4qoBBEaWS_6QQ3F2O0kYX8gdxinIapEKD1GFMsN3eDOczUBLLzu2bo-pckPfhmfK6FOLKiaNDC2IA5wqBp4qj6Xvm6P7CVL3oUSlNQAYF6rEtHzloLSjL4Wzbc1e8uiXnqZzhIUSNtmfXlFE1A9eCXiTJiUrD9OZM6w_MckN8pCOvhp23YX8WvjSxwW5jNG7bpZ0nBC2oUuV2_1j77XmSxrA0Sz1n8CWMd9NdDJfK3w1Sh3EgSx4NDCgIMuBzA7SYUzC9lK_YSSp_QaRvB6_I6Cja8h8c4VKTCDaJ9nhyktId6_RXJlsWxDXHk-DxGPVVrTCDKzWtV331US00i9KAOk4I3Xze4xzH95MCG1z6Wrh_1V2R0aHH7wo_BpoAbFS35cFct62WzBqZ58vVFm9q3dNzgvap8Oswa0_-S8vp-Uy63u84R2ZM2HqcZhnPUIfC5SXX2zMmxKcar-RB9EaOQyRRf4Nm5SHfKrDLE9UMx10f1ovYy44rypW3z8iV6e9juQXiFPNkaSXvEiJkB4w1VVtBoX766etiI-wpaZnD2orZlIK2bmSGECJ7dqfx8DJS4BS_U1LOwoTXdLW6TqjQ19RGAydXf5EuxmHJHEHAUx-swi8POuSFSHnvxeTrT36MkoFTBOx1dc7C-rIEjwapxeeMhty8CSx_cJYnkk6uE6BlcXWwa40yY2OCrar0rWU5Ay-jIT-dJF0C_DeeTGHMUdWr3qnvc0Y0wh-h9A2C7vh6caPpKah58j8s1EnQAKO6CxcSI_UlJYh0ZJx_d4NOIY4RxXm2zTBQNwC99zmLmGGKvBiuc8S7_mSHsKIgjaJRnL1zvjjGvZMYs7X5mW-p-Boo6gDC2AYclM_NyMbN8DeBp5YpgRIQNd1oMy9LzP1oMrjZY6jMq4VX4QRyhz7LuoBjKqZle8zLEJo9gd7NJnfS4MrcfEcRe8ARsWMckcDgLL7alhSjIlRFVk8Jto-8eC8Y-ekDhxOsl-h35r9yhm0bBp8uFH2jUPrbczyz95br6P0-pO7AQ6t9f3QNkcMFaFFsRqnGrbedOILF0COxXRSEYxkz7pIS8PxNjXb2aAxbp2tFFETJ2fkvdL9Yyt0AMHLbVDMLUbyxSmKJYKDIUPJRY9BIVf7nwf-vLCnPbhREXxMseIemCfy8CD04ilauRxbuqaCasMDjFBt5oE7Z4PQxXqZ_Br1ehO_daGfKP5H1zrlrhU5dpvy16oGjHu7cuv45k_oNLhVCorn4cu5zjINvAj9CKt3Dp7-vqULV2709o_XLnKBoIYJkIIO4gGYexxwiWpkBEmqji6ibw-84EWEZNA6GPUD6QBf0ahbIss4LSA4ekKatn-kRwNTlF1FJeSczNUbeR9qfywYu9YK43XIuhx_tpZOsY8rCc9wxuWveH5DpRrwDCai-4YsE95j8zr3DjGQK-kDvTSyi6bmcFPXqJXps33II9IvpHaYH54vNID4N71Wta3WTF0qTTH6QUgUhkb72_aeDc63y_CNIK3fgH9gH9kuZlI7Drlv77u4aahu4vMXWw1biGrJ26eBZRpWlGGTIJFpEgdiSdpAUC5Jf7fZUQH-ws-HsdaEZNK69BYYn9QvHFD80IwWj67aYeKWtBEY0YOo8za5F5TTNuBjY5BdYM4tt0SdDQ06-r6ffLjLReZOILrtfLg5RkxvKUN9B4IGQgPNi1mHYcnv3TifoUxOmb4rQFuiDD9C7iaTifVfSnP8TfDg-HoE2CH9QVFCFINaFT2rAUl0f5RKyGNqh5sdwKqMsz-wj9KxUzafrYzp3Y5Yw-YwB4tzKg8jSwksRA9_1s4Q_9enHdh7rG1dzZods3mC39urB2EPdctZyK6go368q0H6Xw9Jn0SUCU-Tl7r7R5FrlyTglVwlfT6k9ycGvKSD_toZJiyCEMMvzrCy27Uu4AO5wFt0TJQdOkJY2ihf2dH6KM6rm43bKnKkqtrE1xcxVZevxB9tdT2LnVhTl4lx0f7DVDarUMOOel-4KZHIJxdM3_4YY-oeqXKjyAShlzaSnKYSEvpXJg3QZEwPlDE2OXjc_GpjmJaBJwSCVEARWApH52oL-O6trSAdmKjkTpbiQ_IYLtm-Ij-Ksjkl356PC9-IlIxxH-EwTrV9LzF6T5_zEABP0XyBHnl6BRVKes88GYYJutAJOGUhcC9ItVmVBMOoSMgO1nK8kfaZDAlwHAarSal-MV3eDqqbE7fXIuEbzZp78tNYyhGA8kW86Y6EIVkgfMRI6Zn64vZI34U73yKH7UilKqByIWK3uKM-E7fSMSX9JqERwEiC8VorUKpSaPLHbXUcaJW_ikHiZHA__xiEByGzYWn-DFTnTftCMt1K29M3I7XmsAz0zXDfLZRd7kz_3XbXvARIflK8yIG_UfSpHhng8whCa65sArFfqCIcrXf5lCLsx24JBSM5YNfji3Kt9M-_dotw7hCMp2c8VctvWRBF5zE81LRfGIpKsaQR4ciJdAjTowN2JVeP9-LCIejWVnXiE-aiMLet2AXQ-6j9p6dnALxaJnleWT37a1b-RurLKj3de8QZ6eukNeujvUz2v0uqA63U0HUhn7ubtXy7LC6abiJADcz9GHWjyqFP5ClBXyrRSDKcg_aLRLGrkGzqPklKjaUGgaq_fcCraoupn1C1au9lUY4SDe_018Qy075Fc3g6fFB9e3dRoD3vp0RI2TGu9jNJev6SWAS5rHTd4qzUBGJpvQZKIkIgQaVwugmGlDjcz4KwMCktfbG_bIyY6tOZng5JjmB_YpKhG1o7qd2L96euNEH7MVfXlzaiSsPRLw7fyQeQr4RxdI-3aYoCF-vxTLJIoFh81_Ltwio1MwCnHy882jZuHjoe9d6vqOd4hwN1Op4o0LUWDZaC2fJM8xJZqCleRXN2Nwt58u09Yl9_OhwL4d_TEwSDA81ONDoN8Fl3SWPtKYwDEKTF426eJSZsBeED1_RcRTdCyoIrgXcr9-lEZgSPATV2AZLe9R7bRJ1d9vSwqvWosIDVYWTG9eLJJHOL7RSxcrP7dr7W7S6rAwOg1b-D9HGvroJG38yEfQBNoebd9BumDAdMWhv7TwQawNF2npGJHuLyLsFv9S255cv_kxFYnd77RntXD6lm24dY9tYfk_wI0XjypEudcECXGUeG-0ImkI8SYIX_Jn7TZNdE7cotcTS4TdCQOlwbiVhyfa29XoIiOm1ee0pAW-7i6Ol6-FUR7NQoPTCh5RzlFpXdnNuuw90ywgrmNaVNmUTbyDA9omLWELHQ_NFbXGBbB744gyTIw4NcTw5FtxdRdN49t6OEQ82D0WDCm_fnC1YwM4-W6LtY1cSODYJyuwN7ipmCOI1bEltg_62Gtn3lZNiD8cyp5XNn7DYVSfyMBwJT9OMyKJSx0EZqvhXB-_1Z6FQmDJC_HGyImL0BagAiL3dIrcCgoLdhoyoIawPfibWfs_PkLiszIMoXtJ13w7Y-vkJdNZubXFS0MNDYsESRhN6uQ75btnGpHT_2w_krCa-VU6xySuJBlhiX6fyBoFsbmkjgTjkwXXCd9hC2XPMtG2g8Cr1xvTWeSL9TC0BVOU-YmaYsgsRIhjMvmIIqxlpPpiQdxVAIBfSLdR746Yg2QOk1WqDM8_E2W8nO-nnUjsrBRQRsl_nkH97kHnUWoKAjPbZVU6ZqhumzGSv5UOXkVq11x2U9ilykHmXUn7byMsTB44KEz2ouT8AVWpPOR06RC0TqqR-KbQxLOde0-XjP_IAvd_VB2DGmgxZuXnsqtlmBWSBLhmVx6I2cEB7nHpv9XEjcxqv9O9m73JX9HPsEvrjTKOlvluRDSHS7BIfywzViBbFPIR6rcC-P6dJx7v1fF5KM1yWSFgey4aZ2n9T6vfyRs32MQbnfOgiojwoOw2KUtsgG8zdTh2yWZm8dpY3uOdgdwwrZB6Zv9PXjUpZ0brEtm1LVQiN8BJwYhTjj350zR3ZB5g5HTtrjoGGjriJL5Qpz5aupdgKbmBnv_H-NLjfXj4XzJxAb9pL5LcQhvt-P5DKGJGo_MmkPKr6s7d-RuYf_Z1bToKQXw0qWTZIouPgKx01KseNx4eUSTxu_QYuOY5ppZa1oGZ5Q9TR35dhohpwMSKhMnDtdGzP9UEURVIu1MyCkqAV9y5GvSiJPjyiwfcu_RnHXZ-wtCjKgI6H1KPz0xpThclGqhZSoTLGJ5SIfVYSHENa5SQH21mPyj3x_R6cg2woU3hr79Ra3WzRekRzUWwRBp3Ztniw2gjyOy4UQ0NAEOL-JOCNaehITgMgzhmZC5Jok7EH9PVao29lZ7soGLb9q3ycwvDgVKWF4UN99BOCMb-mkzPOVYjeRWv21KW5h2T2RuBrL7O_J1vK3uLRboVmCGmfokTreDCm2thGCI94VBRtXLlK9Ooi_hJ3l0t3i3UqYQl5ET0JbOmGw55nz46V9zvGrlT5e2BME5314Up2AvTvdKNS_Kpvni9FgFF7qSlGVwFH_DJ7a68W98itsJeiU_Y7t6CjtKuYHTlsabH2sTXKOB7YyZrS6IC6LDFcoWdHq2bINtspynUY8OuKwrYneUNGtlKpjRXNbFRcIO9nJiKtuohPdzHJEcF3hnv7g0YDLzZ5xFXysCJA83g11H1tk5NoOjLaORDYEidwY-voJFvIlDzbaxT2BrYRvnZiVKWaadQZtfZ3u_I2OlVhKUkW-MLcYpSMbf08y5bLdfXTrlHok38Jbxljhq1qEgZ2u3oJc-7e3UqH7lXO0hJm9HihwmCJhMcnarHQGLlvesuReHG2SIArlMZ0Hhs2m2jkt-7vvKfZ3fykLO2ltydRwrQIzoagRDpyO4hbnZrkqC2VA5XMsnbcveY8wCdQXhYlMFPGCr31Pnenh36SqB5pnkg5egFSbPxmxBct5ogmwRRapyFu-A_YW3-nNHQT0rnJhroGuPzNChLXM_kLjapqzPy-7vm2_DjmrUF8wnCvl-la1_Id9z0BU08jtPxj5R-O3o-Jb-eJ9vPJQ3ym4WFpNLSRtKJaBQX_A1k_tjFCqCF9zgFdmmOuU6R5dXUt4Fba_BO-S3GYDT0VoOaBhLyNUxOc4O2URqrm4Wj57pGSB4t0ji7FGNku3VUazqE1RYvx_Vf2aQSEmhYDqKoT63HOP9QebmuBmn8tJ1U5xK4NCgcUuuZeu-eKBHRsEQY_zCTP3PFTuJI0tmTo8TrtOe0EqfDo-615UcyH9z1JbWgNdWdCVKhVKis63hkkCdmXzVK5L19BUWZ9KjksHRF7bPN6pSbtPQnprXN2IYv6HExSAXiNpxydvNMNtvS0NwFZwobkV9XXVy9pEV-C9Qmf8TrUkm3FbdqHUna6UXvFbeGbi7hOP1hqbsSIUuccx7P6ikjKcWGi_jHoC7AZP5XOINnpKoyZ_q4OGZRm_kvSnCp2DEupzNQnefr8iDYGrLJmv6CI26GcprFqjRDoNaiTFZWLjJxvnHIEgnfyv0KbPwwx931PEA2Y90K9h6-TAXhDqP19oref6k95Ffa_m3l_tBQarsTGPFQMLUnUE0MnfYn0YETqyO5-x8nR22Er0dQSXcjDwpUWeHBoVfo74nrBvigctMzFagbYnsxiy8N0O6Rrl1eVkGa9V-CZK1xZ3Oa4HGPMMzUl8FEoQTtBOpfHZsTtIcD2KvW1KGRAV0GFP4tX4Iep4S59gMdUX7UHGb60m3nLYNxXarSHagsBKRTADG71oTDRFxsfIUnSTEsnIJ-j9fm9QOTKdYqcY8PrHKkllbzz-IDRQKuCQdxDUletoYct_PxjrbRiGN0CJFWfdx6yrHFdnitgDB1nDE98qVIKlYeHSUu8cxgg5WHj2D9IMzddtxfyWBCjxs1hEjG6Zwauxh_XbVHOCQ3UJ-T8TYzPhsSkJB_J8AxjQWoEUEEbHuqsujdhtbaWeiwWER1Sr24DOVfZqh51_oic2-6iMRsdmKkKsx8e_6c6QzrzXHGzIXh07gTLBdNOsvpK0QizA2Eo1RUu1-qZ3NMF6C697VolOWNsf_Ydp5lG-285biP0UIX08jTtQINL5So-yJEjAjX0aq1It-Odn7zeMSDqzjS4nlaeMvT3UG5p8wyL-uuEad4-RqzYm-alcdk8xrSQ190QCaGdTVMoZgPE5GDbhDrMNsBlPPVpsWlNcFgwQcv08U1L_7Oe0p7_uUNX2TuuNuGEyJIIO-6dyuSsFgNWWnKZ_LwhVeDkX6MiGozW9QmofxOUV7TxBR2-tpdf51NJu0oEviuHJHD4cJjvZ3DhJkFadLtnzEXKxf5vY2Q4kmEc4f74oYJBiXZ9af6gPHuqW_7hv7XjdcN-Dh8vs-w1Ji2Dcd_VHr9Q6UKBcFJjBkEmPMkZOwxyQRw6id1Ca3fJtiaJWz53G81GuD8trYMDpc8Jsx0jPebULSzybch3edVhizWLVQte22vnrSyK62deuFGVKaFfsXJtYnCR0Ij7jpbhW6-2XG8xdgosloqiHZ64N9QJgyBrqp9CML025Z_JeO-GCiohLeFaqmijDvEmNGAb0OTCEXF0C6pJFLK5xn7S1p-MyZaHy2tVbmMqWQ-gVnb6AW3wHsBPnWMJK1uPiooHr1TYRwAL649F65esk6S0bwr0LNqNUKsjFUaEAsBItjIe3Jr0xZ7tLR0F0kz52_g4Jv_-QE1evDOTjBoHfQ_vUp8hlYBgVMCO8prRkgWIEjDWNhkGpWFNTDrRqqLB8jyIP3DmVEBPeZ6matsAOdkBj5jQXQ5uzIUtPHIJCMYpfACs2U-n8n8x2rEcupdJluTHEcXRncqhnvyl48Ivp9_gHYgSKNJ31giCa5g6F4-TY2a0dzdYchZ7DZf0B32vkUqpOS6rRoDSzdxzaQ4d68DRxcLDXqvI3-py41tDmOiZC5qfa1iFAKinZoRl_jCSvS7YPXVPfzv1Qz1UZjUcQ7WlSvtyr-Be2r0dCa5SzE9Ig5X1eHCNZdr-gIPm_3e5INGE9djoE6t9iFAuB2OilsBSp-YxlpWHorHycHff7N7-MWn2W-rrV8tv4fZWt9GHJ6R1CmYzk3kTXp100ofdeqUapYX79zlG97tKv49rvSWcxj1IVrS1Jrxq-qNC91FFdpPirjhubj8QDddfIM38oiCcRoPYYNG8dls0kISmUz1AEK8_YMX-_IisVB2x4oBRF8dJKAAV14YrAisSBRb3NGXB8g4XB_vwQLd3hjnhxTbCapvZP7te0wPt-5-EqiPXIxqeDPh0bmLebsR-A4ATC9VryJ6WCxSBlgHOndk2_ZZA1--he6ZMt6l0RtIiODe0NFik_f-Ii6Jd66WEexlBhzuXLTf9cg4j4uRkPJm6Tj9LIWp6BZXEpldheLmX4OBRPVWZsy9jajkkZ6KbHZKRDkjOoKUGSddbVR_qOfGAXCrHvl1Nk5ypa00T_s0MVr1cG33HeIBOgxA1p2v4DEvxfvduv6OzWOLjt5HVUU6gjwkvLFj4DUjgnQ-4gLUnzIfD4tfbofKSH_QgF-wzjwiuhzmK1q1Cj41YowJjUxCLJ_ioYJOGjICSSnDLvAL0vfSQkJQ58GpKAEne1r61icauHXll4xPG9Sy5wcUyx3Bi9jGRt32_ETdzblzjFHAEZhDKayCYChxyYf0xnuW-iBQxpLYkViJWNWKxOHLUaIJgFkKeSQ7Z7T3eBmkvLwTBA8IupKZBHqLuFuB69t3QuNQBcY6Q0u2gGWgVKOFeW8XtqH0Y7Y_9j2ZRqpCtW5p3cQlPz6FLMNfTuMRq0UW3E-ndEEYhDiTASD_ZBVSFJceGCed6Z4r6CvrIOE-OzA16rImpO6jnbuwBYG2VpNBeDNY5XOFP2ZhrTVOjXlqYgJIrXmXesTd7WzFiyD6O8RwlQJrYZEgn2VFXrr0FkzKy3nyEfAlgBo0JbsW_SqwBWCt-MZsD0EQIMYEZGmPcul3op0pFeZbt9R6DvlHG8FNHj99xB3k3lzNI8WT_LU8u3op5WQRHo45-DVAxjqKma_Et8xcEOX2Y_4VmxMMUPD6GjTbT3PLkkMteQTt8UXQIEmzSTQ-WrgdJNA_NFO8OcmNAD6kxcxnaQqIimeTsqlbOnJ2IvLvUBQFSqDwxsKk4A5eC_LRKWlKRcSo1zAMeLgdWYBmSCVQ-_ynnmgywIIMhtBB4atM1SUIio74QG0kXISzQTsX3xxAmWnn6gj3-KYFLSufd3hDNVwou5zNRu26j7OawxmE5_3IVnsSeveg_p7HFNXta6_Vgr1NALfKTMR_zdGMauqeSUSN9-C7WnFRrTPSpAD_UBc-6hSSMTKAjbugbc-VI2qdrIu3Zaq-P0Hg8PrPqae1WjLrhvtUnMgnV9_uZUthr174ay3HiO1z0S52ANm0v0KlyR3z5ZW8-h9KMqUCzGPrT-6I5RSyTkGc37XKj65RiaHoZ37oiklRs0Ml2wKmfzObwqwHf85CkH73hlw2djF-fiQ-KeEK_gMH_hfHIPvEPgzzWnhH2GICa3ZlvzCSmzZH6hOKuVtcuGylwGoNKy_rnDoIKYx0NTmNUBgPn-Hfgm6jq3rmVPiueaPokIVIZFG-a4QK83oIhQ_lQEd_2hk9dFs55hXyjFVr2iJjxbaOk8PHPlqqgm5cl00a_azKriERS0GIqufdrkppLuWMFnxoCc1ewEG3TQH1uEoODKQyg2ItkLowNhT0aeDqabbgdRztCJ1LYxb_mxkCP-3auBPQdqMcM4h6rSnujVDvN1b3cAfrOz34wtasVcL-FctIM4su5iDKqZhZZz7zJ3RBecDuZg7bdHRc0CErh9kabYojkO9RAOpVvxnJ-3upaOldofEFheqYzeWOlNe_gTDzoQLSyplubghvyNhhIzVI_PCiNY0OGfdWbRhnd01_4pcd4ScaK3C8SsVn3Ng2152MZY_SDctDItPVtjKcxRyX_OrYjG-PYGZZgkZuBXpEXr4rzNg-9aiYBrj9C1ilE1XJxtpVbpWFnAQhXp-u865CNYlMW2iPvs3sIs4LipvBUaY8XVb6pG91jeCIdII4s9es2YKihfsDUMC-D4lc8FNyUFBwDoabevaWgDOO7qeOL9vdbtIoX-oXJN6mjuiM6QeByXuYGDacmAxJUdoCiU0dxvsljy4011LTfX3F-gPZCmkYwLjZSDlfmD5HJ4R87AhEh0eXvyhEOiiJLZxvWxOQNel8rUzdSSTDxJfMDjMMYjXkL3GxMOBieU91Ihigkdg4iRRail740jUj3Ih-t-MDZLT-33mNIjHW0BFoM6vkr_UsDjZlJEHlz8w0v5NV7_2QGEp5H-vDImrxMaGILrLtapg2G9rqc0ZNNtX2LbdY2HUahlf6kSwYQw89OMUKC2iUoWBUG3_85reHOmVot8jBWMO5-rbtZjAkeJv_MROldxThgNmjwCag_qyWSEbNCWtk9UqPcWy2sfB7KexKFyJ3vfR5Fej0U9RYEp0R1dL_DbD4MgistYqtd8_izB9jLsnEYOdrp8xvgM08JTVq3jfqowbDVlH4xvc6RPBQjXa97B8XcQvHQt-Y0kvTQovtGKcW52LJareY0OwrijM8Nmb0fAgZSYWyK847RhmjfVy3RUluDe-rXlRNONDzupYkfa7fCbMfaG3B0S2Js0G7BCt7HkOHEhshXsk4-axj_SMBitsbdVOhjTxiJODsRT4YwLXyf5QndD4zfdXYc4EM29qOQipzJPEn_edGV__9HtG7B2-UznO9Fwxz1Mh3yWUnO9Y1020Z_PKLbuxC8TobNM8dKllo1lxc7vIKTjh0fHB4qkd5964IPR1Jk3V8EXrT5GokuzT3hH7f0bVt-AN1alF4h4ThMg5CJTgcDWcLAdE3AAMtSfYwwO7y3c38X1Ge8zfwcPWV9C-TLetAcPKaAKXVPZRnm94GRgMZ_9mm9tkawuZKSGoE5Wf6aDRqRN6LS82Oo4YW4CJuobFSuF1ppPjqEJB57wZ-G0cYGSIxECQM9YbNHYKvp7FnVbIfZvaq2ZFDvm2B0HPX0FMI8VlQ1zpQxhsqh2Ixbuv28o9e3ho14qan9llz8M9nKFfD3P8zTgZiDAheDmueGy4BnyQDB0YuMofdC9bt5O_AjIUZLnlWNABz9tx_g2e95ivxpWd6Ahu1FBp79NIDBMXjoUupi-jWZm-DoZo8bszAnpjJn0pmHTUNBoxkDEeyhC46poAnOF0-obGgYXamiAMQc8-bUUF-0-uOmi5AumA_gFEV5deknYJOSjJZ9gG60U5PkLG-DniVV5kMZaP2X3YTqgUGmB9gylmxDZRBflaYo2bTOsq0Q9FemjzELKE4iU_Sh7IfdraoB8UJjdL9dN5Mm9As3iwd8c0s4yTYsRQYZzJRnaCIp-cvFYAPtmVsc19wKJv7iRsdhMduWruP0BfT8LtG-IzJdtkBztjcbkY0tG9lETOPnFc5Y7-6MTPs-xeWTryiltt4qN7xWPLhsJOMBvutu8IjDdY3yAvWIAJR1zZqqFcv7bd_tZ8OvlQ_KrPK76lVfN-nFmRVVptDJXGMcOapL04yuEhm4eWxm3F_uFQcTsNV5qLAlxaCoCvWz8eV3BQlArLzmOGQt9p_QEPO4RWbZeQ34IOT0WK565UEtpV0KEX8V1O7899syT3I5mENVmSgYXgDARSi9_zZ82eoUOJ5pYHjpsdltGJ_P5eafu_4ZFRd5H43hZE7XGGqVG6fW2Bn3s_mDTJ7foegFyZnYFOoaT2h47s3tkP09s5AxRmWS5zd1kExh-NZOmoqTQs53qHKwOePMiTDbPTij4xZwjaa9iQtfxowP-2-Pc-aFOoeaxuTV9m3Ez0y9C8MDnfYtyYLDp60fV8esBpuq-Hbw1qakJy5pRgNlIiZXZbBdtXZ_LMhZyN_mf7YrI6MNVwekGN6gu16rQT-E8pGkQ85zwnWxK7DcedshipSkt4CYimRi3jlEUPb2XUedXG7RHSM9ATmhjmHWLRgJGIMeEyKK_zCm9kDGEdo6S1kddgmCHHmqIShqM46tA4P7f_hZfBJDdXS_L3UA6K8MBZA1RFenA04T785bnFwqx5CoOvBc9LSadIbgBiz2FRex5FUwsl90YVvPNgssO9q_mzq_s7CCXxul-cvpgsgMmXjWW6sAO3VwpE-6drYK_UxZJq9soqXTRo9xXEGIQ7CPrDtTeeMdyHycbXW8Of5SWhwVh0dRHleuXRcVfJw8RZfUJHd4rBum7sTqSFcgLh9wyq6r_BaaUIkjiFUD6-8_6QWB34Ny4TbXEfazDdQ007Z1XPg_WoKr0ChgUv-Ik22LCYYdVwurxkbbAp7xkNRCLwdk_MamyB7ZouoRM5h3q914A3ZZV5QvB0mBQ1ePP8eyaKWeJckAM6Govz4YUdDBOPvkLgaRo2BvF38yqjZcv9lFYE3EToybpcNld_GuJ6Ul9Wj03reR0TBY2lYcUUDoLCtedizsJyWuF3CWB0nu5o9BBO3cMzjASCRZYzJErmqMPWN0Po8qqn4oOxTRW4lF2zzpj9hiUOB7woEcrIvgk8ZSf4x13Mz5VgwtOc6bnUeSXmG4et3RVVs5IJAKJbVg9BUZ7KgXS1pg==
|
|
|
|
|
|
runtime/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Astera Customer AI runtime.
|
| 2 |
+
|
| 3 |
+
The runtime intentionally has no import-time network/bootstrap side effects.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from .schemas import FinalResponse, ResolutionMode, RoleName
|
| 7 |
+
|
| 8 |
+
__all__ = ["FinalResponse", "ResolutionMode", "RoleName"]
|
runtime/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (361 Bytes). View file
|
|
|
runtime/__pycache__/answer_quality.cpython-313.pyc
ADDED
|
Binary file (6.88 kB). View file
|
|
|
runtime/__pycache__/bootstrap.cpython-313.pyc
ADDED
|
Binary file (2.18 kB). View file
|
|
|
runtime/__pycache__/integration.cpython-313.pyc
ADDED
|
Binary file (4.01 kB). View file
|
|
|
runtime/__pycache__/japanese_skills.cpython-313.pyc
ADDED
|
Binary file (12.5 kB). View file
|
|
|
runtime/__pycache__/kagrra_bridge.cpython-313.pyc
ADDED
|
Binary file (2.17 kB). View file
|
|
|
runtime/__pycache__/knowledge.cpython-313.pyc
ADDED
|
Binary file (3.53 kB). View file
|
|
|
runtime/__pycache__/model.cpython-313.pyc
ADDED
|
Binary file (6.35 kB). View file
|
|
|
runtime/__pycache__/observability.cpython-313.pyc
ADDED
|
Binary file (2.88 kB). View file
|
|
|
runtime/__pycache__/quality.cpython-313.pyc
ADDED
|
Binary file (2.97 kB). View file
|
|
|
runtime/__pycache__/roles.cpython-313.pyc
ADDED
|
Binary file (1.21 kB). View file
|
|
|
runtime/__pycache__/schemas.cpython-313.pyc
ADDED
|
Binary file (5.89 kB). View file
|
|
|
runtime/__pycache__/security.cpython-313.pyc
ADDED
|
Binary file (1.87 kB). View file
|
|
|
runtime/__pycache__/service.cpython-313.pyc
ADDED
|
Binary file (14.6 kB). View file
|
|
|
runtime/__pycache__/state.cpython-313.pyc
ADDED
|
Binary file (2.57 kB). View file
|
|
|
runtime/__pycache__/v8_bridge.cpython-313.pyc
ADDED
|
Binary file (2.74 kB). View file
|
|
|
runtime/answer_quality.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
from .schemas import NeedTask, ResolutionMode, TaskResolution
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass(frozen=True)
|
| 9 |
+
class IntegratedAnswerPlan:
|
| 10 |
+
needs: tuple[NeedTask, ...]
|
| 11 |
+
resolutions: tuple[TaskResolution, ...]
|
| 12 |
+
blocked_task_ids: tuple[str, ...] = ()
|
| 13 |
+
missing_evidence_task_ids: tuple[str, ...] = ()
|
| 14 |
+
missing_user_inputs: tuple[str, ...] = ()
|
| 15 |
+
safety_blocked: bool = False
|
| 16 |
+
runtime_failure: bool = False
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@dataclass(frozen=True)
|
| 20 |
+
class ComposedAnswer:
|
| 21 |
+
mode: ResolutionMode
|
| 22 |
+
answer: str | None
|
| 23 |
+
resolved_task_ids: tuple[str, ...]
|
| 24 |
+
unresolved_task_ids: tuple[str, ...]
|
| 25 |
+
clarification_questions: tuple[str, ...] = ()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class FinalAnswerComposer:
|
| 29 |
+
def compose(self, plan: IntegratedAnswerPlan) -> ComposedAnswer:
|
| 30 |
+
all_task_ids = tuple(n.task_id for n in plan.needs)
|
| 31 |
+
if plan.runtime_failure:
|
| 32 |
+
return ComposedAnswer(ResolutionMode.RUNTIME_FAILURE, None, (), all_task_ids)
|
| 33 |
+
if plan.safety_blocked:
|
| 34 |
+
return ComposedAnswer(ResolutionMode.SAFETY_BLOCKED, None, (), all_task_ids)
|
| 35 |
+
by_task = {r.task_id: r for r in plan.resolutions}
|
| 36 |
+
blocked = set(plan.blocked_task_ids) | set(plan.missing_evidence_task_ids)
|
| 37 |
+
resolved: list[TaskResolution] = []
|
| 38 |
+
unresolved: list[str] = []
|
| 39 |
+
for need in plan.needs:
|
| 40 |
+
item = by_task.get(need.task_id)
|
| 41 |
+
if need.task_id in blocked or item is None or not item.resolved:
|
| 42 |
+
unresolved.append(need.task_id)
|
| 43 |
+
else:
|
| 44 |
+
resolved.append(item)
|
| 45 |
+
useful = "\n\n".join(r.public_text.strip() for r in resolved) or None
|
| 46 |
+
if plan.missing_user_inputs:
|
| 47 |
+
question = f"{plan.missing_user_inputs[0]}を確認してください。"
|
| 48 |
+
return ComposedAnswer(ResolutionMode.NEEDS_USER_INPUT, useful, tuple(r.task_id for r in resolved), tuple(unresolved), (question,))
|
| 49 |
+
if unresolved:
|
| 50 |
+
return ComposedAnswer(ResolutionMode.SAFE_PARTIAL, useful, tuple(r.task_id for r in resolved), tuple(unresolved))
|
| 51 |
+
return ComposedAnswer(ResolutionMode.RESOLVED, useful, tuple(r.task_id for r in resolved), ())
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass(frozen=True)
|
| 55 |
+
class RuntimeSatisfactionSignals:
|
| 56 |
+
all_major_needs_covered: bool
|
| 57 |
+
evidence_complete: bool
|
| 58 |
+
context_consistent: bool
|
| 59 |
+
required_actionability_present: bool
|
| 60 |
+
false_premise_corrected: bool
|
| 61 |
+
unnecessary_clarification_count: int = 0
|
| 62 |
+
unsupported_claim_count: int = 0
|
| 63 |
+
stale_grounding_count: int = 0
|
| 64 |
+
terminology_violation_count: int = 0
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class RuntimeSatisfactionGate:
|
| 68 |
+
def evaluate(self, mode: ResolutionMode, signals: RuntimeSatisfactionSignals) -> tuple[bool, list[str]]:
|
| 69 |
+
failures: list[str] = []
|
| 70 |
+
if mode != ResolutionMode.RESOLVED: failures.append("conversation_not_resolved")
|
| 71 |
+
if not signals.all_major_needs_covered: failures.append("major_need_missing")
|
| 72 |
+
if not signals.evidence_complete: failures.append("evidence_incomplete")
|
| 73 |
+
if not signals.context_consistent: failures.append("context_inconsistent")
|
| 74 |
+
if not signals.required_actionability_present: failures.append("required_actionability_missing")
|
| 75 |
+
if not signals.false_premise_corrected: failures.append("false_premise_uncorrected")
|
| 76 |
+
if signals.unnecessary_clarification_count: failures.append("unnecessary_clarification")
|
| 77 |
+
if signals.unsupported_claim_count: failures.append("unsupported_claim")
|
| 78 |
+
if signals.stale_grounding_count: failures.append("stale_grounding")
|
| 79 |
+
if signals.terminology_violation_count: failures.append("terminology_violation")
|
| 80 |
+
return not failures, failures
|
runtime/bootstrap.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
from .integration import DialogueIntegrator
|
| 6 |
+
from .japanese_skills import JapaneseShortQASkillPack
|
| 7 |
+
from .kagrra_bridge import KagrraBridge
|
| 8 |
+
from .knowledge import GroundingPlanner
|
| 9 |
+
from .model import ResidentRolePool
|
| 10 |
+
from .quality import CompletionGate
|
| 11 |
+
from .service import CustomerAIWork
|
| 12 |
+
from .state import StateStore
|
| 13 |
+
from .v8_bridge import V8Bridge
|
| 14 |
+
|
| 15 |
+
@dataclass(frozen=True)
|
| 16 |
+
class RuntimeDependencies:
|
| 17 |
+
v8_adapter: object
|
| 18 |
+
kagrra_adapter: object
|
| 19 |
+
canonical_store: object
|
| 20 |
+
live_state_provider: object
|
| 21 |
+
backend_factory: object
|
| 22 |
+
japanese_alias_registry: object
|
| 23 |
+
japanese_fuzzy_threshold: float
|
| 24 |
+
max_targeted_retry: int = 1
|
| 25 |
+
|
| 26 |
+
def build_work(deps: RuntimeDependencies) -> CustomerAIWork:
|
| 27 |
+
return CustomerAIWork(v8=V8Bridge(deps.v8_adapter),kagrra=KagrraBridge(deps.kagrra_adapter),grounding=GroundingPlanner(deps.canonical_store,deps.live_state_provider),roles=ResidentRolePool(deps.backend_factory),integrator=DialogueIntegrator(),gate=CompletionGate(),state=StateStore(),japanese=JapaneseShortQASkillPack(alias_registry=deps.japanese_alias_registry,fuzzy_threshold=deps.japanese_fuzzy_threshold),max_targeted_retry=deps.max_targeted_retry)
|
runtime/integration.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
from .schemas import RoleName, RoleResult, TaskResolution
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass(frozen=True)
|
| 9 |
+
class IntegratedRoleResult:
|
| 10 |
+
resolutions: tuple[TaskResolution, ...]
|
| 11 |
+
missing_task_ids: tuple[str, ...]
|
| 12 |
+
contradiction_task_ids: tuple[str, ...]
|
| 13 |
+
risks: tuple[str, ...]
|
| 14 |
+
uncertainties: tuple[str, ...]
|
| 15 |
+
evidence_ids: tuple[str, ...]
|
| 16 |
+
|
| 17 |
+
def as_dict(self) -> dict[str, object]:
|
| 18 |
+
return {"resolutions":[r.model_dump(mode="json") for r in self.resolutions],"missing_task_ids":list(self.missing_task_ids),"contradiction_task_ids":list(self.contradiction_task_ids),"risks":list(self.risks),"uncertainties":list(self.uncertainties),"evidence_ids":list(self.evidence_ids)}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class DialogueIntegrator:
|
| 22 |
+
def integrate(self, results: list[RoleResult]) -> IntegratedRoleResult:
|
| 23 |
+
by_role = {result.role: result for result in results}
|
| 24 |
+
if set(by_role) != set(RoleName):
|
| 25 |
+
raise ValueError("three_role_result_required")
|
| 26 |
+
constructive = by_role[RoleName.CONSTRUCTIVE]
|
| 27 |
+
missing = set().union(*(r.missing_needs for r in results))
|
| 28 |
+
contradictions = set().union(*(r.contradictions for r in results))
|
| 29 |
+
return IntegratedRoleResult(
|
| 30 |
+
resolutions=tuple(constructive.task_resolutions),
|
| 31 |
+
missing_task_ids=tuple(sorted(missing)),
|
| 32 |
+
contradiction_task_ids=tuple(sorted(contradictions)),
|
| 33 |
+
risks=tuple(dict.fromkeys(item for r in results for item in r.risks)),
|
| 34 |
+
uncertainties=tuple(dict.fromkeys(item for r in results for item in r.uncertainties)),
|
| 35 |
+
evidence_ids=tuple(dict.fromkeys(item for r in results for item in r.evidence_ids)),
|
| 36 |
+
)
|
runtime/japanese_skills.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
import unicodedata
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Iterable, Mapping
|
| 7 |
+
|
| 8 |
+
from rapidfuzz import fuzz
|
| 9 |
+
|
| 10 |
+
_ASCII_TOKEN_RE = re.compile(r"[a-z0-9][a-z0-9._+-]*", re.IGNORECASE)
|
| 11 |
+
_ASCII_ALIAS_RE = re.compile(r"^[a-z0-9][a-z0-9._+-]*$", re.IGNORECASE)
|
| 12 |
+
_WS_RE = re.compile(r"[\t\u00a0\u3000 ]+")
|
| 13 |
+
_MULTI_NL_RE = re.compile(r"\n{3,}")
|
| 14 |
+
|
| 15 |
+
@dataclass(frozen=True)
|
| 16 |
+
class NormalizedJapaneseText:
|
| 17 |
+
raw: str
|
| 18 |
+
normalized: str
|
| 19 |
+
|
| 20 |
+
class JapaneseSurfaceNormalizer:
|
| 21 |
+
def normalize(self, text: str) -> NormalizedJapaneseText:
|
| 22 |
+
normalized = unicodedata.normalize("NFKC", text)
|
| 23 |
+
normalized = normalized.replace("\r\n", "\n").replace("\r", "\n")
|
| 24 |
+
normalized = "\n".join(_WS_RE.sub(" ", line).strip() for line in normalized.split("\n"))
|
| 25 |
+
normalized = _MULTI_NL_RE.sub("\n\n", normalized).strip()
|
| 26 |
+
return NormalizedJapaneseText(raw=text, normalized=normalized)
|
| 27 |
+
|
| 28 |
+
@dataclass(frozen=True)
|
| 29 |
+
class TermCandidate:
|
| 30 |
+
canonical: str
|
| 31 |
+
matched_alias: str
|
| 32 |
+
score: float
|
| 33 |
+
exact: bool
|
| 34 |
+
|
| 35 |
+
class AsteraTermAliasSkill:
|
| 36 |
+
def __init__(self, alias_registry: Mapping[str, Iterable[str]], normalizer: JapaneseSurfaceNormalizer | None = None):
|
| 37 |
+
self.normalizer = normalizer or JapaneseSurfaceNormalizer(); self._aliases=[]
|
| 38 |
+
for canonical, aliases in alias_registry.items():
|
| 39 |
+
for alias in {canonical, *aliases}:
|
| 40 |
+
norm=self.normalizer.normalize(alias).normalized.casefold()
|
| 41 |
+
if norm: self._aliases.append((canonical, alias, norm))
|
| 42 |
+
@staticmethod
|
| 43 |
+
def _ascii_exact(alias: str, query: str) -> bool:
|
| 44 |
+
return bool(re.compile(rf"(?<![a-z0-9]){re.escape(alias)}(?![a-z0-9])", re.IGNORECASE).search(query))
|
| 45 |
+
@staticmethod
|
| 46 |
+
def _ascii_fuzzy_score(alias: str, query: str) -> float:
|
| 47 |
+
scores=[]
|
| 48 |
+
for token in _ASCII_TOKEN_RE.findall(query):
|
| 49 |
+
token=token.casefold()
|
| 50 |
+
if abs(len(token)-len(alias)) > max(1, len(alias)//3): continue
|
| 51 |
+
scores.append(float(fuzz.ratio(alias, token)))
|
| 52 |
+
return max(scores, default=0.0)
|
| 53 |
+
def candidates(self, text: str, *, fuzzy_threshold: float) -> list[TermCandidate]:
|
| 54 |
+
query=self.normalizer.normalize(text).normalized.casefold(); found={}
|
| 55 |
+
for canonical, alias, norm_alias in self._aliases:
|
| 56 |
+
is_ascii=bool(_ASCII_ALIAS_RE.fullmatch(norm_alias))
|
| 57 |
+
if is_ascii:
|
| 58 |
+
exact=self._ascii_exact(norm_alias, query); score=100.0 if exact else self._ascii_fuzzy_score(norm_alias, query)
|
| 59 |
+
else:
|
| 60 |
+
exact=norm_alias in query; score=100.0 if exact else float(fuzz.partial_ratio(norm_alias, query)) if len(norm_alias)>=4 else 0.0
|
| 61 |
+
if exact or score>=fuzzy_threshold:
|
| 62 |
+
candidate=TermCandidate(canonical, alias, score, exact); previous=found.get(canonical)
|
| 63 |
+
if previous is None or (candidate.exact, candidate.score)>(previous.exact, previous.score): found[canonical]=candidate
|
| 64 |
+
return sorted(found.values(), key=lambda x:(not x.exact,-x.score,x.canonical))
|
| 65 |
+
|
| 66 |
+
@dataclass(frozen=True)
|
| 67 |
+
class ConversationContext:
|
| 68 |
+
active_topics: tuple[str,...]=()
|
| 69 |
+
last_user_need: str=""
|
| 70 |
+
user_conditions: tuple[tuple[str,str],...]=()
|
| 71 |
+
|
| 72 |
+
class JapaneseEllipsisContextSkill:
|
| 73 |
+
SHORT_FOLLOWUP_MARKERS=("それは","それで","じゃあ","では","なら","こっちは","これは","あれは","あと","で、")
|
| 74 |
+
def bind(self,text:str,context:ConversationContext)->dict[str,object]:
|
| 75 |
+
normalized=JapaneseSurfaceNormalizer().normalize(text).normalized; is_short=len(normalized)<=40; marker_hit=any(normalized.startswith(m) for m in self.SHORT_FOLLOWUP_MARKERS)
|
| 76 |
+
return {"text":normalized,"is_ellipsis_followup":bool(is_short and marker_hit and (context.active_topics or context.last_user_need)),"active_topics":list(context.active_topics),"last_user_need":context.last_user_need,"user_conditions":dict(context.user_conditions)}
|
| 77 |
+
|
| 78 |
+
@dataclass(frozen=True)
|
| 79 |
+
class TerminologyViolation:
|
| 80 |
+
alias:str
|
| 81 |
+
canonical:str
|
| 82 |
+
|
| 83 |
+
class JapaneseResponseTerminologyGuard:
|
| 84 |
+
def __init__(self,alias_registry:Mapping[str,Iterable[str]]):
|
| 85 |
+
self._pairs=[(alias,canonical) for canonical,aliases in alias_registry.items() for alias in aliases if alias!=canonical]
|
| 86 |
+
@staticmethod
|
| 87 |
+
def _contains_term(text:str,term:str)->bool:
|
| 88 |
+
nt=unicodedata.normalize("NFKC",text); nm=unicodedata.normalize("NFKC",term)
|
| 89 |
+
if _ASCII_ALIAS_RE.fullmatch(nm): return bool(re.search(rf"(?<![A-Za-z0-9]){re.escape(nm)}(?![A-Za-z0-9])",nt))
|
| 90 |
+
return nm in nt
|
| 91 |
+
def check(self,answer:str)->list[TerminologyViolation]:
|
| 92 |
+
return [TerminologyViolation(alias=a,canonical=c) for a,c in self._pairs if self._contains_term(answer,a) and not self._contains_term(answer,c)]
|
| 93 |
+
|
| 94 |
+
class JapaneseShortQASkillPack:
|
| 95 |
+
def __init__(self,*,alias_registry:Mapping[str,Iterable[str]],fuzzy_threshold:float):
|
| 96 |
+
self.normalizer=JapaneseSurfaceNormalizer(); self.alias=AsteraTermAliasSkill(alias_registry,self.normalizer); self.ellipsis=JapaneseEllipsisContextSkill(); self.terminology=JapaneseResponseTerminologyGuard(alias_registry); self.fuzzy_threshold=fuzzy_threshold
|
| 97 |
+
def prepare(self,text:str,context:ConversationContext)->dict[str,object]:
|
| 98 |
+
normalized=self.normalizer.normalize(text); aliases=self.alias.candidates(normalized.normalized,fuzzy_threshold=self.fuzzy_threshold); ellipsis=self.ellipsis.bind(normalized.normalized,context)
|
| 99 |
+
return {"raw_text":normalized.raw,"normalized_text":normalized.normalized,"term_candidates":[candidate.__dict__ for candidate in aliases],"ellipsis":ellipsis}
|
| 100 |
+
def terminology_violations(self,answer:str)->list[TerminologyViolation]: return self.terminology.check(answer)
|
runtime/kagrra_bridge.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Protocol
|
| 4 |
+
|
| 5 |
+
from .schemas import RoleResult, SharedRolePacket
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class KagrraAdapter(Protocol):
|
| 9 |
+
async def preprocess(self, message: str, context: dict[str, Any]) -> dict[str, Any]: ...
|
| 10 |
+
async def audit(self, packet: SharedRolePacket, results: list[RoleResult], integrated: dict[str, Any]) -> dict[str, Any]: ...
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class KagrraBridge:
|
| 14 |
+
def __init__(self, adapter: KagrraAdapter): self.adapter = adapter
|
| 15 |
+
|
| 16 |
+
async def preprocess(self, message: str, context: dict[str, Any]) -> dict[str, Any]:
|
| 17 |
+
raw = await self.adapter.preprocess(message, context)
|
| 18 |
+
if not isinstance(raw, dict): raise TypeError("kagrra_preprocess_invalid")
|
| 19 |
+
return raw
|
| 20 |
+
|
| 21 |
+
async def audit(self, packet: SharedRolePacket, results: list[RoleResult], integrated: dict[str, Any]) -> dict[str, Any]:
|
| 22 |
+
raw = await self.adapter.audit(packet, results, integrated)
|
| 23 |
+
if not isinstance(raw, dict): raise TypeError("kagrra_audit_invalid")
|
| 24 |
+
return raw
|
runtime/knowledge.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Protocol
|
| 4 |
+
|
| 5 |
+
from .schemas import GroundedFact, NeedTask
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class CanonicalKnowledgeStore(Protocol):
|
| 9 |
+
async def find_for_tasks(self, tasks: list[NeedTask]) -> list[GroundedFact]: ...
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class LiveStateProvider(Protocol):
|
| 13 |
+
async def current_facts(self, tasks: list[NeedTask]) -> list[GroundedFact]: ...
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class GroundingConflictError(ValueError):
|
| 17 |
+
pass
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class GroundingPlanner:
|
| 21 |
+
PRIORITY = {"canonical": 0, "current": 1, "live": 2}
|
| 22 |
+
|
| 23 |
+
def __init__(self, canonical: CanonicalKnowledgeStore, live: LiveStateProvider):
|
| 24 |
+
self.canonical = canonical
|
| 25 |
+
self.live = live
|
| 26 |
+
|
| 27 |
+
async def build_shared_facts(self, tasks: list[NeedTask]) -> list[GroundedFact]:
|
| 28 |
+
raw = [*await self.canonical.find_for_tasks(tasks), *await self.live.current_facts(tasks)]
|
| 29 |
+
public = [f for f in raw if f.public and not f.legacy and not f.undecided]
|
| 30 |
+
selected: dict[str, GroundedFact] = {}
|
| 31 |
+
for fact in public:
|
| 32 |
+
previous = selected.get(fact.fact_id)
|
| 33 |
+
if previous is None:
|
| 34 |
+
selected[fact.fact_id] = fact
|
| 35 |
+
continue
|
| 36 |
+
pp = self.PRIORITY[previous.authority]
|
| 37 |
+
cp = self.PRIORITY[fact.authority]
|
| 38 |
+
if cp > pp:
|
| 39 |
+
selected[fact.fact_id] = fact
|
| 40 |
+
elif cp == pp and previous.value != fact.value:
|
| 41 |
+
raise GroundingConflictError(f"same-authority conflict for {fact.fact_id}: {previous.source_id} vs {fact.source_id}")
|
| 42 |
+
return sorted(selected.values(), key=lambda item: item.fact_id)
|
runtime/model.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import json
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Protocol
|
| 7 |
+
|
| 8 |
+
import httpx
|
| 9 |
+
|
| 10 |
+
from .roles import role_rules
|
| 11 |
+
from .schemas import RoleName, RoleResult, SharedRolePacket
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class ModelBackend(Protocol):
|
| 15 |
+
async def generate_role(self, role: RoleName, packet: SharedRolePacket) -> RoleResult: ...
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass(slots=True)
|
| 19 |
+
class ResidentRoleWorker:
|
| 20 |
+
role: RoleName
|
| 21 |
+
backend: ModelBackend
|
| 22 |
+
|
| 23 |
+
async def run(self, packet: SharedRolePacket) -> RoleResult:
|
| 24 |
+
result = await self.backend.generate_role(self.role, packet)
|
| 25 |
+
if result.role != self.role:
|
| 26 |
+
raise ValueError(f"role mismatch: expected={self.role} actual={result.role}")
|
| 27 |
+
return result
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class ResidentRolePool:
|
| 31 |
+
def __init__(self, backend_factory):
|
| 32 |
+
self._workers = {role: ResidentRoleWorker(role=role, backend=backend_factory(role)) for role in RoleName}
|
| 33 |
+
|
| 34 |
+
async def run_all(self, packet: SharedRolePacket) -> list[RoleResult]:
|
| 35 |
+
tasks = [asyncio.create_task(self._workers[role].run(packet)) for role in RoleName]
|
| 36 |
+
return await asyncio.gather(*tasks)
|
| 37 |
+
|
| 38 |
+
async def retry_role(self, role: RoleName, packet: SharedRolePacket) -> RoleResult:
|
| 39 |
+
return await self._workers[role].run(packet)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class HuggingFaceRoleBackend:
|
| 43 |
+
def __init__(self, *, role: RoleName, model_id: str, token: str, api_url: str = "https://router.huggingface.co/v1/chat/completions", timeout_seconds: float = 30.0):
|
| 44 |
+
if not model_id.strip(): raise ValueError("model_id_required")
|
| 45 |
+
if not token.strip(): raise ValueError("hf_token_required")
|
| 46 |
+
self.role, self.model_id, self.token, self.api_url, self.timeout_seconds = role, model_id.strip(), token.strip(), api_url, timeout_seconds
|
| 47 |
+
|
| 48 |
+
async def generate_role(self, role: RoleName, packet: SharedRolePacket) -> RoleResult:
|
| 49 |
+
if role != self.role: raise ValueError("backend_role_mismatch")
|
| 50 |
+
prompt = {
|
| 51 |
+
"role": role.value,
|
| 52 |
+
"rules": role_rules(role),
|
| 53 |
+
"packet": packet.model_dump(mode="json"),
|
| 54 |
+
"required_output_schema": RoleResult.model_json_schema(),
|
| 55 |
+
"constraints": ["Use only supplied packet facts for Astera-specific claims.", "Return JSON only.", "Do not claim external actions were executed."],
|
| 56 |
+
}
|
| 57 |
+
async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
|
| 58 |
+
response = await client.post(self.api_url, headers={"authorization": f"Bearer {self.token}"}, json={"model": self.model_id, "messages": [{"role": "user", "content": json.dumps(prompt, ensure_ascii=False)}], "response_format": {"type": "json_object"}, "stream": False})
|
| 59 |
+
response.raise_for_status()
|
| 60 |
+
payload = response.json(); choices = payload.get("choices") or []
|
| 61 |
+
if not choices: raise RuntimeError("model_empty_choices")
|
| 62 |
+
content = choices[0].get("message", {}).get("content")
|
| 63 |
+
if not isinstance(content, str) or not content.strip(): raise RuntimeError("model_empty_content")
|
| 64 |
+
return RoleResult.model_validate_json(content)
|
runtime/observability.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
import time
|
| 6 |
+
from dataclasses import asdict, dataclass
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@dataclass(frozen=True)
|
| 11 |
+
class AuditEvent:
|
| 12 |
+
event: str
|
| 13 |
+
request_id: str
|
| 14 |
+
session_hash: str
|
| 15 |
+
latency_ms: float
|
| 16 |
+
passed: bool
|
| 17 |
+
resolution_score: float
|
| 18 |
+
retry_count: int
|
| 19 |
+
violations: list[str]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def hash_session(session_id: str) -> str:
|
| 23 |
+
return hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:16]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class AuditSink:
|
| 27 |
+
def __init__(self, path: Path):
|
| 28 |
+
self.path = path; self.path.parent.mkdir(parents=True, exist_ok=True)
|
| 29 |
+
def write(self, event: AuditEvent) -> None:
|
| 30 |
+
with self.path.open("a", encoding="utf-8") as file: file.write(json.dumps(asdict(event), ensure_ascii=False) + "\n")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class Timer:
|
| 34 |
+
def __enter__(self):
|
| 35 |
+
self.started = time.perf_counter(); self.latency_ms = 0.0; return self
|
| 36 |
+
def __exit__(self, exc_type, exc, tb):
|
| 37 |
+
self.latency_ms = (time.perf_counter() - self.started) * 1000
|
runtime/quality.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
from .integration import IntegratedRoleResult
|
| 6 |
+
from .schemas import SharedRolePacket
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@dataclass(frozen=True)
|
| 10 |
+
class CompletionCheck:
|
| 11 |
+
passed: bool
|
| 12 |
+
resolution_score: float
|
| 13 |
+
violations: tuple[str, ...]
|
| 14 |
+
missing_evidence_task_ids: tuple[str, ...]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class CompletionGate:
|
| 18 |
+
def evaluate(self, packet: SharedRolePacket, integrated: IntegratedRoleResult, *, external_violations=()) -> CompletionCheck:
|
| 19 |
+
violations = list(external_violations)
|
| 20 |
+
by_task = {item.task_id: item for item in integrated.resolutions}
|
| 21 |
+
missing_evidence: list[str] = []
|
| 22 |
+
resolved = 0
|
| 23 |
+
for task in packet.tasks:
|
| 24 |
+
resolution = by_task.get(task.task_id)
|
| 25 |
+
if resolution is None or not resolution.resolved:
|
| 26 |
+
violations.append("major_need_missing"); continue
|
| 27 |
+
if task.required_facts and not resolution.evidence_ids:
|
| 28 |
+
missing_evidence.append(task.task_id); violations.append("evidence_incomplete"); continue
|
| 29 |
+
if task.actionability_required and not resolution.action_steps:
|
| 30 |
+
violations.append("required_actionability_missing"); continue
|
| 31 |
+
resolved += 1
|
| 32 |
+
if integrated.missing_task_ids: violations.append("major_need_missing")
|
| 33 |
+
if integrated.contradiction_task_ids: violations.append("contradiction")
|
| 34 |
+
total = max(1, len(packet.tasks))
|
| 35 |
+
deduped = tuple(dict.fromkeys(violations))
|
| 36 |
+
return CompletionCheck(not deduped and resolved == len(packet.tasks), resolved / total, deduped, tuple(sorted(set(missing_evidence))))
|
runtime/roles.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from .schemas import RoleName
|
| 4 |
+
|
| 5 |
+
ROLE_RULES: dict[RoleName, tuple[str, ...]] = {
|
| 6 |
+
RoleName.CONSTRUCTIVE: (
|
| 7 |
+
"Resolve every major user need.",
|
| 8 |
+
"Build the answer, conditions, exceptions, procedure and completion conditions.",
|
| 9 |
+
"Do not invent Astera-specific facts.",
|
| 10 |
+
),
|
| 11 |
+
RoleName.ADVERSARIAL: (
|
| 12 |
+
"Independently inspect missing needs, contradictions and failure conditions.",
|
| 13 |
+
"Detect false premises, legacy mixing, unsupported claims and hidden conditions.",
|
| 14 |
+
"Do not expand the task outside the user need.",
|
| 15 |
+
),
|
| 16 |
+
RoleName.EVIDENCE_BOUND: (
|
| 17 |
+
"Bind claims to supplied canonical/current/live evidence.",
|
| 18 |
+
"Check source authority, freshness, conflicts and public boundary.",
|
| 19 |
+
"Do not approve claims without supported evidence when grounding is required.",
|
| 20 |
+
),
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def role_rules(role: RoleName) -> tuple[str, ...]:
|
| 25 |
+
return ROLE_RULES[role]
|
runtime/schemas.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from enum import Enum
|
| 4 |
+
from typing import Any, Literal
|
| 5 |
+
|
| 6 |
+
from pydantic import BaseModel, Field
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class RoleName(str, Enum):
|
| 10 |
+
CONSTRUCTIVE = "constructive"
|
| 11 |
+
ADVERSARIAL = "adversarial"
|
| 12 |
+
EVIDENCE_BOUND = "evidence_bound"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class NeedTask(BaseModel):
|
| 16 |
+
task_id: str
|
| 17 |
+
text: str
|
| 18 |
+
intent: str
|
| 19 |
+
required_facts: list[str] = Field(default_factory=list)
|
| 20 |
+
completion_condition: str
|
| 21 |
+
priority: Literal["primary", "secondary"] = "primary"
|
| 22 |
+
response_shape: Literal["direct", "procedure", "comparison", "troubleshooting"] = "direct"
|
| 23 |
+
required_user_inputs: list[str] = Field(default_factory=list)
|
| 24 |
+
actionability_required: bool = False
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class GroundedFact(BaseModel):
|
| 28 |
+
fact_id: str
|
| 29 |
+
value: str
|
| 30 |
+
source_id: str
|
| 31 |
+
authority: Literal["canonical", "current", "live"]
|
| 32 |
+
freshness: str | None = None
|
| 33 |
+
public: bool = True
|
| 34 |
+
legacy: bool = False
|
| 35 |
+
undecided: bool = False
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class TaskResolution(BaseModel):
|
| 39 |
+
task_id: str
|
| 40 |
+
public_text: str = ""
|
| 41 |
+
evidence_ids: list[str] = Field(default_factory=list)
|
| 42 |
+
conditions: list[str] = Field(default_factory=list)
|
| 43 |
+
exceptions: list[str] = Field(default_factory=list)
|
| 44 |
+
action_steps: list[str] = Field(default_factory=list)
|
| 45 |
+
unresolved_reason: str | None = None
|
| 46 |
+
|
| 47 |
+
@property
|
| 48 |
+
def resolved(self) -> bool:
|
| 49 |
+
return bool(self.public_text.strip()) and self.unresolved_reason is None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class SharedRolePacket(BaseModel):
|
| 53 |
+
request_id: str
|
| 54 |
+
session_id: str
|
| 55 |
+
turn_id: str
|
| 56 |
+
user_message: str
|
| 57 |
+
normalized_need: str
|
| 58 |
+
audience: str
|
| 59 |
+
tasks: list[NeedTask]
|
| 60 |
+
user_conditions: dict[str, Any] = Field(default_factory=dict)
|
| 61 |
+
language_hints: dict[str, Any] = Field(default_factory=dict)
|
| 62 |
+
facts: list[GroundedFact] = Field(default_factory=list)
|
| 63 |
+
unresolved_items: list[str] = Field(default_factory=list)
|
| 64 |
+
repair_targets: list[str] = Field(default_factory=list)
|
| 65 |
+
forbidden_claims: list[str] = Field(default_factory=list)
|
| 66 |
+
legacy_exclusions: list[str] = Field(default_factory=list)
|
| 67 |
+
completion_conditions: list[str] = Field(default_factory=list)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class RoleResult(BaseModel):
|
| 71 |
+
role: RoleName
|
| 72 |
+
claims: list[str] = Field(default_factory=list)
|
| 73 |
+
evidence_ids: list[str] = Field(default_factory=list)
|
| 74 |
+
risks: list[str] = Field(default_factory=list)
|
| 75 |
+
uncertainties: list[str] = Field(default_factory=list)
|
| 76 |
+
missing_needs: list[str] = Field(default_factory=list)
|
| 77 |
+
contradictions: list[str] = Field(default_factory=list)
|
| 78 |
+
task_resolutions: list[TaskResolution] = Field(default_factory=list)
|
| 79 |
+
proposed_resolution: str = Field(default="", description="Legacy migration field; never use as the sole final response source.")
|
| 80 |
+
completion_state: Literal["complete", "partial", "blocked"] = "partial"
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class ResolutionMode(str, Enum):
|
| 84 |
+
RESOLVED = "resolved"
|
| 85 |
+
NEEDS_USER_INPUT = "needs_user_input"
|
| 86 |
+
SAFE_PARTIAL = "safe_partial"
|
| 87 |
+
BLOCKED_CURRENT_FACT = "blocked_current_fact"
|
| 88 |
+
SAFETY_BLOCKED = "safety_blocked"
|
| 89 |
+
RUNTIME_FAILURE = "runtime_failure"
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class FinalResponse(BaseModel):
|
| 93 |
+
request_id: str
|
| 94 |
+
session_id: str
|
| 95 |
+
turn_id: str
|
| 96 |
+
answer: str | None
|
| 97 |
+
answered_task_ids: list[str]
|
| 98 |
+
unresolved_task_ids: list[str]
|
| 99 |
+
evidence_ids: list[str]
|
| 100 |
+
resolution_score: float
|
| 101 |
+
passed: bool
|
| 102 |
+
resolution_mode: ResolutionMode
|
| 103 |
+
clarification_questions: list[str] = Field(default_factory=list)
|
| 104 |
+
failure_class: Literal["coverage_defect", "grounding_conflict", "safety_rejection", "runtime_failure"] | None = None
|
| 105 |
+
violations: list[str] = Field(default_factory=list)
|
runtime/security.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import Iterable
|
| 5 |
+
|
| 6 |
+
from .schemas import GroundedFact
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@dataclass(frozen=True)
|
| 10 |
+
class SecurityCheck:
|
| 11 |
+
passed: bool
|
| 12 |
+
violations: list[str]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class PublicBoundary:
|
| 16 |
+
def filter_facts(self, facts: Iterable[GroundedFact]) -> list[GroundedFact]:
|
| 17 |
+
return [f for f in facts if f.public and not f.legacy and not f.undecided]
|
| 18 |
+
|
| 19 |
+
def check_output(self, *, answer: str, forbidden_literals: Iterable[str], unexecuted_completion_claim: bool) -> SecurityCheck:
|
| 20 |
+
violations: list[str] = []
|
| 21 |
+
for literal in forbidden_literals:
|
| 22 |
+
if literal and literal in answer:
|
| 23 |
+
violations.append("forbidden_literal_exposed"); break
|
| 24 |
+
if unexecuted_completion_claim:
|
| 25 |
+
violations.append("unexecuted_completion_claim")
|
| 26 |
+
return SecurityCheck(passed=not violations, violations=violations)
|
runtime/service.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import uuid
|
| 4 |
+
|
| 5 |
+
from .answer_quality import FinalAnswerComposer, IntegratedAnswerPlan, RuntimeSatisfactionGate, RuntimeSatisfactionSignals
|
| 6 |
+
from .integration import DialogueIntegrator
|
| 7 |
+
from .japanese_skills import JapaneseShortQASkillPack
|
| 8 |
+
from .kagrra_bridge import KagrraBridge
|
| 9 |
+
from .knowledge import GroundingConflictError, GroundingPlanner
|
| 10 |
+
from .model import ResidentRolePool
|
| 11 |
+
from .quality import CompletionGate
|
| 12 |
+
from .schemas import FinalResponse, ResolutionMode, RoleName, SharedRolePacket
|
| 13 |
+
from .security import PublicBoundary
|
| 14 |
+
from .state import StateStore
|
| 15 |
+
from .v8_bridge import V8Bridge
|
| 16 |
+
|
| 17 |
+
class CustomerAIWork:
|
| 18 |
+
def __init__(self,*,v8:V8Bridge,kagrra:KagrraBridge,grounding:GroundingPlanner,roles:ResidentRolePool,integrator:DialogueIntegrator,gate:CompletionGate,state:StateStore,japanese:JapaneseShortQASkillPack,max_targeted_retry:int=1):
|
| 19 |
+
self.v8=v8; self.kagrra=kagrra; self.grounding=grounding; self.roles=roles; self.integrator=integrator; self.gate=gate; self.state=state; self.japanese=japanese; self.max_targeted_retry=max(0,max_targeted_retry); self.composer=FinalAnswerComposer(); self.satisfaction=RuntimeSatisfactionGate(); self.security=PublicBoundary()
|
| 20 |
+
async def run(self,session_id:str,message:str)->FinalResponse:
|
| 21 |
+
request_id="req_"+uuid.uuid4().hex; turn_id="turn_"+uuid.uuid4().hex; state=self.state.get(session_id); prepared=self.japanese.prepare(message,state.as_japanese_context()); normalized_text=str(prepared["normalized_text"])
|
| 22 |
+
context={"active_topics":list(state.active_topics),"last_user_need":state.last_user_need,"user_conditions":dict(state.user_conditions),"japanese":prepared}
|
| 23 |
+
try:
|
| 24 |
+
kpre=await self.kagrra.preprocess(normalized_text,context); normalized_need,audience,tasks=await self.v8.analyze_need(normalized_text,{**context,**kpre}); facts=await self.grounding.build_shared_facts(tasks)
|
| 25 |
+
except GroundingConflictError:
|
| 26 |
+
return self._failure(request_id,session_id,turn_id,ResolutionMode.BLOCKED_CURRENT_FACT,"grounding_conflict",["grounding_conflict"])
|
| 27 |
+
except Exception:
|
| 28 |
+
return self._failure(request_id,session_id,turn_id,ResolutionMode.RUNTIME_FAILURE,"runtime_failure",["preflight_runtime_failure"])
|
| 29 |
+
packet=SharedRolePacket(request_id=request_id,session_id=session_id,turn_id=turn_id,user_message=message,normalized_need=normalized_need,audience=audience,tasks=tasks,user_conditions=dict(state.user_conditions),language_hints={"term_candidates":prepared["term_candidates"],"ellipsis":prepared["ellipsis"]},facts=facts,completion_conditions=[task.completion_condition for task in tasks])
|
| 30 |
+
try:
|
| 31 |
+
results=await self.roles.run_all(packet); integrated=self.integrator.integrate(results); compare=await self.v8.compare(packet,results); audit=await self.kagrra.audit(packet,results,integrated.as_dict())
|
| 32 |
+
except Exception:
|
| 33 |
+
return self._failure(request_id,session_id,turn_id,ResolutionMode.RUNTIME_FAILURE,"runtime_failure",["role_runtime_failure"])
|
| 34 |
+
external=[*[str(i) for i in compare.get("violations",[])],*[str(i) for i in audit.get("violations",[])]]; quality=self.gate.evaluate(packet,integrated,external_violations=external); retries=0
|
| 35 |
+
while not quality.passed and retries<self.max_targeted_retry:
|
| 36 |
+
targets=sorted(set(integrated.missing_task_ids)|set(integrated.contradiction_task_ids)|set(quality.missing_evidence_task_ids))
|
| 37 |
+
if not targets: break
|
| 38 |
+
repair_packet=packet.model_copy(update={"repair_targets":targets})
|
| 39 |
+
try:
|
| 40 |
+
repaired=await self.roles.retry_role(RoleName.CONSTRUCTIVE,repair_packet); results=[repaired if item.role==RoleName.CONSTRUCTIVE else item for item in results]; integrated=self.integrator.integrate(results); compare=await self.v8.compare(repair_packet,results); audit=await self.kagrra.audit(repair_packet,results,integrated.as_dict()); external=[*[str(i) for i in compare.get("violations",[])],*[str(i) for i in audit.get("violations",[])]]; quality=self.gate.evaluate(repair_packet,integrated,external_violations=external)
|
| 41 |
+
except Exception: break
|
| 42 |
+
retries+=1
|
| 43 |
+
missing_inputs=tuple(item for task in tasks for item in task.required_user_inputs if item); plan=IntegratedAnswerPlan(needs=tuple(tasks),resolutions=integrated.resolutions,blocked_task_ids=tuple(integrated.contradiction_task_ids),missing_evidence_task_ids=quality.missing_evidence_task_ids,missing_user_inputs=missing_inputs if quality.resolution_score<1.0 else ()); composed=self.composer.compose(plan); answer=composed.answer or ""; terminology=self.japanese.terminology_violations(answer) if answer else []; security=self.security.check_output(answer=answer,forbidden_literals=packet.forbidden_claims,unexecuted_completion_claim=False)
|
| 44 |
+
major=[t for t in tasks if t.priority=="primary"]; resolved=set(composed.resolved_task_ids); all_major=all(t.task_id in resolved for t in major); actionable=all((not t.actionability_required) or any(r.task_id==t.task_id and bool(r.action_steps) for r in integrated.resolutions) for t in tasks); evidence_complete=not quality.missing_evidence_task_ids; fp_ok="false_premise_uncorrected" not in quality.violations
|
| 45 |
+
sat_ok,sat_violations=self.satisfaction.evaluate(composed.mode,RuntimeSatisfactionSignals(all_major,evidence_complete,True,actionable,fp_ok,unsupported_claim_count=int("unsupported_claim" in quality.violations),terminology_violation_count=len(terminology))); violations=list(dict.fromkeys([*quality.violations,*sat_violations,*security.violations,*("terminology_violation" for _ in terminology)])); passed=bool(quality.passed and sat_ok and security.passed and not terminology)
|
| 46 |
+
if passed: self.state.update(session_id,active_topics=[t.intent for t in tasks],last_user_need=normalized_need,user_conditions=dict(state.user_conditions))
|
| 47 |
+
failure_class=None
|
| 48 |
+
if not passed:
|
| 49 |
+
if "grounding_conflict" in violations: failure_class="grounding_conflict"
|
| 50 |
+
elif {"major_need_missing","evidence_incomplete","conversation_not_resolved"}.intersection(violations): failure_class="coverage_defect"
|
| 51 |
+
elif not security.passed: failure_class="safety_rejection"
|
| 52 |
+
else: failure_class="runtime_failure"
|
| 53 |
+
return FinalResponse(request_id=request_id,session_id=session_id,turn_id=turn_id,answer=composed.answer,answered_task_ids=list(composed.resolved_task_ids),unresolved_task_ids=list(composed.unresolved_task_ids),evidence_ids=list(integrated.evidence_ids),resolution_score=quality.resolution_score,passed=passed,resolution_mode=composed.mode,clarification_questions=list(composed.clarification_questions),failure_class=failure_class,violations=violations)
|
| 54 |
+
@staticmethod
|
| 55 |
+
def _failure(request_id,session_id,turn_id,mode,failure_class,violations)->FinalResponse:
|
| 56 |
+
return FinalResponse(request_id=request_id,session_id=session_id,turn_id=turn_id,answer=None,answered_task_ids=[],unresolved_task_ids=[],evidence_ids=[],resolution_score=0.0,passed=False,resolution_mode=mode,failure_class=failure_class,violations=violations)
|
runtime/state.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass, field
|
| 4 |
+
|
| 5 |
+
from .japanese_skills import ConversationContext
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class SessionState:
|
| 10 |
+
active_topics: list[str] = field(default_factory=list)
|
| 11 |
+
last_user_need: str = ""
|
| 12 |
+
user_conditions: dict[str, str] = field(default_factory=dict)
|
| 13 |
+
|
| 14 |
+
def as_japanese_context(self) -> ConversationContext:
|
| 15 |
+
return ConversationContext(
|
| 16 |
+
active_topics=tuple(self.active_topics),
|
| 17 |
+
last_user_need=self.last_user_need,
|
| 18 |
+
user_conditions=tuple(sorted(self.user_conditions.items())),
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class StateStore:
|
| 23 |
+
def __init__(self):
|
| 24 |
+
self._states: dict[str, SessionState] = {}
|
| 25 |
+
|
| 26 |
+
def get(self, session_id: str) -> SessionState:
|
| 27 |
+
return self._states.setdefault(session_id, SessionState())
|
| 28 |
+
|
| 29 |
+
def update(self, session_id: str, *, active_topics: list[str] | None = None, last_user_need: str | None = None, user_conditions: dict[str, str] | None = None) -> SessionState:
|
| 30 |
+
state = self.get(session_id)
|
| 31 |
+
if active_topics is not None:
|
| 32 |
+
state.active_topics = list(active_topics)
|
| 33 |
+
if last_user_need is not None:
|
| 34 |
+
state.last_user_need = last_user_need
|
| 35 |
+
if user_conditions is not None:
|
| 36 |
+
state.user_conditions = dict(user_conditions)
|
| 37 |
+
return state
|
| 38 |
+
|
| 39 |
+
def delete(self, session_id: str) -> bool:
|
| 40 |
+
return self._states.pop(session_id, None) is not None
|
runtime/v8_bridge.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Protocol
|
| 4 |
+
|
| 5 |
+
from .schemas import NeedTask, RoleResult, SharedRolePacket
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class V8Adapter(Protocol):
|
| 9 |
+
async def analyze_need(self, message: str, context: dict[str, Any]) -> dict[str, Any]: ...
|
| 10 |
+
async def compare_results(self, packet: SharedRolePacket, results: list[RoleResult]) -> dict[str, Any]: ...
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class V8Bridge:
|
| 14 |
+
def __init__(self, adapter: V8Adapter): self.adapter = adapter
|
| 15 |
+
|
| 16 |
+
async def analyze_need(self, message: str, context: dict[str, Any]) -> tuple[str, str, list[NeedTask]]:
|
| 17 |
+
raw = await self.adapter.analyze_need(message, context)
|
| 18 |
+
tasks = [NeedTask.model_validate(item) for item in raw.get("tasks", [])]
|
| 19 |
+
if not tasks: raise ValueError("v8_preflight_missing_tasks")
|
| 20 |
+
return str(raw.get("normalized_need") or message).strip(), str(raw.get("audience") or "general").strip() or "general", tasks
|
| 21 |
+
|
| 22 |
+
async def compare(self, packet: SharedRolePacket, results: list[RoleResult]) -> dict[str, Any]:
|
| 23 |
+
raw = await self.adapter.compare_results(packet, results)
|
| 24 |
+
if not isinstance(raw, dict): raise TypeError("v8_compare_invalid")
|
| 25 |
+
return raw
|