diff --git a/.gitignore b/.gitignore index dde1b02..4ae3b95 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ benchmark-results/live/*.json benchmark-results/mteb-reranking/ benchmark-results/*original-check.json confidence-runs/ +runs/ artifacts/*confidence*.joblib dist/ build/ diff --git a/README.ko.md b/README.ko.md index 7aa6517..71e728b 100644 --- a/README.ko.md +++ b/README.ko.md @@ -14,7 +14,7 @@ Azure OpenAI 기반 zero-shot candidate reranking에 집중합니다. 주요 특징: - listwise RankGPT, pairwise PRP, tournament 방식 TourRank-r, - uncertainty-aware AcuRank built-in Strategy + uncertainty-aware AcuRank, confidence-gain built-in Strategy - 커스텀 reranking 메소드를 위한 public Strategy contract - vendor 독립 LLM 호출을 위한 `ModelClient` / `ModelProvider` 경계 - 엄격한 JSON parsing과 fast-fail 오류 정책 @@ -68,6 +68,8 @@ for result in results: | `tourrank_r`, `rounds=2` | `TourRankStrategy` | 중간 수준 호출 예산에서 listwise보다 강한 품질을 원할 때 | RankGPT보다 호출 수가 많지만 TourRank-10보다 훨씬 가벼움 | | `tourrank_r`, `rounds=10` | `TourRankStrategy` | 품질 중심 offline reranking, 논문식 평가, 최종 reranking처럼 latency를 감수할 수 있을 때 | 일반 사용 기준 built-in 중 호출 비용이 가장 큼 | | `acurank` | `AcuRankStrategy` | top-k 경계 근처의 불확실한 후보에 listwise 호출을 집중하고 싶을 때 | TrueSkill 상태를 사용하며, cap을 두지 않으면 기본 listwise보다 호출 수가 늘 수 있음 | +| `confidence_gain` | `ConfidenceGainStrategy` | query-only 및 query+context confidence scorer를 학습했고 `Conf(Q+C)-Conf(Q)`로 문서를 정렬하고 싶을 때 | scorer artifact와 answer generator hook이 필요함. 문서 수가 `N`이면 runtime에서 answer generation `N+1`회, confidence scoring `N+1`회를 수행함 | +| `cbdr` | `CBDRStrategy` | answerability confidence scorer를 학습했고 `Conf(Q)`가 충분히 높으면 context reranking을 건너뛰고, 낮으면 confidence gain으로 정렬하고 싶을 때 | scorer artifact와 answer generator hook이 필요함. skip path는 answer generation 1회와 confidence scoring 1회, rerank path는 각각 `N+1`회를 수행함 | | Custom strategy | `RerankStrategy` / `AsyncRerankStrategy` | deterministic business logic, proprietary ranking, 새 research method가 필요할 때 | ranking contract와 validation을 직접 책임져야 함 | ### 전략 적용 방법 @@ -308,8 +310,74 @@ worker thread들이 공유하므로 thread-safe backend에서만 `max_workers>1` 합니다. 첫 worker error에서 pending work를 취소하지만, 이미 시작된 Python thread는 background에서 완료될 수 있습니다. -`ranksmith.confidence_generation`은 raw answer/relevance 예시에 대해 closed -model을 호출해 confidence training용 supervised canonical JSONL을 생성할 수 +`ConfidenceGainStrategy`는 별도 sync reranking Strategy입니다. 두 개의 compatible +confidence estimator와 answer generator hook을 받아 사용합니다. + +```python +from ranksmith.confidence import StructuralConfidenceEstimator +from ranksmith.strategies import ConfidenceGainStrategy + +base_estimator = StructuralConfidenceEstimator.from_artifact( + "query-answerability.joblib" +) +context_estimator = StructuralConfidenceEstimator.from_artifact( + "query-context-answerability.joblib" +) + +strategy = ConfidenceGainStrategy( + base_estimator=base_estimator, + context_estimator=context_estimator, + answer_generator=my_answer_generator, +) +``` + +이 Strategy는 `Conf(Q+C)-Conf(Q)` 기준으로 정렬합니다. CBDR retrieval skip, async +reranking, scorer 학습은 구현하지 않습니다. + +`CBDRStrategy`는 sync reranking-side router입니다. retriever와 통합하거나 upstream +retrieval 호출 자체를 멈추지는 않습니다. 이미 `rerank(...)`에 documents가 전달된 +뒤 context reranking을 건너뛸지 결정합니다. + +```python +from ranksmith.integrations import AzureAnswerGenerator +from ranksmith.strategies import CBDRStrategy + +answer_generator = AzureAnswerGenerator.from_env() + +strategy = CBDRStrategy.from_artifacts( + base_artifact_path="query-answerability.joblib", + context_artifact_path="query-context-answerability.joblib", + answer_generator=answer_generator, + skip_threshold=0.8, +) + +results = strategy.rerank(query=query, documents=documents) +``` + +`Conf(Q) >= skip_threshold`이면 original document order를 보존하고 +`metadata["cbdr_skipped"] == True`를 남깁니다. `Conf(Q) < skip_threshold`이면 +모든 문서를 scoring한 뒤 `top_k`를 적용합니다. +`AzureAnswerGenerator`는 `ranksmith.confidence_generation`과 같은 no-answer +sentinel 계약을 사용하며, 답할 수 없으면 `{"answer":"__NO_ANSWER__"}`를 +반환하게 합니다. + +compatible scorer artifact가 있으면 benchmark runner에서도 CBDR을 명시적으로 실행할 +수 있습니다. + +```bash +uv run python scripts/compare_reranking.py \ + --dataset benchmark-cache \ + --cache-dir .benchmark-cache/askubuntu-bm25 \ + --candidates benchmark-results/pyserini/askubuntu-bm25-top20.trec \ + --algorithm cbdr \ + --cbdr-base-artifact query-answerability.joblib \ + --cbdr-context-artifact query-context-answerability.joblib \ + --cbdr-max-document-chars 4000 \ + --allow-live +``` + +`ranksmith.confidence_generation`은 raw answer/relevance/answerability 예시에 대해 +closed model을 호출해 confidence training용 supervised canonical JSONL을 생성할 수 있습니다. 이 모듈은 reranking Strategy가 아니라 데이터 생성 utility입니다. ### compatible confidence scorer 학습 @@ -341,6 +409,102 @@ result = train_confidence_scorer( print(result.export_path) ``` +### LM Studio 로컬 confidence pipeline + +CBDR에는 두 answerability scorer가 필요합니다. query-only 예시에서 `Conf(Q)`를, +query+context 예시에서 `Conf(Q+C)`를 학습합니다. LM Studio는 supervised label +생성에만 사용하며, scorer artifact는 그대로 `ranksmith.confidence_training`이 +학습합니다. + +로컬 OpenAI-compatible server를 시작하고 loaded model을 지정합니다. + +```bash +lms server start +export LMSTUDIO_MODEL=google/gemma-4-12b +``` + +canonical JSONL dataset을 생성합니다. + +```bash +uv run python scripts/generate_confidence_dataset.py \ + --task query_answerability_confidence \ + --provider lmstudio \ + --input runs/confidence/local/raw/query_answerability.jsonl \ + --output runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --resume + +uv run python scripts/generate_confidence_dataset.py \ + --task query_context_answerability_confidence \ + --provider lmstudio \ + --input runs/confidence/local/raw/query_context_answerability.jsonl \ + --output runs/confidence/local/canonical/query_context_answerability_confidence.jsonl \ + --max-context-chars 8000 \ + --resume +``` + +scorer를 범용적으로 다루기 전에 source/group balance를 확인합니다. + +```bash +uv run python scripts/report_confidence_dataset.py \ + --task query_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_answerability_confidence.jsonl +``` + +CBDR-compatible scorer artifact를 학습합니다. + +```bash +uv run python scripts/train_confidence_scorer.py \ + --task query_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --output-dir runs/confidence/local/training/query_answerability \ + --export-path runs/confidence/local/artifacts/query_answerability.joblib \ + --encoder-name bert-base-uncased \ + --max-length 256 + +uv run python scripts/train_confidence_scorer.py \ + --task query_context_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_context_answerability_confidence.jsonl \ + --output-dir runs/confidence/local/training/query_context_answerability \ + --export-path runs/confidence/local/artifacts/query_context_answerability.joblib \ + --encoder-name bert-base-uncased \ + --max-length 256 +``` + +학습된 artifact를 LM Studio runtime과 함께 사용합니다. + +```python +from ranksmith.integrations import LMStudioModelProvider, ProviderAnswerGenerator +from ranksmith.strategies import CBDRStrategy + +answer_generator = ProviderAnswerGenerator( + provider=LMStudioModelProvider(model="google/gemma-4-12b") +) + +strategy = CBDRStrategy.from_artifacts( + base_artifact_path="runs/confidence/local/artifacts/query_answerability.joblib", + context_artifact_path="runs/confidence/local/artifacts/query_context_answerability.joblib", + answer_generator=answer_generator, + skip_threshold=0.8, +) +``` + +benchmark runner도 같은 provider를 사용할 수 있습니다. 이 명령은 live 실행이므로 +`--allow-live`가 필요합니다. summary artifact를 만들고 커밋하기 전까지는 benchmark +품질 수치로 주장하지 않습니다. + +```bash +uv run python scripts/compare_reranking.py \ + --dataset benchmark-cache \ + --cache-dir .benchmark-cache/askubuntu-bm25 \ + --candidates benchmark-results/pyserini/askubuntu-bm25-top20.trec \ + --algorithm cbdr \ + --cbdr-answer-provider lmstudio \ + --cbdr-base-artifact runs/confidence/local/artifacts/query_answerability.joblib \ + --cbdr-context-artifact runs/confidence/local/artifacts/query_context_answerability.joblib \ + --lmstudio-model google/gemma-4-12b \ + --allow-live +``` + ## 실전 가이드 (Examples) 실행 가능한 예제는 `examples/` 폴더에 있습니다. @@ -392,6 +556,7 @@ top-5만 출력할 수 있습니다. Live LLM 호출에는 Azure OpenAI deployme - [`benchmark-results/live/askubuntu-bm25-top20-default-live.v3.merged.json`](https://github.com/pko89403/ranksmith/blob/main/benchmark-results/live/askubuntu-bm25-top20-default-live.v3.merged.json) - [`benchmark-results/pyserini/askubuntu-bm25-top20.trec`](https://github.com/pko89403/ranksmith/blob/main/benchmark-results/pyserini/askubuntu-bm25-top20.trec) +- [`benchmark-results/askubuntu-bm25-top20-cbdr-live.json`](https://github.com/pko89403/ranksmith/blob/main/benchmark-results/askubuntu-bm25-top20-cbdr-live.json) (optional `cbdr` method, 별도 run) | Method | NDCG@5 | MRR@5 | Recall@5 | Valid rows | Invalid rate | Nominal LLM calls/query | LLM row attempts/query incl. retries | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -402,13 +567,26 @@ top-5만 출력할 수 있습니다. Live LLM 호출에는 Azure OpenAI deployme | `tourrank_r2` | 0.4236 | 0.5725 | 0.3601 | 361/361 | 0.000 | 8 | 1.03 | | `setwise_hs_s10` | 0.3653 | 0.5059 | 0.3005 | 361/361 | 0.000 | 12 | 1.00 | | `prp_sliding_p1` | 0.4065 | 0.5818 | 0.3277 | 361/361 | 0.000 | 38 | 1.00 | +| `cbdr` *(scorer: TriviaQA, 도메인 밖)* | 0.2259 | 0.3458 | 0.1867 | 361/361 | 0.000 | 21 | 1.00 | `tourrank_r2`는 NDCG@5와 Recall@5가 가장 높았고, `prp_sliding_p1`은 MRR@5가 가장 높았습니다. `single_call_listwise@20`은 one-shot listwise baseline입니다. `rankgpt_sw_w5`는 이 top-20 설정의 실제 sliding-window listwise baseline입니다. `acurank_k5_b1`은 AcuRank uncertainty boundary를 `@5` 평가 cutoff와 맞춘 설정입니다. `setwise_hs_s10`은 20개 후보에서 평가 대상 top-5만 추출하는 실용적인 -Setwise Heapsort 설정입니다. +Setwise Heapsort 설정입니다. `cbdr`은 [LM Studio 로컬 confidence +pipeline](#lm-studio-로컬-confidence-pipeline)에 문서화된 `Conf(Q)`/`Conf(Q+C)` +스코어러 두 개를 사용하며, TriviaQA로 학습해 AskUbuntu 기준 도메인 밖입니다. +이 벤치마크에서는 BM25 baseline보다 낮은 점수를 기록했고, 이기도록 튜닝하지 +않고 측정된 그대로 보고합니다. + +왜 이기게 튜닝하지 않는가: 스코어러를 이 벤치마크 분포에 맞추면 알고리즘의 +일반적인 품질이 아니라 AskUbuntu에 대한 과적합을 측정하게 됩니다. 이는 +smoke/partial run이나 cherry-pick된 수치를 벤치마크 품질로 보고하지 않는다는 +이 프로젝트의 보고 규칙([`docs/benchmarks/bm25_top20_reranking.md`](docs/benchmarks/bm25_top20_reranking.md#reporting-rules))과도 +어긋납니다. 더 강한 domain-in 스코어러를 만드는 건 정당한 후속 작업이지만, +그건 더 나은 artifact를 학습해서 같은 커맨드를 다시 돌리는 것이지 보고 +방식을 바꾸는 게 아닙니다. 재시도 후에도 `single_call_listwise@20` 2개 row와 `acurank_k5_b1` 5개 row가 invalid로 남았습니다. 이 row는 보정하지 않고 invalid rate에 반영했습니다. diff --git a/README.md b/README.md index 6abbf86..0530bf8 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ documents. Highlights: -- Built-in listwise RankGPT, pairwise PRP, tournament-style TourRank-r, and - uncertainty-aware AcuRank strategies +- Built-in listwise RankGPT, pairwise PRP, tournament-style TourRank-r, + uncertainty-aware AcuRank, and confidence-gain strategies - Public strategy contracts for custom reranking methods - `ModelClient` / `ModelProvider` boundary for vendor-independent LLM calls - Strict JSON parsing and fast-fail error behavior @@ -70,6 +70,8 @@ logic (Algorithm). | `tourrank_r`, `rounds=2` | `TourRankStrategy` | You want stronger quality than listwise on a moderate call budget. | More calls than RankGPT, much fewer than TourRank-10. | | `tourrank_r`, `rounds=10` | `TourRankStrategy` | You are doing quality-focused offline reranking, paper-style evaluation, or final reranking where latency is acceptable. | Highest call cost among built-in methods in normal use. | | `acurank` | `AcuRankStrategy` | You want adaptive listwise reranking that spends calls on uncertain candidates near the top-k boundary. | Uses TrueSkill state and may issue more calls than basic listwise reranking unless capped. | +| `confidence_gain` | `ConfidenceGainStrategy` | You have trained query-only and query+context confidence scorers and want to rank documents by `Conf(Q+C)-Conf(Q)`. | Requires scorer artifacts and an answer generator hook. Runtime calls answer generation `N+1` times and confidence scoring `N+1` times for `N` documents. | +| `cbdr` | `CBDRStrategy` | You have trained answerability confidence scorers and want to skip context reranking when `Conf(Q)` is already high, otherwise rerank by confidence gain. | Requires scorer artifacts and an answer generator hook. Skip path uses 1 answer generation call and 1 confidence score; rerank path uses `N+1` answer generations and `N+1` confidence scores. | | Custom strategy | `RerankStrategy` / `AsyncRerankStrategy` | You need deterministic business logic, a proprietary ranking process, or a new research method. | You own the ranking contract and validation behavior. | ### Applying a Strategy @@ -312,9 +314,76 @@ encoder and scorer instances across worker threads, so use `max_workers>1` only with thread-safe backends. It cancels pending work on the first worker error, but Python threads that have already started may finish in the background. +`ConfidenceGainStrategy` is a separate sync reranking Strategy that consumes +two compatible confidence estimators and an answer generator hook: + +```python +from ranksmith.confidence import StructuralConfidenceEstimator +from ranksmith.strategies import ConfidenceGainStrategy + +base_estimator = StructuralConfidenceEstimator.from_artifact( + "query-answerability.joblib" +) +context_estimator = StructuralConfidenceEstimator.from_artifact( + "query-context-answerability.joblib" +) + +strategy = ConfidenceGainStrategy( + base_estimator=base_estimator, + context_estimator=context_estimator, + answer_generator=my_answer_generator, +) +``` + +It ranks by `Conf(Q+C)-Conf(Q)`. It does not implement CBDR retrieval skipping, +async reranking, or scorer training. + +`CBDRStrategy` is a sync reranking-side router. It does not integrate with a +retriever or stop upstream retrieval calls; it only skips context reranking once +documents have already been passed to `rerank(...)`. + +```python +from ranksmith.integrations import AzureAnswerGenerator +from ranksmith.strategies import CBDRStrategy + +answer_generator = AzureAnswerGenerator.from_env() + +strategy = CBDRStrategy.from_artifacts( + base_artifact_path="query-answerability.joblib", + context_artifact_path="query-context-answerability.joblib", + answer_generator=answer_generator, + skip_threshold=0.8, +) + +results = strategy.rerank(query=query, documents=documents) +``` + +When `Conf(Q) >= skip_threshold`, results preserve original document order and +include `metadata["cbdr_skipped"] == True`. When `Conf(Q) < skip_threshold`, all +documents are scored before `top_k` slicing. +`AzureAnswerGenerator` uses the same no-answer sentinel contract as +`ranksmith.confidence_generation` and returns `{"answer":"__NO_ANSWER__"}` when +the model cannot answer. + +The benchmark runner can execute CBDR explicitly when compatible scorer +artifacts are available: + +```bash +uv run python scripts/compare_reranking.py \ + --dataset benchmark-cache \ + --cache-dir .benchmark-cache/askubuntu-bm25 \ + --candidates benchmark-results/pyserini/askubuntu-bm25-top20.trec \ + --algorithm cbdr \ + --cbdr-base-artifact query-answerability.joblib \ + --cbdr-context-artifact query-context-answerability.joblib \ + --cbdr-max-document-chars 4000 \ + --allow-live +``` + `ranksmith.confidence_generation` can create supervised canonical JSONL for -confidence training by calling a closed model over raw answer or relevance -examples. It is a data-generation utility, not a reranking Strategy. +confidence training by calling a closed model over raw answer, relevance, or +answerability examples. It is a data-generation utility, not a reranking +Strategy. ### Training a compatible confidence scorer @@ -345,6 +414,102 @@ result = train_confidence_scorer( print(result.export_path) ``` +### Local LM Studio confidence pipeline + +For CBDR, train two answerability scorers: `Conf(Q)` from query-only examples +and `Conf(Q+C)` from query+context examples. LM Studio is used only to generate +supervised labels; the scorer artifact is still trained by +`ranksmith.confidence_training`. + +Start the local OpenAI-compatible server and select the loaded model: + +```bash +lms server start +export LMSTUDIO_MODEL=google/gemma-4-12b +``` + +Generate canonical JSONL datasets: + +```bash +uv run python scripts/generate_confidence_dataset.py \ + --task query_answerability_confidence \ + --provider lmstudio \ + --input runs/confidence/local/raw/query_answerability.jsonl \ + --output runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --resume + +uv run python scripts/generate_confidence_dataset.py \ + --task query_context_answerability_confidence \ + --provider lmstudio \ + --input runs/confidence/local/raw/query_context_answerability.jsonl \ + --output runs/confidence/local/canonical/query_context_answerability_confidence.jsonl \ + --max-context-chars 8000 \ + --resume +``` + +Review source and group balance before treating the scorer as general: + +```bash +uv run python scripts/report_confidence_dataset.py \ + --task query_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_answerability_confidence.jsonl +``` + +Train CBDR-compatible scorer artifacts: + +```bash +uv run python scripts/train_confidence_scorer.py \ + --task query_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --output-dir runs/confidence/local/training/query_answerability \ + --export-path runs/confidence/local/artifacts/query_answerability.joblib \ + --encoder-name bert-base-uncased \ + --max-length 256 + +uv run python scripts/train_confidence_scorer.py \ + --task query_context_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_context_answerability_confidence.jsonl \ + --output-dir runs/confidence/local/training/query_context_answerability \ + --export-path runs/confidence/local/artifacts/query_context_answerability.joblib \ + --encoder-name bert-base-uncased \ + --max-length 256 +``` + +Use the artifacts with LM Studio at runtime: + +```python +from ranksmith.integrations import LMStudioModelProvider, ProviderAnswerGenerator +from ranksmith.strategies import CBDRStrategy + +answer_generator = ProviderAnswerGenerator( + provider=LMStudioModelProvider(model="google/gemma-4-12b") +) + +strategy = CBDRStrategy.from_artifacts( + base_artifact_path="runs/confidence/local/artifacts/query_answerability.joblib", + context_artifact_path="runs/confidence/local/artifacts/query_context_answerability.joblib", + answer_generator=answer_generator, + skip_threshold=0.8, +) +``` + +The benchmark runner can use the same provider. This command is live and +requires `--allow-live`; it does not imply any benchmark quality number unless +summary artifacts are produced and committed. + +```bash +uv run python scripts/compare_reranking.py \ + --dataset benchmark-cache \ + --cache-dir .benchmark-cache/askubuntu-bm25 \ + --candidates benchmark-results/pyserini/askubuntu-bm25-top20.trec \ + --algorithm cbdr \ + --cbdr-answer-provider lmstudio \ + --cbdr-base-artifact runs/confidence/local/artifacts/query_answerability.joblib \ + --cbdr-context-artifact runs/confidence/local/artifacts/query_context_answerability.joblib \ + --lmstudio-model google/gemma-4-12b \ + --allow-live +``` + ## Examples Runnable examples live in the `examples/` directory. @@ -396,6 +561,7 @@ algorithm run. The committed evidence artifacts are: - [`benchmark-results/live/askubuntu-bm25-top20-default-live.v3.merged.json`](https://github.com/pko89403/ranksmith/blob/main/benchmark-results/live/askubuntu-bm25-top20-default-live.v3.merged.json) - [`benchmark-results/pyserini/askubuntu-bm25-top20.trec`](https://github.com/pko89403/ranksmith/blob/main/benchmark-results/pyserini/askubuntu-bm25-top20.trec) +- [`benchmark-results/askubuntu-bm25-top20-cbdr-live.json`](https://github.com/pko89403/ranksmith/blob/main/benchmark-results/askubuntu-bm25-top20-cbdr-live.json) (optional `cbdr` method, run separately) | Method | NDCG@5 | MRR@5 | Recall@5 | Valid rows | Invalid rate | Nominal LLM calls/query | LLM row attempts/query incl. retries | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -406,13 +572,27 @@ algorithm run. The committed evidence artifacts are: | `tourrank_r2` | 0.4236 | 0.5725 | 0.3601 | 361/361 | 0.000 | 8 | 1.03 | | `setwise_hs_s10` | 0.3653 | 0.5059 | 0.3005 | 361/361 | 0.000 | 12 | 1.00 | | `prp_sliding_p1` | 0.4065 | 0.5818 | 0.3277 | 361/361 | 0.000 | 38 | 1.00 | +| `cbdr` *(scorers: TriviaQA, out-of-domain)* | 0.2259 | 0.3458 | 0.1867 | 361/361 | 0.000 | 21 | 1.00 | `tourrank_r2` had the best NDCG@5 and Recall@5, while `prp_sliding_p1` had the best MRR@5. `single_call_listwise@20` is the one-shot listwise baseline. `rankgpt_sw_w5` is the true sliding-window listwise baseline for this top-20 setup. `acurank_k5_b1` aligns AcuRank's uncertainty boundary with the `@5` evaluation cutoff. `setwise_hs_s10` is a practical Setwise Heapsort setting -that extracts only the evaluated top-5 from 20 candidates. +that extracts only the evaluated top-5 from 20 candidates. `cbdr` uses the two +`Conf(Q)`/`Conf(Q+C)` scorers documented under [Local LM Studio confidence +pipeline](#local-lm-studio-confidence-pipeline), trained on TriviaQA (out-of-domain +for AskUbuntu); it scores below the BM25 baseline here and is reported as +measured, not tuned to win. + +Why not tune it to win: fitting the scorer to this benchmark's distribution +would measure overfitting to AskUbuntu, not the algorithm's general quality, +which conflicts with this project's reporting rule that smoke/partial runs +and cherry-picked numbers are never reported as benchmark quality (see +[`docs/benchmarks/bm25_top20_reranking.md`](docs/benchmarks/bm25_top20_reranking.md#reporting-rules)). +A stronger in-domain scorer is a legitimate follow-up, but that means +training a better artifact and re-running this exact command, not adjusting +the report. After retries, 2 `single_call_listwise@20` rows and 5 `acurank_k5_b1` rows remained invalid. They are included in the invalid-rate accounting instead of diff --git a/benchmark-results/askubuntu-bm25-top20-cbdr-live.checkpoint.jsonl b/benchmark-results/askubuntu-bm25-top20-cbdr-live.checkpoint.jsonl new file mode 100644 index 0000000..a86116d --- /dev/null +++ b/benchmark-results/askubuntu-bm25-top20-cbdr-live.checkpoint.jsonl @@ -0,0 +1,361 @@ +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query0", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.08333333333333333}, "query_id": "test_query0", "ranked_ids": ["negative_test_query36_00011", "apositive_test_query130_00000", "negative_test_query9_00017", "negative_test_query221_00006", "apositive_test_query0_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query1", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query1", "ranked_ids": ["negative_test_query1_00005", "negative_test_query1_00009", "negative_test_query331_00002", "apositive_test_query188_00002", "negative_test_query61_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query10", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.18154179253735267, "recall@5": 0.3333333333333333}, "query_id": "test_query10", "ranked_ids": ["negative_test_query10_00000", "negative_test_query10_00011", "negative_test_query10_00005", "negative_test_query135_00014", "apositive_test_query10_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query100", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.2}, "query_id": "test_query100", "ranked_ids": ["apositive_test_query181_00000", "apositive_test_query100_00000", "negative_test_query100_00009", "negative_test_query290_00015", "negative_test_query100_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query101", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.38356636737133565, "recall@5": 0.2}, "query_id": "test_query101", "ranked_ids": ["negative_test_query68_00009", "apositive_test_query101_00005", "apositive_test_query101_00002", "apositive_test_query291_00000", "apositive_test_query73_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query102", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query102", "ranked_ids": ["negative_test_query209_00006", "apositive_test_query20_00001", "negative_test_query108_00009", "negative_test_query131_00009", "negative_test_query203_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query103", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.14285714285714285}, "query_id": "test_query103", "ranked_ids": ["negative_test_query185_00003", "negative_test_query307_00004", "apositive_test_query103_00005", "negative_test_query103_00011", "apositive_test_query185_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query104", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query104", "ranked_ids": ["negative_test_query104_00004", "negative_test_query104_00003", "negative_test_query104_00015", "negative_test_query104_00009", "negative_test_query104_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query105", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.46927872602275644, "recall@5": 0.3333333333333333}, "query_id": "test_query105", "ranked_ids": ["apositive_test_query105_00000", "negative_test_query105_00005", "negative_test_query142_00010", "negative_test_query313_00013", "negative_test_query79_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query106", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6548086577531307, "recall@5": 0.42857142857142855}, "query_id": "test_query106", "ranked_ids": ["apositive_test_query106_00000", "negative_test_query326_00008", "apositive_test_query106_00005", "apositive_test_query106_00001", "negative_test_query212_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query107", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query107", "ranked_ids": ["negative_test_query46_00012", "negative_test_query105_00015", "negative_test_query333_00005", "apositive_test_query221_00001", "negative_test_query142_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query108", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6548086577531307, "recall@5": 0.375}, "query_id": "test_query108", "ranked_ids": ["apositive_test_query108_00000", "negative_test_query250_00009", "apositive_test_query108_00002", "apositive_test_query108_00003", "negative_test_query311_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query109", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.2}, "query_id": "test_query109", "ranked_ids": ["apositive_test_query319_00008", "apositive_test_query82_00008", "negative_test_query109_00012", "negative_test_query268_00010", "apositive_test_query109_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query11", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.4703652827859579, "recall@5": 0.25}, "query_id": "test_query11", "ranked_ids": ["apositive_test_query11_00006", "negative_test_query132_00013", "negative_test_query309_00002", "negative_test_query358_00005", "apositive_test_query11_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query110", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query110", "ranked_ids": ["negative_test_query89_00006", "negative_test_query110_00014", "apositive_test_query180_00001", "apositive_test_query242_00006", "apositive_test_query291_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query111", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query111", "ranked_ids": ["negative_test_query277_00006", "negative_test_query277_00010", "negative_test_query65_00007", "negative_test_query243_00001", "negative_test_query58_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query112", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query112", "ranked_ids": ["negative_test_query112_00004", "apositive_test_query36_00003", "negative_test_query112_00013", "negative_test_query80_00007", "negative_test_query6_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query113", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.16666666666666666}, "query_id": "test_query113", "ranked_ids": ["apositive_test_query186_00005", "negative_test_query113_00003", "apositive_test_query113_00000", "negative_test_query67_00014", "negative_test_query223_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query114", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.14285714285714285}, "query_id": "test_query114", "ranked_ids": ["negative_test_query114_00005", "apositive_test_query114_00003", "negative_test_query114_00002", "negative_test_query114_00001", "negative_test_query262_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query115", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.5296347172140421, "recall@5": 0.5}, "query_id": "test_query115", "ranked_ids": ["negative_test_query139_00000", "apositive_test_query115_00003", "apositive_test_query115_00000", "apositive_test_query115_00004", "negative_test_query115_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query116", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6131471927654584, "recall@5": 0.5}, "query_id": "test_query116", "ranked_ids": ["apositive_test_query116_00000", "negative_test_query116_00006", "negative_test_query116_00008", "negative_test_query116_00010", "negative_test_query116_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query117", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.125}, "query_id": "test_query117", "ranked_ids": ["negative_test_query117_00003", "apositive_test_query134_00004", "apositive_test_query117_00004", "negative_test_query117_00001", "negative_test_query117_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query118", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query118", "ranked_ids": ["negative_test_query23_00005", "apositive_test_query155_00005", "negative_test_query340_00007", "negative_test_query296_00010", "negative_test_query171_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query119", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.07692307692307693}, "query_id": "test_query119", "ranked_ids": ["apositive_test_query119_00005", "apositive_test_query221_00004", "negative_test_query359_00005", "negative_test_query157_00001", "apositive_test_query26_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query12", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query12", "ranked_ids": ["negative_test_query12_00003", "negative_test_query12_00011", "negative_test_query12_00017", "negative_test_query12_00004", "negative_test_query12_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query120", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query120", "ranked_ids": ["negative_test_query120_00008", "negative_test_query148_00014", "apositive_test_query32_00007", "negative_test_query196_00006", "negative_test_query120_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query121", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query121", "ranked_ids": ["negative_test_query121_00003", "apositive_test_query307_00010", "negative_test_query121_00008", "negative_test_query121_00000", "negative_test_query256_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query122", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query122", "ranked_ids": ["negative_test_query17_00012", "negative_test_query86_00001", "apositive_test_query173_00000", "negative_test_query86_00006", "apositive_test_query175_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query123", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.2}, "query_id": "test_query123", "ranked_ids": ["negative_test_query123_00013", "negative_test_query123_00008", "apositive_test_query123_00002", "negative_test_query123_00005", "negative_test_query155_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query124", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query124", "ranked_ids": ["negative_test_query93_00010", "negative_test_query123_00009", "apositive_test_query13_00004", "negative_test_query262_00011", "negative_test_query171_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query125", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.44149241373678083, "recall@5": 0.5}, "query_id": "test_query125", "ranked_ids": ["negative_test_query125_00013", "apositive_test_query125_00000", "apositive_test_query125_00003", "negative_test_query125_00000", "apositive_test_query286_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query126", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query126", "ranked_ids": ["negative_test_query126_00017", "negative_test_query190_00017", "apositive_test_query5_00002", "negative_test_query27_00013", "apositive_test_query330_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query127", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.11764705882352941}, "query_id": "test_query127", "ranked_ids": ["apositive_test_query127_00006", "negative_test_query94_00003", "apositive_test_query127_00000", "apositive_test_query94_00005", "negative_test_query94_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query128", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.125}, "query_id": "test_query128", "ranked_ids": ["apositive_test_query142_00000", "apositive_test_query216_00002", "negative_test_query283_00006", "apositive_test_query128_00001", "negative_test_query21_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query129", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query129", "ranked_ids": ["negative_test_query211_00012", "negative_test_query156_00017", "negative_test_query63_00006", "apositive_test_query308_00004", "negative_test_query63_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query13", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.38356636737133565, "recall@5": 0.15384615384615385}, "query_id": "test_query13", "ranked_ids": ["negative_test_query13_00001", "apositive_test_query13_00007", "apositive_test_query13_00002", "negative_test_query13_00002", "negative_test_query13_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query130", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.16666666666666666}, "query_id": "test_query130", "ranked_ids": ["apositive_test_query323_00004", "apositive_test_query284_00001", "negative_test_query289_00004", "apositive_test_query130_00005", "negative_test_query90_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query131", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6366824387328317, "recall@5": 0.5}, "query_id": "test_query131", "ranked_ids": ["apositive_test_query131_00001", "apositive_test_query131_00000", "apositive_test_query344_00003", "apositive_test_query50_00008", "negative_test_query357_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query132", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query132", "ranked_ids": ["negative_test_query132_00000", "negative_test_query123_00012", "negative_test_query81_00005", "apositive_test_query161_00007", "negative_test_query148_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query133", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.38356636737133565, "recall@5": 0.13333333333333333}, "query_id": "test_query133", "ranked_ids": ["apositive_test_query287_00001", "apositive_test_query133_00002", "apositive_test_query133_00008", "apositive_test_query129_00007", "negative_test_query310_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query134", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query134", "ranked_ids": ["negative_test_query359_00011", "negative_test_query134_00000", "negative_test_query218_00009", "apositive_test_query357_00000", "apositive_test_query142_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query135", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.3065735963827292, "recall@5": 0.5}, "query_id": "test_query135", "ranked_ids": ["negative_test_query135_00015", "negative_test_query135_00008", "apositive_test_query135_00000", "negative_test_query135_00017", "negative_test_query135_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query136", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6608397947263839, "recall@5": 0.26666666666666666}, "query_id": "test_query136", "ranked_ids": ["negative_test_query301_00001", "apositive_test_query136_00005", "apositive_test_query136_00002", "apositive_test_query136_00007", "apositive_test_query136_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query137", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query137", "ranked_ids": ["negative_test_query137_00012", "negative_test_query137_00000", "negative_test_query149_00013", "negative_test_query137_00014", "negative_test_query95_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query138", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query138", "ranked_ids": ["negative_test_query138_00013", "negative_test_query240_00000", "negative_test_query183_00004", "negative_test_query138_00004", "negative_test_query138_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query139", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.46927872602275644, "recall@5": 0.3333333333333333}, "query_id": "test_query139", "ranked_ids": ["apositive_test_query139_00001", "negative_test_query35_00010", "negative_test_query163_00000", "apositive_test_query35_00001", "negative_test_query81_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query14", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3903800499921017, "recall@5": 0.25}, "query_id": "test_query14", "ranked_ids": ["apositive_test_query14_00003", "negative_test_query199_00009", "negative_test_query14_00005", "negative_test_query155_00009", "negative_test_query14_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query140", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query140", "ranked_ids": ["negative_test_query140_00005", "negative_test_query18_00006", "apositive_test_query18_00001", "negative_test_query140_00001", "negative_test_query140_00018"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query141", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.14285714285714285}, "query_id": "test_query141", "ranked_ids": ["apositive_test_query141_00005", "apositive_test_query288_00005", "negative_test_query141_00010", "negative_test_query25_00006", "negative_test_query308_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query142", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query142", "ranked_ids": ["negative_test_query102_00001", "negative_test_query142_00003", "negative_test_query46_00002", "negative_test_query142_00009", "negative_test_query46_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query143", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6131471927654584, "recall@5": 0.5}, "query_id": "test_query143", "ranked_ids": ["apositive_test_query143_00000", "negative_test_query143_00010", "negative_test_query143_00014", "negative_test_query90_00014", "negative_test_query143_00015"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query144", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query144", "ranked_ids": ["negative_test_query315_00011", "negative_test_query331_00015", "negative_test_query144_00018", "negative_test_query166_00001", "negative_test_query144_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query145", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query145", "ranked_ids": ["negative_test_query190_00012", "apositive_test_query225_00002", "negative_test_query171_00003", "apositive_test_query213_00004", "apositive_test_query217_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query146", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.0625}, "query_id": "test_query146", "ranked_ids": ["apositive_test_query146_00004", "apositive_test_query3_00010", "apositive_test_query314_00004", "apositive_test_query161_00008", "apositive_test_query161_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query147", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.4}, "query_id": "test_query147", "ranked_ids": ["apositive_test_query147_00003", "apositive_test_query76_00001", "apositive_test_query147_00002", "negative_test_query258_00008", "negative_test_query53_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query148", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query148", "ranked_ids": ["negative_test_query148_00005", "negative_test_query148_00017", "negative_test_query210_00013", "negative_test_query112_00013", "negative_test_query80_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query149", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.3065735963827292, "recall@5": 0.5}, "query_id": "test_query149", "ranked_ids": ["negative_test_query149_00008", "negative_test_query149_00017", "apositive_test_query149_00000", "negative_test_query332_00004", "negative_test_query332_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query15", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6131471927654584, "recall@5": 0.5}, "query_id": "test_query15", "ranked_ids": ["apositive_test_query15_00001", "negative_test_query15_00015", "negative_test_query15_00004", "negative_test_query325_00011", "negative_test_query266_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query150", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query150", "ranked_ids": ["negative_test_query150_00010", "negative_test_query62_00009", "negative_test_query213_00007", "apositive_test_query112_00000", "negative_test_query250_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query151", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query151", "ranked_ids": ["negative_test_query126_00017", "negative_test_query190_00017", "negative_test_query126_00000", "negative_test_query151_00003", "apositive_test_query300_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query152", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.1}, "query_id": "test_query152", "ranked_ids": ["negative_test_query237_00011", "negative_test_query312_00014", "negative_test_query95_00017", "negative_test_query237_00009", "apositive_test_query152_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query153", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.830419897363192, "recall@5": 0.25}, "query_id": "test_query153", "ranked_ids": ["apositive_test_query153_00015", "apositive_test_query153_00001", "negative_test_query359_00003", "apositive_test_query153_00003", "apositive_test_query153_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query154", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query154", "ranked_ids": ["apositive_test_query33_00008", "apositive_test_query20_00003", "negative_test_query245_00006", "apositive_test_query66_00000", "negative_test_query356_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query155", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query155", "ranked_ids": ["negative_test_query155_00009", "negative_test_query155_00006", "apositive_test_query40_00004", "negative_test_query155_00003", "apositive_test_query3_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query156", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query156", "ranked_ids": ["negative_test_query49_00005", "apositive_test_query25_00009", "negative_test_query1_00012", "negative_test_query283_00001", "negative_test_query92_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query157", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query157", "ranked_ids": ["negative_test_query157_00005", "apositive_test_query237_00002", "apositive_test_query237_00004", "apositive_test_query237_00000", "negative_test_query237_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query158", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.1}, "query_id": "test_query158", "ranked_ids": ["negative_test_query223_00007", "negative_test_query26_00004", "apositive_test_query158_00000", "negative_test_query102_00015", "negative_test_query334_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query159", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6131471927654584, "recall@5": 0.5}, "query_id": "test_query159", "ranked_ids": ["apositive_test_query159_00001", "negative_test_query264_00013", "negative_test_query159_00017", "negative_test_query264_00017", "apositive_test_query212_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query16", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6843515475204855, "recall@5": 0.6}, "query_id": "test_query16", "ranked_ids": ["apositive_test_query16_00003", "apositive_test_query16_00001", "negative_test_query280_00007", "negative_test_query237_00010", "apositive_test_query16_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query160", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.07142857142857142}, "query_id": "test_query160", "ranked_ids": ["negative_test_query271_00010", "negative_test_query160_00004", "apositive_test_query355_00007", "apositive_test_query160_00000", "negative_test_query149_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query161", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query161", "ranked_ids": ["apositive_test_query301_00003", "negative_test_query358_00001", "apositive_test_query305_00002", "apositive_test_query301_00000", "apositive_test_query37_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query162", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.08333333333333333}, "query_id": "test_query162", "ranked_ids": ["negative_test_query177_00002", "negative_test_query240_00008", "apositive_test_query162_00009", "negative_test_query162_00001", "negative_test_query343_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query163", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query163", "ranked_ids": ["negative_test_query197_00002", "apositive_test_query152_00007", "negative_test_query163_00011", "negative_test_query224_00010", "negative_test_query350_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query164", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query164", "ranked_ids": ["negative_test_query165_00003", "negative_test_query344_00009", "negative_test_query85_00010", "negative_test_query131_00014", "negative_test_query309_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query165", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query165", "ranked_ids": ["negative_test_query159_00017", "negative_test_query264_00017", "negative_test_query215_00013", "negative_test_query327_00010", "negative_test_query348_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query166", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.3156484524795145, "recall@5": 0.3333333333333333}, "query_id": "test_query166", "ranked_ids": ["negative_test_query166_00013", "negative_test_query166_00001", "apositive_test_query166_00002", "apositive_test_query166_00005", "negative_test_query166_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query167", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query167", "ranked_ids": ["apositive_test_query288_00005", "negative_test_query141_00010", "negative_test_query167_00011", "negative_test_query278_00010", "negative_test_query167_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query168", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.16812753627111746, "recall@5": 0.25}, "query_id": "test_query168", "ranked_ids": ["negative_test_query6_00000", "negative_test_query344_00009", "negative_test_query85_00010", "apositive_test_query168_00002", "apositive_test_query83_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query169", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query169", "ranked_ids": ["apositive_test_query180_00001", "apositive_test_query330_00001", "negative_test_query89_00006", "negative_test_query72_00001", "negative_test_query72_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query17", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query17", "ranked_ids": ["negative_test_query17_00002", "negative_test_query17_00014", "negative_test_query17_00015", "negative_test_query10_00005", "negative_test_query17_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query170", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.7227265726449519, "recall@5": 0.3333333333333333}, "query_id": "test_query170", "ranked_ids": ["apositive_test_query170_00006", "apositive_test_query170_00001", "apositive_test_query170_00007", "negative_test_query170_00000", "negative_test_query297_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query171", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query171", "ranked_ids": ["negative_test_query171_00004", "negative_test_query277_00007", "negative_test_query24_00007", "negative_test_query346_00005", "negative_test_query261_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query172", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.34519134224686937, "recall@5": 0.25}, "query_id": "test_query172", "ranked_ids": ["negative_test_query192_00000", "apositive_test_query172_00004", "negative_test_query64_00006", "negative_test_query350_00009", "apositive_test_query172_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query173", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.16666666666666666}, "query_id": "test_query173", "ranked_ids": ["negative_test_query64_00006", "negative_test_query173_00003", "negative_test_query173_00013", "negative_test_query173_00006", "apositive_test_query173_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query174", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.8687949224876582, "recall@5": 0.23529411764705882}, "query_id": "test_query174", "ranked_ids": ["apositive_test_query174_00015", "apositive_test_query174_00001", "apositive_test_query174_00009", "apositive_test_query174_00008", "negative_test_query262_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query175", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.4912596920895758, "recall@5": 0.5}, "query_id": "test_query175", "ranked_ids": ["negative_test_query175_00010", "apositive_test_query175_00003", "negative_test_query175_00013", "apositive_test_query175_00000", "apositive_test_query175_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query176", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query176", "ranked_ids": ["negative_test_query144_00018", "negative_test_query200_00005", "negative_test_query337_00005", "negative_test_query224_00003", "apositive_test_query344_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query177", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.1}, "query_id": "test_query177", "ranked_ids": ["negative_test_query147_00005", "apositive_test_query177_00004", "apositive_test_query162_00010", "apositive_test_query162_00009", "negative_test_query147_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query178", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.3333333333333333}, "query_id": "test_query178", "ranked_ids": ["apositive_test_query178_00004", "negative_test_query327_00004", "apositive_test_query178_00005", "negative_test_query178_00007", "negative_test_query214_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query179", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query179", "ranked_ids": ["negative_test_query260_00012", "negative_test_query179_00014", "apositive_test_query316_00001", "apositive_test_query173_00000", "negative_test_query14_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query18", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.4}, "query_id": "test_query18", "ranked_ids": ["apositive_test_query18_00001", "negative_test_query18_00013", "apositive_test_query18_00000", "negative_test_query140_00003", "negative_test_query18_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query180", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.20210734650054757, "recall@5": 0.3333333333333333}, "query_id": "test_query180", "ranked_ids": ["negative_test_query176_00001", "negative_test_query95_00014", "negative_test_query180_00004", "apositive_test_query180_00001", "negative_test_query180_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query181", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query181", "ranked_ids": ["negative_test_query181_00007", "negative_test_query181_00006", "negative_test_query181_00010", "negative_test_query181_00017", "negative_test_query181_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query182", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.5, "recall@5": 1.0}, "query_id": "test_query182", "ranked_ids": ["negative_test_query182_00017", "negative_test_query182_00011", "apositive_test_query182_00000", "negative_test_query237_00001", "negative_test_query296_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query183", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query183", "ranked_ids": ["negative_test_query7_00007", "negative_test_query7_00008", "negative_test_query7_00014", "negative_test_query183_00018", "negative_test_query183_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query184", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query184", "ranked_ids": ["apositive_test_query33_00004", "negative_test_query101_00007", "apositive_test_query316_00000", "negative_test_query228_00011", "negative_test_query322_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query185", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query185", "ranked_ids": ["apositive_test_query307_00008", "negative_test_query139_00014", "negative_test_query350_00007", "negative_test_query103_00000", "negative_test_query72_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query186", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query186", "ranked_ids": ["negative_test_query247_00012", "negative_test_query46_00001", "negative_test_query336_00008", "negative_test_query29_00003", "negative_test_query313_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query187", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query187", "ranked_ids": ["negative_test_query306_00004", "apositive_test_query33_00008", "apositive_test_query66_00000", "negative_test_query356_00004", "negative_test_query13_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query188", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query188", "ranked_ids": ["negative_test_query188_00013", "negative_test_query20_00006", "negative_test_query188_00008", "apositive_test_query20_00009", "negative_test_query188_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query189", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.36005461457723403, "recall@5": 0.4}, "query_id": "test_query189", "ranked_ids": ["negative_test_query189_00001", "apositive_test_query189_00002", "negative_test_query140_00016", "apositive_test_query189_00003", "negative_test_query189_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query19", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.16666666666666666}, "query_id": "test_query19", "ranked_ids": ["negative_test_query292_00004", "negative_test_query169_00014", "negative_test_query106_00007", "apositive_test_query19_00000", "apositive_test_query4_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query190", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query190", "ranked_ids": ["negative_test_query85_00006", "negative_test_query205_00016", "negative_test_query85_00014", "negative_test_query233_00008", "negative_test_query265_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query191", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.15101961822780524, "recall@5": 0.25}, "query_id": "test_query191", "ranked_ids": ["negative_test_query96_00006", "negative_test_query241_00007", "negative_test_query274_00002", "apositive_test_query63_00003", "apositive_test_query191_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query192", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query192", "ranked_ids": ["negative_test_query350_00010", "negative_test_query120_00007", "negative_test_query350_00013", "negative_test_query192_00013", "negative_test_query172_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query193", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6608397947263839, "recall@5": 0.3076923076923077}, "query_id": "test_query193", "ranked_ids": ["apositive_test_query99_00003", "apositive_test_query193_00009", "apositive_test_query193_00002", "apositive_test_query193_00001", "apositive_test_query193_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query194", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.48522855511632257, "recall@5": 0.14285714285714285}, "query_id": "test_query194", "ranked_ids": ["apositive_test_query194_00001", "negative_test_query148_00009", "negative_test_query194_00003", "apositive_test_query194_00006", "negative_test_query194_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query195", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.4912596920895758, "recall@5": 0.5}, "query_id": "test_query195", "ranked_ids": ["apositive_test_query251_00000", "apositive_test_query195_00005", "negative_test_query195_00002", "apositive_test_query195_00002", "apositive_test_query195_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query196", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.2}, "query_id": "test_query196", "ranked_ids": ["apositive_test_query196_00003", "apositive_test_query32_00011", "apositive_test_query32_00012", "negative_test_query196_00011", "negative_test_query64_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query197", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.4367467095119258, "recall@5": 0.6666666666666666}, "query_id": "test_query197", "ranked_ids": ["negative_test_query163_00001", "negative_test_query163_00002", "apositive_test_query197_00000", "apositive_test_query197_00002", "negative_test_query197_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query198", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query198", "ranked_ids": ["negative_test_query239_00010", "negative_test_query239_00001", "negative_test_query198_00008", "negative_test_query112_00005", "negative_test_query198_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query199", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query199", "ranked_ids": ["negative_test_query199_00015", "negative_test_query199_00013", "negative_test_query305_00007", "negative_test_query139_00014", "negative_test_query199_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query2", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query2", "ranked_ids": ["negative_test_query343_00004", "negative_test_query125_00012", "negative_test_query2_00010", "negative_test_query2_00011", "apositive_test_query57_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query20", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.30078518014914984, "recall@5": 0.18181818181818182}, "query_id": "test_query20", "ranked_ids": ["negative_test_query154_00002", "apositive_test_query154_00004", "apositive_test_query20_00003", "negative_test_query188_00000", "apositive_test_query20_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query200", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query200", "ranked_ids": ["negative_test_query303_00009", "negative_test_query200_00007", "negative_test_query337_00007", "negative_test_query337_00004", "negative_test_query200_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query201", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query201", "ranked_ids": ["negative_test_query209_00008", "negative_test_query231_00012", "apositive_test_query20_00005", "negative_test_query71_00002", "negative_test_query71_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query202", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query202", "ranked_ids": ["negative_test_query176_00011", "negative_test_query15_00010", "negative_test_query202_00009", "apositive_test_query61_00003", "apositive_test_query206_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query203", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.4468535299918563, "recall@5": 0.3}, "query_id": "test_query203", "ranked_ids": ["negative_test_query209_00000", "negative_test_query250_00011", "apositive_test_query203_00009", "apositive_test_query203_00003", "apositive_test_query203_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query204", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.058823529411764705}, "query_id": "test_query204", "ranked_ids": ["apositive_test_query353_00011", "apositive_test_query353_00005", "apositive_test_query204_00002", "negative_test_query353_00003", "apositive_test_query175_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query205", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query205", "ranked_ids": ["apositive_test_query256_00002", "negative_test_query139_00007", "negative_test_query205_00003", "negative_test_query256_00006", "negative_test_query205_00015"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query206", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query206", "ranked_ids": ["negative_test_query206_00000", "negative_test_query206_00018", "negative_test_query206_00002", "negative_test_query206_00013", "negative_test_query64_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query207", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.3333333333333333}, "query_id": "test_query207", "ranked_ids": ["apositive_test_query207_00004", "negative_test_query207_00009", "apositive_test_query207_00000", "negative_test_query207_00012", "negative_test_query207_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query208", "metrics": {"mrr@5": 1.0, "ndcg@5": 1.0, "recall@5": 1.0}, "query_id": "test_query208", "ranked_ids": ["apositive_test_query208_00000", "negative_test_query208_00008", "negative_test_query128_00003", "negative_test_query208_00016", "apositive_test_query128_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query209", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.14285714285714285}, "query_id": "test_query209", "ranked_ids": ["apositive_test_query209_00001", "negative_test_query347_00004", "negative_test_query102_00010", "apositive_test_query26_00007", "negative_test_query223_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query21", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query21", "ranked_ids": ["negative_test_query21_00001", "negative_test_query224_00015", "negative_test_query234_00007", "negative_test_query128_00004", "negative_test_query21_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query210", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query210", "ranked_ids": ["negative_test_query148_00007", "negative_test_query210_00013", "negative_test_query335_00004", "negative_test_query112_00013", "negative_test_query80_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query211", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.20210734650054757, "recall@5": 0.3333333333333333}, "query_id": "test_query211", "ranked_ids": ["apositive_test_query163_00004", "apositive_test_query101_00000", "negative_test_query211_00000", "apositive_test_query211_00001", "negative_test_query293_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query212", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query212", "ranked_ids": ["negative_test_query282_00010", "negative_test_query344_00008", "apositive_test_query89_00004", "apositive_test_query244_00007", "apositive_test_query71_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query213", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.14285714285714285}, "query_id": "test_query213", "ranked_ids": ["negative_test_query137_00015", "negative_test_query213_00011", "negative_test_query213_00004", "apositive_test_query213_00004", "negative_test_query112_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query214", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query214", "ranked_ids": ["negative_test_query17_00013", "apositive_test_query234_00001", "apositive_test_query323_00003", "negative_test_query103_00009", "negative_test_query214_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query215", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query215", "ranked_ids": ["negative_test_query215_00000", "negative_test_query257_00003", "negative_test_query215_00009", "negative_test_query215_00011", "negative_test_query263_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query216", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.2}, "query_id": "test_query216", "ranked_ids": ["negative_test_query216_00008", "apositive_test_query216_00002", "negative_test_query216_00007", "apositive_test_query330_00002", "apositive_test_query154_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query217", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.125}, "query_id": "test_query217", "ranked_ids": ["negative_test_query28_00002", "apositive_test_query28_00008", "apositive_test_query213_00004", "apositive_test_query217_00007", "apositive_test_query307_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query218", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.36005461457723403, "recall@5": 0.2857142857142857}, "query_id": "test_query218", "ranked_ids": ["negative_test_query134_00003", "apositive_test_query218_00000", "negative_test_query218_00012", "apositive_test_query218_00004", "negative_test_query43_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query219", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6934264036172708, "recall@5": 1.0}, "query_id": "test_query219", "ranked_ids": ["negative_test_query171_00005", "apositive_test_query219_00001", "apositive_test_query219_00000", "negative_test_query219_00016", "negative_test_query219_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query22", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query22", "ranked_ids": ["negative_test_query22_00008", "negative_test_query22_00016", "negative_test_query22_00007", "negative_test_query22_00013", "negative_test_query22_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query220", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.16666666666666666}, "query_id": "test_query220", "ranked_ids": ["negative_test_query188_00004", "negative_test_query154_00009", "apositive_test_query220_00000", "negative_test_query344_00007", "negative_test_query220_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query221", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query221", "ranked_ids": ["apositive_test_query92_00003", "apositive_test_query162_00005", "apositive_test_query180_00002", "apositive_test_query203_00002", "negative_test_query65_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query222", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query222", "ranked_ids": ["apositive_test_query275_00004", "apositive_test_query275_00000", "negative_test_query289_00006", "negative_test_query275_00004", "apositive_test_query302_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query223", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query223", "ranked_ids": ["negative_test_query47_00007", "apositive_test_query259_00011", "negative_test_query359_00008", "negative_test_query47_00002", "apositive_test_query201_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query224", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.34620964322385606, "recall@5": 0.5}, "query_id": "test_query224", "ranked_ids": ["negative_test_query224_00012", "apositive_test_query179_00002", "apositive_test_query224_00003", "negative_test_query224_00008", "apositive_test_query224_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query225", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.18181818181818182}, "query_id": "test_query225", "ranked_ids": ["apositive_test_query225_00001", "negative_test_query145_00000", "apositive_test_query225_00000", "apositive_test_query145_00001", "negative_test_query225_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query226", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.5296347172140421, "recall@5": 0.5}, "query_id": "test_query226", "ranked_ids": ["negative_test_query226_00009", "apositive_test_query226_00002", "apositive_test_query226_00001", "apositive_test_query226_00005", "negative_test_query226_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query227", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query227", "ranked_ids": ["negative_test_query227_00014", "negative_test_query227_00003", "negative_test_query227_00002", "negative_test_query227_00004", "apositive_test_query348_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query228", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query228", "ranked_ids": ["apositive_test_query318_00001", "negative_test_query165_00011", "negative_test_query179_00010", "negative_test_query228_00011", "negative_test_query228_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query229", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.1}, "query_id": "test_query229", "ranked_ids": ["negative_test_query229_00009", "apositive_test_query229_00003", "apositive_test_query158_00006", "negative_test_query355_00001", "negative_test_query229_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query23", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.4}, "query_id": "test_query23", "ranked_ids": ["apositive_test_query23_00000", "apositive_test_query54_00007", "apositive_test_query23_00002", "negative_test_query259_00007", "negative_test_query23_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query230", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query230", "ranked_ids": ["negative_test_query23_00012", "negative_test_query3_00005", "negative_test_query230_00007", "negative_test_query3_00000", "negative_test_query230_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query231", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.16812753627111746, "recall@5": 0.25}, "query_id": "test_query231", "ranked_ids": ["negative_test_query221_00006", "apositive_test_query130_00000", "negative_test_query306_00006", "apositive_test_query231_00003", "negative_test_query231_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query232", "metrics": {"mrr@5": 1.0, "ndcg@5": 1.0, "recall@5": 1.0}, "query_id": "test_query232", "ranked_ids": ["apositive_test_query232_00000", "negative_test_query232_00004", "negative_test_query232_00002", "negative_test_query232_00006", "negative_test_query232_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query233", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.2960819109658652, "recall@5": 0.3333333333333333}, "query_id": "test_query233", "ranked_ids": ["negative_test_query2_00014", "apositive_test_query233_00000", "negative_test_query130_00001", "negative_test_query219_00015", "negative_test_query233_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query234", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.19519002499605084, "recall@5": 0.25}, "query_id": "test_query234", "ranked_ids": ["apositive_test_query304_00001", "negative_test_query234_00015", "apositive_test_query234_00003", "apositive_test_query304_00002", "negative_test_query214_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query235", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.3973220069685352, "recall@5": 0.5}, "query_id": "test_query235", "ranked_ids": ["negative_test_query61_00001", "apositive_test_query235_00003", "negative_test_query235_00014", "negative_test_query235_00009", "apositive_test_query235_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query236", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query236", "ranked_ids": ["negative_test_query279_00003", "negative_test_query236_00000", "negative_test_query236_00013", "negative_test_query236_00009", "negative_test_query236_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query237", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.38356636737133565, "recall@5": 0.4}, "query_id": "test_query237", "ranked_ids": ["negative_test_query152_00008", "apositive_test_query237_00002", "apositive_test_query237_00000", "negative_test_query237_00006", "negative_test_query312_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query238", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query238", "ranked_ids": ["negative_test_query29_00006", "negative_test_query102_00007", "negative_test_query108_00007", "apositive_test_query146_00013", "apositive_test_query29_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query239", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query239", "ranked_ids": ["negative_test_query239_00010", "negative_test_query198_00002", "negative_test_query352_00002", "negative_test_query268_00005", "negative_test_query239_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query24", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.43067655807339306, "recall@5": 1.0}, "query_id": "test_query24", "ranked_ids": ["negative_test_query24_00002", "negative_test_query24_00009", "negative_test_query250_00008", "apositive_test_query24_00000", "negative_test_query24_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query240", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.5, "recall@5": 1.0}, "query_id": "test_query240", "ranked_ids": ["negative_test_query240_00006", "negative_test_query240_00004", "apositive_test_query240_00000", "negative_test_query240_00010", "negative_test_query240_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query241", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query241", "ranked_ids": ["negative_test_query241_00016", "negative_test_query241_00010", "negative_test_query241_00002", "negative_test_query241_00008", "negative_test_query191_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query242", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6164336326286644, "recall@5": 0.2}, "query_id": "test_query242", "ranked_ids": ["apositive_test_query242_00002", "apositive_test_query247_00004", "apositive_test_query291_00001", "apositive_test_query242_00008", "apositive_test_query242_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query243", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.48522855511632257, "recall@5": 0.15384615384615385}, "query_id": "test_query243", "ranked_ids": ["apositive_test_query243_00002", "apositive_test_query160_00003", "negative_test_query205_00007", "apositive_test_query243_00003", "negative_test_query243_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query244", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.1111111111111111}, "query_id": "test_query244", "ranked_ids": ["negative_test_query244_00008", "apositive_test_query244_00005", "negative_test_query244_00005", "negative_test_query244_00001", "negative_test_query244_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query245", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query245", "ranked_ids": ["negative_test_query245_00000", "apositive_test_query141_00002", "apositive_test_query141_00006", "negative_test_query13_00005", "negative_test_query245_00015"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query246", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query246", "ranked_ids": ["apositive_test_query216_00000", "negative_test_query293_00000", "negative_test_query246_00002", "negative_test_query246_00001", "negative_test_query6_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query247", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.16666666666666666}, "query_id": "test_query247", "ranked_ids": ["negative_test_query247_00005", "apositive_test_query242_00010", "negative_test_query291_00003", "apositive_test_query242_00002", "apositive_test_query247_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query248", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.7227265726449519, "recall@5": 0.375}, "query_id": "test_query248", "ranked_ids": ["apositive_test_query248_00001", "apositive_test_query248_00002", "apositive_test_query248_00005", "negative_test_query251_00017", "apositive_test_query29_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query249", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query249", "ranked_ids": ["negative_test_query72_00017", "apositive_test_query281_00001", "negative_test_query193_00001", "negative_test_query156_00002", "negative_test_query122_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query25", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.5147714448836774, "recall@5": 0.2727272727272727}, "query_id": "test_query25", "ranked_ids": ["negative_test_query45_00014", "apositive_test_query25_00008", "apositive_test_query25_00003", "negative_test_query25_00005", "apositive_test_query25_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query250", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.23463936301137822, "recall@5": 0.3333333333333333}, "query_id": "test_query250", "ranked_ids": ["negative_test_query159_00014", "negative_test_query250_00011", "apositive_test_query250_00000", "apositive_test_query203_00006", "negative_test_query250_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query251", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.3065735963827292, "recall@5": 0.5}, "query_id": "test_query251", "ranked_ids": ["negative_test_query326_00000", "apositive_test_query134_00003", "apositive_test_query251_00001", "negative_test_query8_00009", "negative_test_query269_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query252", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query252", "ranked_ids": ["negative_test_query252_00009", "negative_test_query252_00011", "negative_test_query318_00006", "apositive_test_query110_00001", "negative_test_query14_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query253", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.1}, "query_id": "test_query253", "ranked_ids": ["apositive_test_query13_00012", "negative_test_query253_00005", "apositive_test_query349_00000", "apositive_test_query253_00003", "negative_test_query95_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query254", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.09090909090909091}, "query_id": "test_query254", "ranked_ids": ["apositive_test_query254_00005", "negative_test_query21_00009", "apositive_test_query333_00004", "negative_test_query254_00004", "negative_test_query128_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query255", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query255", "ranked_ids": ["negative_test_query255_00018", "negative_test_query255_00017", "apositive_test_query322_00001", "negative_test_query255_00010", "negative_test_query246_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query256", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.16812753627111746, "recall@5": 0.25}, "query_id": "test_query256", "ranked_ids": ["negative_test_query256_00008", "negative_test_query5_00010", "apositive_test_query43_00001", "apositive_test_query256_00002", "negative_test_query135_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query257", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.5296347172140421, "recall@5": 0.5}, "query_id": "test_query257", "ranked_ids": ["negative_test_query52_00004", "apositive_test_query257_00003", "apositive_test_query257_00004", "apositive_test_query257_00000", "negative_test_query257_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query258", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query258", "ranked_ids": ["apositive_test_query301_00002", "negative_test_query311_00002", "negative_test_query319_00000", "negative_test_query82_00000", "negative_test_query171_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query259", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.08333333333333333}, "query_id": "test_query259", "ranked_ids": ["negative_test_query259_00001", "apositive_test_query259_00008", "negative_test_query111_00010", "negative_test_query30_00009", "apositive_test_query54_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query26", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.25}, "query_id": "test_query26", "ranked_ids": ["apositive_test_query26_00004", "apositive_test_query26_00005", "negative_test_query102_00015", "negative_test_query334_00016", "negative_test_query164_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query260", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query260", "ranked_ids": ["negative_test_query6_00004", "apositive_test_query6_00002", "negative_test_query125_00001", "negative_test_query6_00009", "negative_test_query6_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query261", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.2}, "query_id": "test_query261", "ranked_ids": ["negative_test_query261_00006", "negative_test_query261_00007", "negative_test_query261_00002", "negative_test_query261_00011", "apositive_test_query261_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query262", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query262", "ranked_ids": ["apositive_test_query194_00002", "negative_test_query172_00000", "negative_test_query106_00010", "apositive_test_query177_00008", "negative_test_query147_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query263", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query263", "ranked_ids": ["negative_test_query307_00005", "negative_test_query263_00005", "negative_test_query135_00004", "apositive_test_query37_00002", "negative_test_query88_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query264", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query264", "ranked_ids": ["negative_test_query159_00013", "negative_test_query200_00017", "negative_test_query264_00018", "apositive_test_query38_00003", "negative_test_query159_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query265", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.23463936301137822, "recall@5": 0.3333333333333333}, "query_id": "test_query265", "ranked_ids": ["negative_test_query265_00007", "negative_test_query271_00005", "apositive_test_query265_00000", "negative_test_query165_00007", "negative_test_query165_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query266", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query266", "ranked_ids": ["negative_test_query266_00008", "apositive_test_query19_00002", "negative_test_query266_00009", "negative_test_query266_00017", "negative_test_query266_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query267", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query267", "ranked_ids": ["negative_test_query98_00013", "negative_test_query186_00006", "negative_test_query5_00012", "negative_test_query295_00002", "apositive_test_query157_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query268", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.2}, "query_id": "test_query268", "ranked_ids": ["negative_test_query47_00017", "apositive_test_query268_00002", "negative_test_query268_00012", "apositive_test_query51_00003", "negative_test_query268_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query269", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query269", "ranked_ids": ["negative_test_query269_00004", "negative_test_query269_00001", "negative_test_query269_00003", "negative_test_query269_00000", "negative_test_query269_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query27", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query27", "ranked_ids": ["negative_test_query27_00004", "negative_test_query27_00009", "negative_test_query164_00007", "apositive_test_query127_00010", "negative_test_query130_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query270", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query270", "ranked_ids": ["negative_test_query270_00012", "negative_test_query76_00005", "negative_test_query270_00014", "negative_test_query270_00005", "apositive_test_query81_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query271", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6131471927654584, "recall@5": 0.5}, "query_id": "test_query271", "ranked_ids": ["apositive_test_query271_00001", "negative_test_query330_00008", "negative_test_query227_00009", "apositive_test_query109_00001", "negative_test_query84_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query272", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query272", "ranked_ids": ["negative_test_query272_00003", "negative_test_query272_00010", "negative_test_query272_00015", "negative_test_query272_00016", "negative_test_query272_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query273", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.1}, "query_id": "test_query273", "ranked_ids": ["negative_test_query273_00004", "apositive_test_query18_00000", "apositive_test_query18_00003", "negative_test_query273_00008", "apositive_test_query273_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query274", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6366824387328317, "recall@5": 0.5}, "query_id": "test_query274", "ranked_ids": ["apositive_test_query274_00000", "apositive_test_query274_00003", "negative_test_query274_00005", "negative_test_query78_00007", "negative_test_query218_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query275", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.4703652827859579, "recall@5": 0.16666666666666666}, "query_id": "test_query275", "ranked_ids": ["apositive_test_query275_00000", "negative_test_query202_00005", "negative_test_query275_00004", "apositive_test_query110_00001", "apositive_test_query275_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query276", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query276", "ranked_ids": ["negative_test_query22_00004", "apositive_test_query153_00006", "apositive_test_query314_00006", "apositive_test_query314_00007", "negative_test_query146_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query277", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.75369761125927, "recall@5": 0.75}, "query_id": "test_query277", "ranked_ids": ["apositive_test_query277_00003", "negative_test_query277_00015", "apositive_test_query277_00000", "apositive_test_query277_00001", "negative_test_query277_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query278", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query278", "ranked_ids": ["apositive_test_query263_00005", "negative_test_query278_00011", "apositive_test_query4_00013", "negative_test_query110_00011", "negative_test_query27_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query279", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query279", "ranked_ids": ["negative_test_query279_00009", "apositive_test_query3_00000", "apositive_test_query76_00005", "negative_test_query279_00007", "negative_test_query279_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query28", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.34519134224686937, "recall@5": 0.14285714285714285}, "query_id": "test_query28", "ranked_ids": ["negative_test_query28_00002", "apositive_test_query28_00008", "negative_test_query72_00018", "apositive_test_query217_00001", "apositive_test_query28_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query280", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6843515475204855, "recall@5": 0.5}, "query_id": "test_query280", "ranked_ids": ["apositive_test_query280_00005", "apositive_test_query280_00003", "negative_test_query236_00018", "negative_test_query129_00008", "apositive_test_query280_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query281", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.639945385422766, "recall@5": 0.15789473684210525}, "query_id": "test_query281", "ranked_ids": ["apositive_test_query281_00004", "negative_test_query273_00003", "apositive_test_query281_00008", "negative_test_query273_00004", "apositive_test_query281_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query282", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6509209298071326, "recall@5": 1.0}, "query_id": "test_query282", "ranked_ids": ["negative_test_query344_00001", "apositive_test_query282_00000", "negative_test_query282_00001", "apositive_test_query282_00001", "negative_test_query282_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query283", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query283", "ranked_ids": ["negative_test_query283_00014", "negative_test_query283_00003", "negative_test_query283_00012", "negative_test_query93_00010", "negative_test_query299_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query284", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.34519134224686937, "recall@5": 0.4}, "query_id": "test_query284", "ranked_ids": ["apositive_test_query162_00005", "apositive_test_query284_00000", "apositive_test_query141_00002", "negative_test_query284_00001", "apositive_test_query284_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query285", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.25}, "query_id": "test_query285", "ranked_ids": ["apositive_test_query285_00006", "apositive_test_query285_00001", "negative_test_query304_00003", "negative_test_query285_00002", "negative_test_query86_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query286", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.13333333333333333}, "query_id": "test_query286", "ranked_ids": ["apositive_test_query286_00014", "negative_test_query257_00001", "apositive_test_query286_00010", "apositive_test_query257_00001", "apositive_test_query257_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query287", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.30078518014914984, "recall@5": 0.4}, "query_id": "test_query287", "ranked_ids": ["apositive_test_query133_00000", "apositive_test_query133_00008", "apositive_test_query287_00001", "apositive_test_query133_00013", "apositive_test_query287_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query288", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query288", "ranked_ids": ["apositive_test_query141_00002", "apositive_test_query141_00006", "negative_test_query210_00013", "negative_test_query93_00000", "apositive_test_query210_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query289", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.2}, "query_id": "test_query289", "ranked_ids": ["negative_test_query289_00004", "apositive_test_query289_00004", "apositive_test_query157_00008", "negative_test_query47_00009", "negative_test_query289_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query29", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.8539316501572937, "recall@5": 0.5}, "query_id": "test_query29", "ranked_ids": ["apositive_test_query29_00001", "apositive_test_query29_00007", "apositive_test_query29_00003", "negative_test_query39_00011", "apositive_test_query29_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query290", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.24630238874073, "recall@5": 0.25}, "query_id": "test_query290", "ranked_ids": ["negative_test_query237_00013", "apositive_test_query290_00001", "negative_test_query157_00005", "apositive_test_query237_00002", "negative_test_query59_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query291", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query291", "ranked_ids": ["apositive_test_query242_00001", "negative_test_query242_00000", "negative_test_query291_00000", "apositive_test_query242_00002", "apositive_test_query247_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query292", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.20210734650054757, "recall@5": 0.3333333333333333}, "query_id": "test_query292", "ranked_ids": ["negative_test_query292_00012", "negative_test_query292_00000", "negative_test_query106_00007", "apositive_test_query292_00001", "negative_test_query61_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query293", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.49818925746641285, "recall@5": 0.6666666666666666}, "query_id": "test_query293", "ranked_ids": ["negative_test_query293_00015", "apositive_test_query293_00001", "negative_test_query293_00016", "apositive_test_query293_00002", "negative_test_query293_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query294", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query294", "ranked_ids": ["negative_test_query294_00012", "negative_test_query294_00016", "negative_test_query294_00013", "negative_test_query314_00001", "negative_test_query294_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query295", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.2960819109658652, "recall@5": 0.3333333333333333}, "query_id": "test_query295", "ranked_ids": ["negative_test_query295_00013", "apositive_test_query295_00000", "negative_test_query295_00011", "negative_test_query57_00009", "negative_test_query295_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query296", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.5147714448836774, "recall@5": 0.5}, "query_id": "test_query296", "ranked_ids": ["negative_test_query296_00009", "apositive_test_query296_00001", "apositive_test_query296_00004", "negative_test_query296_00001", "apositive_test_query296_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query297", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query297", "ranked_ids": ["negative_test_query297_00014", "negative_test_query297_00013", "apositive_test_query139_00001", "negative_test_query297_00004", "negative_test_query297_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query298", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.41618115554873086, "recall@5": 0.6666666666666666}, "query_id": "test_query298", "ranked_ids": ["apositive_test_query347_00001", "negative_test_query202_00011", "apositive_test_query298_00002", "negative_test_query298_00004", "apositive_test_query298_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query299", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query299", "ranked_ids": ["apositive_test_query265_00002", "negative_test_query299_00005", "apositive_test_query325_00002", "negative_test_query125_00005", "negative_test_query305_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query3", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.7860137352654724, "recall@5": 0.36363636363636365}, "query_id": "test_query3", "ranked_ids": ["apositive_test_query3_00001", "apositive_test_query177_00006", "apositive_test_query3_00008", "apositive_test_query3_00002", "apositive_test_query3_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query30", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.14285714285714285}, "query_id": "test_query30", "ranked_ids": ["negative_test_query231_00008", "negative_test_query71_00010", "negative_test_query356_00010", "apositive_test_query71_00002", "apositive_test_query30_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query300", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3903800499921017, "recall@5": 0.25}, "query_id": "test_query300", "ranked_ids": ["apositive_test_query300_00001", "negative_test_query300_00005", "negative_test_query300_00013", "negative_test_query190_00004", "negative_test_query266_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query301", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.16812753627111746, "recall@5": 0.25}, "query_id": "test_query301", "ranked_ids": ["negative_test_query301_00011", "negative_test_query301_00002", "negative_test_query21_00018", "apositive_test_query301_00000", "apositive_test_query171_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query302", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6548086577531307, "recall@5": 0.42857142857142855}, "query_id": "test_query302", "ranked_ids": ["apositive_test_query302_00000", "apositive_test_query308_00009", "apositive_test_query302_00002", "apositive_test_query302_00003", "negative_test_query302_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query303", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query303", "ranked_ids": ["negative_test_query303_00017", "negative_test_query173_00001", "negative_test_query316_00009", "negative_test_query303_00015", "negative_test_query35_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query304", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.24630238874073, "recall@5": 0.25}, "query_id": "test_query304", "ranked_ids": ["apositive_test_query234_00003", "apositive_test_query304_00002", "negative_test_query304_00005", "negative_test_query214_00002", "negative_test_query43_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query305", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.15101961822780524, "recall@5": 0.25}, "query_id": "test_query305", "ranked_ids": ["apositive_test_query127_00005", "apositive_test_query357_00000", "negative_test_query0_00007", "negative_test_query134_00006", "apositive_test_query305_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query306", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query306", "ranked_ids": ["apositive_test_query71_00001", "apositive_test_query209_00005", "negative_test_query71_00011", "apositive_test_query30_00002", "apositive_test_query127_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query307", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query307", "ranked_ids": ["negative_test_query163_00010", "negative_test_query307_00006", "apositive_test_query177_00004", "negative_test_query70_00010", "negative_test_query217_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query308", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6548086577531307, "recall@5": 0.3}, "query_id": "test_query308", "ranked_ids": ["apositive_test_query308_00009", "negative_test_query159_00013", "apositive_test_query308_00001", "apositive_test_query308_00000", "negative_test_query94_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query309", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query309", "ranked_ids": ["negative_test_query354_00002", "negative_test_query309_00005", "apositive_test_query358_00007", "negative_test_query81_00002", "apositive_test_query358_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query31", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.23719771276929622, "recall@5": 0.5}, "query_id": "test_query31", "ranked_ids": ["apositive_test_query60_00011", "negative_test_query31_00004", "negative_test_query31_00008", "negative_test_query31_00000", "apositive_test_query31_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query310", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6608397947263839, "recall@5": 0.4}, "query_id": "test_query310", "ranked_ids": ["negative_test_query148_00010", "apositive_test_query310_00008", "apositive_test_query310_00002", "apositive_test_query310_00001", "apositive_test_query310_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query311", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query311", "ranked_ids": ["apositive_test_query330_00001", "apositive_test_query330_00002", "negative_test_query288_00004", "negative_test_query330_00011", "negative_test_query202_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query312", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query312", "ranked_ids": ["apositive_test_query220_00005", "negative_test_query343_00016", "negative_test_query312_00006", "negative_test_query212_00003", "negative_test_query242_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query313", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query313", "ranked_ids": ["negative_test_query119_00005", "negative_test_query56_00001", "apositive_test_query142_00005", "negative_test_query334_00001", "apositive_test_query108_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query314", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6608397947263839, "recall@5": 0.23529411764705882}, "query_id": "test_query314", "ranked_ids": ["apositive_test_query3_00009", "apositive_test_query314_00011", "apositive_test_query314_00001", "apositive_test_query314_00004", "apositive_test_query314_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query315", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.7039180890341347, "recall@5": 0.6666666666666666}, "query_id": "test_query315", "ranked_ids": ["apositive_test_query315_00001", "negative_test_query85_00005", "apositive_test_query315_00002", "apositive_test_query147_00004", "negative_test_query76_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query316", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.23719771276929622, "recall@5": 0.5}, "query_id": "test_query316", "ranked_ids": ["negative_test_query316_00008", "negative_test_query316_00004", "negative_test_query316_00000", "negative_test_query316_00001", "apositive_test_query316_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query317", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.23719771276929622, "recall@5": 0.5}, "query_id": "test_query317", "ranked_ids": ["negative_test_query317_00013", "negative_test_query317_00015", "negative_test_query317_00004", "negative_test_query317_00006", "apositive_test_query317_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query318", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5585075862632192, "recall@5": 0.5}, "query_id": "test_query318", "ranked_ids": ["apositive_test_query318_00001", "negative_test_query318_00003", "negative_test_query73_00000", "apositive_test_query318_00000", "negative_test_query318_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query319", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.639945385422766, "recall@5": 0.3333333333333333}, "query_id": "test_query319", "ranked_ids": ["apositive_test_query319_00000", "apositive_test_query82_00000", "apositive_test_query319_00001", "apositive_test_query82_00001", "apositive_test_query319_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query32", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query32", "ranked_ids": ["apositive_test_query357_00000", "apositive_test_query196_00000", "negative_test_query68_00009", "negative_test_query211_00005", "apositive_test_query192_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query320", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query320", "ranked_ids": ["negative_test_query100_00001", "negative_test_query304_00005", "negative_test_query258_00012", "negative_test_query53_00014", "negative_test_query43_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query321", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query321", "ranked_ids": ["negative_test_query322_00002", "negative_test_query141_00009", "negative_test_query321_00000", "negative_test_query103_00011", "apositive_test_query185_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query322", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.1111111111111111}, "query_id": "test_query322", "ranked_ids": ["apositive_test_query322_00001", "negative_test_query255_00010", "negative_test_query318_00014", "negative_test_query322_00010", "negative_test_query322_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query323", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.14285714285714285}, "query_id": "test_query323", "ranked_ids": ["apositive_test_query323_00004", "negative_test_query201_00009", "negative_test_query323_00001", "negative_test_query75_00002", "negative_test_query323_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query324", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.2960819109658652, "recall@5": 0.3333333333333333}, "query_id": "test_query324", "ranked_ids": ["apositive_test_query308_00000", "apositive_test_query324_00001", "negative_test_query324_00016", "apositive_test_query308_00003", "negative_test_query218_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query325", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query325", "ranked_ids": ["apositive_test_query19_00002", "apositive_test_query246_00001", "negative_test_query15_00016", "negative_test_query169_00014", "negative_test_query15_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query326", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.1111111111111111}, "query_id": "test_query326", "ranked_ids": ["negative_test_query178_00012", "negative_test_query181_00007", "apositive_test_query326_00000", "apositive_test_query23_00000", "negative_test_query156_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query327", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query327", "ranked_ids": ["negative_test_query327_00013", "negative_test_query327_00014", "apositive_test_query271_00001", "negative_test_query299_00010", "negative_test_query271_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query328", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.48522855511632257, "recall@5": 0.2222222222222222}, "query_id": "test_query328", "ranked_ids": ["apositive_test_query328_00001", "negative_test_query328_00002", "negative_test_query59_00017", "apositive_test_query328_00007", "negative_test_query144_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query329", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.20210734650054757, "recall@5": 0.3333333333333333}, "query_id": "test_query329", "ranked_ids": ["negative_test_query269_00000", "negative_test_query329_00011", "negative_test_query329_00009", "apositive_test_query329_00002", "negative_test_query329_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query33", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.1}, "query_id": "test_query33", "ranked_ids": ["negative_test_query154_00004", "negative_test_query161_00008", "apositive_test_query33_00004", "apositive_test_query284_00004", "apositive_test_query154_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query330", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query330", "ranked_ids": ["negative_test_query330_00012", "negative_test_query330_00010", "negative_test_query288_00006", "negative_test_query330_00001", "negative_test_query330_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query331", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.4367467095119258, "recall@5": 0.6666666666666666}, "query_id": "test_query331", "ranked_ids": ["negative_test_query331_00008", "negative_test_query331_00007", "apositive_test_query331_00002", "apositive_test_query331_00001", "negative_test_query334_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query332", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.4}, "query_id": "test_query332", "ranked_ids": ["apositive_test_query332_00003", "apositive_test_query332_00001", "negative_test_query332_00002", "negative_test_query171_00008", "apositive_test_query348_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query333", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.3333333333333333}, "query_id": "test_query333", "ranked_ids": ["apositive_test_query333_00000", "apositive_test_query333_00002", "negative_test_query73_00008", "apositive_test_query322_00003", "negative_test_query333_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query334", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query334", "ranked_ids": ["negative_test_query155_00013", "negative_test_query107_00001", "negative_test_query111_00010", "negative_test_query164_00005", "negative_test_query111_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query335", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6992148198508501, "recall@5": 0.6}, "query_id": "test_query335", "ranked_ids": ["apositive_test_query335_00003", "apositive_test_query335_00002", "negative_test_query92_00002", "apositive_test_query335_00001", "negative_test_query335_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query336", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query336", "ranked_ids": ["negative_test_query336_00009", "negative_test_query247_00000", "apositive_test_query186_00002", "negative_test_query247_00006", "negative_test_query108_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query337", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.43067655807339306, "recall@5": 1.0}, "query_id": "test_query337", "ranked_ids": ["negative_test_query200_00010", "negative_test_query337_00004", "negative_test_query200_00000", "apositive_test_query337_00000", "negative_test_query200_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query338", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.36005461457723403, "recall@5": 0.13333333333333333}, "query_id": "test_query338", "ranked_ids": ["negative_test_query338_00003", "apositive_test_query338_00002", "apositive_test_query114_00005", "apositive_test_query338_00005", "negative_test_query76_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query339", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.3065735963827292, "recall@5": 0.5}, "query_id": "test_query339", "ranked_ids": ["negative_test_query96_00003", "negative_test_query339_00004", "apositive_test_query339_00001", "apositive_test_query96_00001", "negative_test_query339_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query34", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query34", "ranked_ids": ["apositive_test_query352_00004", "negative_test_query34_00013", "apositive_test_query352_00000", "apositive_test_query352_00002", "apositive_test_query352_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query340", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.30078518014914984, "recall@5": 0.4}, "query_id": "test_query340", "ranked_ids": ["negative_test_query340_00007", "negative_test_query340_00011", "apositive_test_query340_00001", "negative_test_query88_00006", "apositive_test_query340_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query341", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.125}, "query_id": "test_query341", "ranked_ids": ["negative_test_query101_00003", "apositive_test_query341_00001", "negative_test_query73_00014", "negative_test_query341_00009", "negative_test_query196_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query342", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.18154179253735267, "recall@5": 0.3333333333333333}, "query_id": "test_query342", "ranked_ids": ["negative_test_query212_00009", "negative_test_query342_00015", "negative_test_query98_00011", "negative_test_query68_00005", "apositive_test_query342_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query343", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query343", "ranked_ids": ["negative_test_query225_00008", "apositive_test_query279_00002", "negative_test_query360_00017", "apositive_test_query217_00004", "apositive_test_query28_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query344", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query344", "ranked_ids": ["negative_test_query344_00004", "negative_test_query344_00005", "negative_test_query83_00008", "negative_test_query223_00016", "negative_test_query345_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query345", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.2}, "query_id": "test_query345", "ranked_ids": ["apositive_test_query203_00005", "negative_test_query259_00007", "apositive_test_query345_00001", "negative_test_query345_00001", "negative_test_query345_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query346", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query346", "ranked_ids": ["negative_test_query346_00002", "negative_test_query346_00010", "negative_test_query346_00006", "negative_test_query49_00007", "negative_test_query346_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query347", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query347", "ranked_ids": ["negative_test_query31_00003", "negative_test_query347_00007", "negative_test_query298_00006", "negative_test_query347_00000", "negative_test_query298_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query348", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.2}, "query_id": "test_query348", "ranked_ids": ["apositive_test_query348_00004", "apositive_test_query348_00005", "apositive_test_query115_00004", "negative_test_query115_00002", "negative_test_query351_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query349", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query349", "ranked_ids": ["apositive_test_query355_00007", "negative_test_query349_00015", "negative_test_query13_00002", "apositive_test_query253_00009", "negative_test_query253_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query35", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query35", "ranked_ids": ["apositive_test_query139_00000", "negative_test_query101_00006", "negative_test_query350_00005", "negative_test_query262_00002", "negative_test_query205_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query350", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query350", "ranked_ids": ["negative_test_query149_00005", "negative_test_query192_00013", "apositive_test_query33_00008", "negative_test_query350_00010", "negative_test_query120_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query351", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query351", "ranked_ids": ["apositive_test_query60_00012", "negative_test_query53_00011", "apositive_test_query335_00004", "apositive_test_query60_00010", "apositive_test_query60_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query352", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.2}, "query_id": "test_query352", "ranked_ids": ["negative_test_query34_00002", "negative_test_query352_00004", "apositive_test_query34_00001", "apositive_test_query352_00004", "negative_test_query34_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query353", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.48522855511632257, "recall@5": 0.13333333333333333}, "query_id": "test_query353", "ranked_ids": ["apositive_test_query353_00011", "apositive_test_query204_00008", "negative_test_query353_00001", "apositive_test_query353_00005", "apositive_test_query204_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query354", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.25}, "query_id": "test_query354", "ranked_ids": ["apositive_test_query354_00000", "apositive_test_query16_00003", "apositive_test_query354_00003", "apositive_test_query16_00001", "negative_test_query331_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query355", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.1111111111111111}, "query_id": "test_query355", "ranked_ids": ["negative_test_query355_00002", "negative_test_query215_00001", "apositive_test_query355_00001", "negative_test_query104_00001", "negative_test_query68_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query356", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5087403079104241, "recall@5": 0.2222222222222222}, "query_id": "test_query356", "ranked_ids": ["apositive_test_query356_00004", "apositive_test_query203_00006", "apositive_test_query356_00002", "apositive_test_query20_00001", "apositive_test_query154_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query357", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query357", "ranked_ids": ["apositive_test_query117_00004", "negative_test_query300_00007", "apositive_test_query119_00002", "apositive_test_query221_00000", "apositive_test_query119_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query358", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6843515475204855, "recall@5": 0.3}, "query_id": "test_query358", "ranked_ids": ["apositive_test_query358_00000", "apositive_test_query358_00007", "apositive_test_query301_00003", "negative_test_query358_00001", "apositive_test_query358_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query359", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query359", "ranked_ids": ["apositive_test_query0_00002", "apositive_test_query0_00006", "apositive_test_query259_00008", "apositive_test_query0_00007", "apositive_test_query0_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query36", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query36", "ranked_ids": ["negative_test_query36_00014", "apositive_test_query286_00005", "negative_test_query260_00009", "negative_test_query216_00002", "negative_test_query112_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query360", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query360", "ranked_ids": ["negative_test_query148_00018", "negative_test_query189_00013", "negative_test_query360_00005", "negative_test_query216_00011", "negative_test_query360_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query37", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.14606834984270645, "recall@5": 0.125}, "query_id": "test_query37", "ranked_ids": ["apositive_test_query213_00006", "negative_test_query57_00009", "apositive_test_query158_00007", "apositive_test_query37_00001", "apositive_test_query108_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query38", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6164336326286644, "recall@5": 0.375}, "query_id": "test_query38", "ranked_ids": ["apositive_test_query38_00007", "negative_test_query159_00011", "negative_test_query264_00012", "apositive_test_query38_00003", "apositive_test_query38_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query39", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.14285714285714285}, "query_id": "test_query39", "ranked_ids": ["negative_test_query39_00004", "apositive_test_query39_00003", "negative_test_query39_00000", "negative_test_query18_00004", "negative_test_query39_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query4", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.30078518014914984, "recall@5": 0.14285714285714285}, "query_id": "test_query4", "ranked_ids": ["apositive_test_query174_00001", "negative_test_query149_00015", "apositive_test_query4_00012", "negative_test_query19_00004", "apositive_test_query4_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query40", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query40", "ranked_ids": ["negative_test_query40_00005", "negative_test_query40_00013", "negative_test_query66_00005", "negative_test_query40_00004", "negative_test_query40_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query41", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query41", "ranked_ids": ["negative_test_query98_00000", "negative_test_query41_00007", "negative_test_query41_00000", "negative_test_query41_00010", "negative_test_query226_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query42", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.125}, "query_id": "test_query42", "ranked_ids": ["apositive_test_query42_00005", "negative_test_query42_00000", "negative_test_query250_00010", "apositive_test_query276_00012", "negative_test_query137_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query43", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.125}, "query_id": "test_query43", "ranked_ids": ["apositive_test_query161_00002", "apositive_test_query185_00002", "negative_test_query48_00017", "apositive_test_query146_00003", "apositive_test_query43_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query44", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query44", "ranked_ids": ["negative_test_query293_00003", "negative_test_query293_00015", "apositive_test_query20_00003", "negative_test_query27_00002", "negative_test_query27_00008"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query45", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query45", "ranked_ids": ["negative_test_query45_00003", "apositive_test_query165_00000", "negative_test_query45_00004", "negative_test_query328_00005", "negative_test_query143_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query46", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query46", "ranked_ids": ["apositive_test_query146_00013", "apositive_test_query186_00001", "negative_test_query56_00001", "negative_test_query359_00005", "negative_test_query39_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query47", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query47", "ranked_ids": ["negative_test_query47_00013", "apositive_test_query20_00005", "negative_test_query331_00005", "negative_test_query345_00002", "negative_test_query328_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query48", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query48", "ranked_ids": ["apositive_test_query28_00013", "negative_test_query48_00016", "negative_test_query48_00009", "negative_test_query307_00005", "negative_test_query49_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query49", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.23719771276929622, "recall@5": 0.5}, "query_id": "test_query49", "ranked_ids": ["negative_test_query169_00000", "apositive_test_query54_00009", "negative_test_query106_00004", "apositive_test_query19_00001", "apositive_test_query49_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query5", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.7653606369886217, "recall@5": 0.6666666666666666}, "query_id": "test_query5", "ranked_ids": ["apositive_test_query5_00000", "apositive_test_query5_00001", "negative_test_query67_00003", "negative_test_query246_00008", "negative_test_query5_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query50", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.4703652827859579, "recall@5": 0.11764705882352941}, "query_id": "test_query50", "ranked_ids": ["apositive_test_query50_00014", "negative_test_query119_00002", "negative_test_query223_00011", "apositive_test_query186_00005", "apositive_test_query50_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query51", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.1}, "query_id": "test_query51", "ranked_ids": ["negative_test_query51_00006", "negative_test_query97_00007", "negative_test_query267_00007", "negative_test_query97_00011", "apositive_test_query51_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query52", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query52", "ranked_ids": ["negative_test_query257_00007", "apositive_test_query257_00004", "negative_test_query54_00004", "negative_test_query52_00004", "apositive_test_query257_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query53", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query53", "ranked_ids": ["negative_test_query258_00016", "apositive_test_query217_00004", "apositive_test_query28_00006", "negative_test_query132_00015", "negative_test_query258_00018"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query54", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query54", "ranked_ids": ["negative_test_query51_00006", "negative_test_query56_00011", "negative_test_query200_00006", "negative_test_query44_00007", "negative_test_query140_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query55", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.2960819109658652, "recall@5": 0.3333333333333333}, "query_id": "test_query55", "ranked_ids": ["negative_test_query139_00015", "apositive_test_query55_00001", "negative_test_query55_00016", "negative_test_query55_00015", "negative_test_query55_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query56", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.125}, "query_id": "test_query56", "ranked_ids": ["negative_test_query105_00006", "negative_test_query46_00014", "negative_test_query200_00014", "negative_test_query337_00016", "apositive_test_query56_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query57", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6608397947263839, "recall@5": 0.4444444444444444}, "query_id": "test_query57", "ranked_ids": ["negative_test_query295_00003", "apositive_test_query57_00008", "apositive_test_query57_00000", "apositive_test_query57_00005", "apositive_test_query57_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query58", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query58", "ranked_ids": ["negative_test_query240_00015", "negative_test_query134_00001", "negative_test_query218_00004", "negative_test_query58_00001", "negative_test_query27_00017"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query59", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query59", "ranked_ids": ["negative_test_query116_00013", "negative_test_query59_00018", "negative_test_query59_00014", "negative_test_query227_00008", "negative_test_query116_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query6", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.4776237035032179, "recall@5": 0.6666666666666666}, "query_id": "test_query6", "ranked_ids": ["apositive_test_query242_00004", "apositive_test_query6_00000", "negative_test_query6_00011", "apositive_test_query109_00001", "apositive_test_query6_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query60", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.0625}, "query_id": "test_query60", "ranked_ids": ["apositive_test_query218_00004", "negative_test_query304_00002", "apositive_test_query285_00001", "negative_test_query304_00003", "apositive_test_query60_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query61", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3903800499921017, "recall@5": 0.25}, "query_id": "test_query61", "ranked_ids": ["apositive_test_query61_00003", "negative_test_query61_00005", "negative_test_query61_00002", "negative_test_query61_00003", "negative_test_query61_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query62", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query62", "ranked_ids": ["negative_test_query62_00000", "negative_test_query352_00005", "negative_test_query62_00003", "negative_test_query222_00007", "negative_test_query250_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query63", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6992148198508501, "recall@5": 0.3333333333333333}, "query_id": "test_query63", "ranked_ids": ["apositive_test_query63_00006", "apositive_test_query63_00001", "negative_test_query63_00002", "apositive_test_query63_00008", "negative_test_query63_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query64", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query64", "ranked_ids": ["negative_test_query149_00017", "apositive_test_query34_00003", "negative_test_query352_00001", "negative_test_query120_00000", "negative_test_query64_00015"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query65", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.6934264036172708, "recall@5": 1.0}, "query_id": "test_query65", "ranked_ids": ["negative_test_query260_00012", "apositive_test_query65_00001", "apositive_test_query65_00000", "negative_test_query6_00003", "apositive_test_query134_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query66", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.16666666666666666}, "query_id": "test_query66", "ranked_ids": ["apositive_test_query20_00005", "negative_test_query188_00006", "negative_test_query344_00007", "apositive_test_query220_00000", "apositive_test_query66_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query67", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query67", "ranked_ids": ["negative_test_query67_00005", "negative_test_query67_00001", "negative_test_query67_00008", "negative_test_query246_00016", "negative_test_query67_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query68", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.4367467095119258, "recall@5": 0.6666666666666666}, "query_id": "test_query68", "ranked_ids": ["negative_test_query68_00001", "negative_test_query68_00007", "apositive_test_query68_00002", "apositive_test_query68_00000", "apositive_test_query5_00002"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query69", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.639945385422766, "recall@5": 0.15789473684210525}, "query_id": "test_query69", "ranked_ids": ["apositive_test_query69_00012", "negative_test_query277_00001", "apositive_test_query69_00002", "apositive_test_query257_00003", "apositive_test_query69_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query7", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query7", "ranked_ids": ["apositive_test_query212_00002", "negative_test_query306_00008", "negative_test_query79_00002", "negative_test_query327_00011", "negative_test_query7_00005"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query70", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query70", "ranked_ids": ["negative_test_query227_00004", "negative_test_query227_00006", "apositive_test_query170_00001", "negative_test_query70_00000", "negative_test_query227_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query71", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6366824387328317, "recall@5": 0.5}, "query_id": "test_query71", "ranked_ids": ["apositive_test_query71_00001", "apositive_test_query71_00000", "negative_test_query231_00014", "apositive_test_query209_00005", "negative_test_query71_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query72", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query72", "ranked_ids": ["negative_test_query72_00001", "negative_test_query72_00016", "negative_test_query72_00013", "negative_test_query72_00015", "negative_test_query228_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query73", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query73", "ranked_ids": ["negative_test_query73_00013", "negative_test_query73_00001", "apositive_test_query322_00003", "negative_test_query73_00008", "apositive_test_query254_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query74", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.19519002499605084, "recall@5": 0.25}, "query_id": "test_query74", "ranked_ids": ["negative_test_query74_00003", "negative_test_query74_00015", "apositive_test_query74_00002", "negative_test_query238_00013", "apositive_test_query113_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query75", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.2857142857142857}, "query_id": "test_query75", "ranked_ids": ["apositive_test_query75_00003", "apositive_test_query75_00005", "negative_test_query75_00012", "negative_test_query67_00017", "negative_test_query319_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query76", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query76", "ranked_ids": ["negative_test_query76_00007", "negative_test_query91_00004", "negative_test_query76_00008", "negative_test_query76_00009", "negative_test_query53_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query77", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query77", "ranked_ids": ["negative_test_query47_00010", "negative_test_query148_00013", "negative_test_query150_00011", "negative_test_query216_00012", "negative_test_query326_00009"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query78", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query78", "ranked_ids": ["negative_test_query290_00013", "negative_test_query101_00008", "negative_test_query61_00007", "negative_test_query144_00000", "negative_test_query272_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query79", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query79", "ranked_ids": ["negative_test_query38_00007", "negative_test_query56_00002", "apositive_test_query221_00004", "negative_test_query79_00013", "negative_test_query157_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query8", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query8", "ranked_ids": ["negative_test_query192_00003", "negative_test_query8_00012", "negative_test_query205_00007", "negative_test_query8_00008", "apositive_test_query243_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query80", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query80", "ranked_ids": ["negative_test_query85_00007", "negative_test_query287_00005", "negative_test_query80_00001", "negative_test_query179_00005", "negative_test_query80_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query81", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5531464700081437, "recall@5": 0.2222222222222222}, "query_id": "test_query81", "ranked_ids": ["apositive_test_query81_00000", "apositive_test_query81_00008", "negative_test_query81_00006", "negative_test_query81_00008", "negative_test_query81_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query82", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.36005461457723403, "recall@5": 0.2222222222222222}, "query_id": "test_query82", "ranked_ids": ["apositive_test_query319_00000", "apositive_test_query82_00000", "apositive_test_query319_00001", "apositive_test_query82_00001", "apositive_test_query319_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query83", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.48522855511632257, "recall@5": 0.2857142857142857}, "query_id": "test_query83", "ranked_ids": ["apositive_test_query83_00002", "negative_test_query348_00006", "apositive_test_query229_00001", "apositive_test_query83_00000", "negative_test_query309_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query84", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.5012658353418871, "recall@5": 1.0}, "query_id": "test_query84", "ranked_ids": ["negative_test_query268_00009", "negative_test_query84_00006", "negative_test_query84_00017", "apositive_test_query84_00000", "apositive_test_query84_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query85", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.19519002499605084, "recall@5": 0.25}, "query_id": "test_query85", "ranked_ids": ["negative_test_query85_00003", "apositive_test_query284_00004", "apositive_test_query85_00000", "apositive_test_query133_00013", "apositive_test_query287_00004"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query86", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.5855700749881525, "recall@5": 0.5}, "query_id": "test_query86", "ranked_ids": ["apositive_test_query86_00001", "negative_test_query86_00014", "apositive_test_query86_00000", "negative_test_query86_00001", "negative_test_query188_00013"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query87", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query87", "ranked_ids": ["negative_test_query87_00008", "negative_test_query87_00015", "negative_test_query87_00013", "negative_test_query87_00006", "negative_test_query275_00006"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query88", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query88", "ranked_ids": ["apositive_test_query250_00002", "negative_test_query88_00002", "negative_test_query88_00008", "negative_test_query88_00005", "negative_test_query88_00016"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query89", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.16666666666666666}, "query_id": "test_query89", "ranked_ids": ["apositive_test_query89_00004", "negative_test_query110_00011", "negative_test_query326_00006", "negative_test_query89_00004", "negative_test_query89_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query9", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query9", "ranked_ids": ["negative_test_query221_00007", "negative_test_query9_00003", "negative_test_query9_00015", "negative_test_query305_00009", "negative_test_query221_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query90", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.6131471927654584, "recall@5": 0.5}, "query_id": "test_query90", "ranked_ids": ["apositive_test_query90_00000", "negative_test_query90_00017", "negative_test_query90_00010", "negative_test_query90_00002", "apositive_test_query304_00000"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query91", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.3391602052736161, "recall@5": 0.1111111111111111}, "query_id": "test_query91", "ranked_ids": ["apositive_test_query91_00000", "apositive_test_query342_00001", "negative_test_query292_00009", "negative_test_query303_00011", "negative_test_query91_00010"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query92", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.16666666666666666}, "query_id": "test_query92", "ranked_ids": ["apositive_test_query198_00001", "apositive_test_query92_00005", "apositive_test_query258_00000", "negative_test_query230_00006", "negative_test_query92_00003"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query93", "metrics": {"mrr@5": 0.3333333333333333, "ndcg@5": 0.16958010263680806, "recall@5": 0.14285714285714285}, "query_id": "test_query93", "ranked_ids": ["negative_test_query335_00011", "negative_test_query195_00001", "apositive_test_query93_00004", "negative_test_query335_00003", "negative_test_query92_00012"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query94", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.8539316501572937, "recall@5": 0.5}, "query_id": "test_query94", "ranked_ids": ["apositive_test_query94_00000", "apositive_test_query94_00002", "apositive_test_query94_00006", "apositive_test_query127_00001", "apositive_test_query94_00001"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query95", "metrics": {"mrr@5": 0.25, "ndcg@5": 0.2640681225725909, "recall@5": 0.5}, "query_id": "test_query95", "ranked_ids": ["negative_test_query95_00012", "negative_test_query95_00005", "negative_test_query95_00002", "apositive_test_query95_00000", "apositive_test_query253_00007"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query96", "metrics": {"mrr@5": 1.0, "ndcg@5": 0.46927872602275644, "recall@5": 0.3333333333333333}, "query_id": "test_query96", "ranked_ids": ["apositive_test_query96_00002", "negative_test_query339_00005", "negative_test_query96_00003", "apositive_test_query339_00001", "negative_test_query339_00014"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query97", "metrics": {"mrr@5": 0.5, "ndcg@5": 0.21398626473452756, "recall@5": 0.16666666666666666}, "query_id": "test_query97", "ranked_ids": ["negative_test_query51_00009", "apositive_test_query97_00003", "negative_test_query113_00009", "negative_test_query113_00006", "negative_test_query97_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query98", "metrics": {"mrr@5": 0.0, "ndcg@5": 0.0, "recall@5": 0.0}, "query_id": "test_query98", "ranked_ids": ["apositive_test_query14_00003", "apositive_test_query201_00004", "negative_test_query345_00002", "negative_test_query98_00004", "negative_test_query98_00011"], "valid": true} +{"algorithm": "cbdr", "fixture_id": "askubuntu-bm25-test-test_query99", "metrics": {"mrr@5": 0.2, "ndcg@5": 0.13120507751234178, "recall@5": 0.1111111111111111}, "query_id": "test_query99", "ranked_ids": ["apositive_test_query253_00003", "apositive_test_query333_00003", "negative_test_query193_00002", "apositive_test_query193_00010", "apositive_test_query99_00001"], "valid": true} diff --git a/benchmark-results/askubuntu-bm25-top20-cbdr-live.json b/benchmark-results/askubuntu-bm25-top20-cbdr-live.json new file mode 100644 index 0000000..22e16cb --- /dev/null +++ b/benchmark-results/askubuntu-bm25-top20-cbdr-live.json @@ -0,0 +1,6529 @@ +{ + "aggregate": [ + { + "algorithm": "cbdr", + "case_count": 361, + "invalid_rate": 0.0, + "metrics": { + "mrr@5": 0.34575253924284394, + "ndcg@5": 0.22589396228276157, + "recall@5": 0.18674158487327958 + }, + "valid_count": 361 + } + ], + "algorithm": [ + "cbdr" + ], + "benchmark_type": "reranking_with_first_stage_candidates", + "cache_dir": ".benchmark-cache/askubuntu-bm25", + "call_estimates": { + "cbdr": 7581 + }, + "candidate_count": 20, + "candidate_strategy": "candidate_file", + "candidates": "benchmark-results/pyserini/askubuntu-bm25-top20.trec", + "case_count": 361, + "dataset": "benchmark-cache", + "dataset_name": "askubuntu-bm25", + "per_query": [ + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query0", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.08333333333333333 + }, + "query_id": "test_query0", + "ranked_ids": [ + "negative_test_query36_00011", + "apositive_test_query130_00000", + "negative_test_query9_00017", + "negative_test_query221_00006", + "apositive_test_query0_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query1", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query1", + "ranked_ids": [ + "negative_test_query1_00005", + "negative_test_query1_00009", + "negative_test_query331_00002", + "apositive_test_query188_00002", + "negative_test_query61_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query10", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.18154179253735267, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query10", + "ranked_ids": [ + "negative_test_query10_00000", + "negative_test_query10_00011", + "negative_test_query10_00005", + "negative_test_query135_00014", + "apositive_test_query10_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query100", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.2 + }, + "query_id": "test_query100", + "ranked_ids": [ + "apositive_test_query181_00000", + "apositive_test_query100_00000", + "negative_test_query100_00009", + "negative_test_query290_00015", + "negative_test_query100_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query101", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.38356636737133565, + "recall@5": 0.2 + }, + "query_id": "test_query101", + "ranked_ids": [ + "negative_test_query68_00009", + "apositive_test_query101_00005", + "apositive_test_query101_00002", + "apositive_test_query291_00000", + "apositive_test_query73_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query102", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query102", + "ranked_ids": [ + "negative_test_query209_00006", + "apositive_test_query20_00001", + "negative_test_query108_00009", + "negative_test_query131_00009", + "negative_test_query203_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query103", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query103", + "ranked_ids": [ + "negative_test_query185_00003", + "negative_test_query307_00004", + "apositive_test_query103_00005", + "negative_test_query103_00011", + "apositive_test_query185_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query104", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query104", + "ranked_ids": [ + "negative_test_query104_00004", + "negative_test_query104_00003", + "negative_test_query104_00015", + "negative_test_query104_00009", + "negative_test_query104_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query105", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.46927872602275644, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query105", + "ranked_ids": [ + "apositive_test_query105_00000", + "negative_test_query105_00005", + "negative_test_query142_00010", + "negative_test_query313_00013", + "negative_test_query79_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query106", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6548086577531307, + "recall@5": 0.42857142857142855 + }, + "query_id": "test_query106", + "ranked_ids": [ + "apositive_test_query106_00000", + "negative_test_query326_00008", + "apositive_test_query106_00005", + "apositive_test_query106_00001", + "negative_test_query212_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query107", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query107", + "ranked_ids": [ + "negative_test_query46_00012", + "negative_test_query105_00015", + "negative_test_query333_00005", + "apositive_test_query221_00001", + "negative_test_query142_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query108", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6548086577531307, + "recall@5": 0.375 + }, + "query_id": "test_query108", + "ranked_ids": [ + "apositive_test_query108_00000", + "negative_test_query250_00009", + "apositive_test_query108_00002", + "apositive_test_query108_00003", + "negative_test_query311_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query109", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.2 + }, + "query_id": "test_query109", + "ranked_ids": [ + "apositive_test_query319_00008", + "apositive_test_query82_00008", + "negative_test_query109_00012", + "negative_test_query268_00010", + "apositive_test_query109_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query11", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.4703652827859579, + "recall@5": 0.25 + }, + "query_id": "test_query11", + "ranked_ids": [ + "apositive_test_query11_00006", + "negative_test_query132_00013", + "negative_test_query309_00002", + "negative_test_query358_00005", + "apositive_test_query11_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query110", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query110", + "ranked_ids": [ + "negative_test_query89_00006", + "negative_test_query110_00014", + "apositive_test_query180_00001", + "apositive_test_query242_00006", + "apositive_test_query291_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query111", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query111", + "ranked_ids": [ + "negative_test_query277_00006", + "negative_test_query277_00010", + "negative_test_query65_00007", + "negative_test_query243_00001", + "negative_test_query58_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query112", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query112", + "ranked_ids": [ + "negative_test_query112_00004", + "apositive_test_query36_00003", + "negative_test_query112_00013", + "negative_test_query80_00007", + "negative_test_query6_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query113", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query113", + "ranked_ids": [ + "apositive_test_query186_00005", + "negative_test_query113_00003", + "apositive_test_query113_00000", + "negative_test_query67_00014", + "negative_test_query223_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query114", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query114", + "ranked_ids": [ + "negative_test_query114_00005", + "apositive_test_query114_00003", + "negative_test_query114_00002", + "negative_test_query114_00001", + "negative_test_query262_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query115", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.5296347172140421, + "recall@5": 0.5 + }, + "query_id": "test_query115", + "ranked_ids": [ + "negative_test_query139_00000", + "apositive_test_query115_00003", + "apositive_test_query115_00000", + "apositive_test_query115_00004", + "negative_test_query115_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query116", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6131471927654584, + "recall@5": 0.5 + }, + "query_id": "test_query116", + "ranked_ids": [ + "apositive_test_query116_00000", + "negative_test_query116_00006", + "negative_test_query116_00008", + "negative_test_query116_00010", + "negative_test_query116_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query117", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.125 + }, + "query_id": "test_query117", + "ranked_ids": [ + "negative_test_query117_00003", + "apositive_test_query134_00004", + "apositive_test_query117_00004", + "negative_test_query117_00001", + "negative_test_query117_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query118", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query118", + "ranked_ids": [ + "negative_test_query23_00005", + "apositive_test_query155_00005", + "negative_test_query340_00007", + "negative_test_query296_00010", + "negative_test_query171_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query119", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.07692307692307693 + }, + "query_id": "test_query119", + "ranked_ids": [ + "apositive_test_query119_00005", + "apositive_test_query221_00004", + "negative_test_query359_00005", + "negative_test_query157_00001", + "apositive_test_query26_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query12", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query12", + "ranked_ids": [ + "negative_test_query12_00003", + "negative_test_query12_00011", + "negative_test_query12_00017", + "negative_test_query12_00004", + "negative_test_query12_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query120", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query120", + "ranked_ids": [ + "negative_test_query120_00008", + "negative_test_query148_00014", + "apositive_test_query32_00007", + "negative_test_query196_00006", + "negative_test_query120_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query121", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query121", + "ranked_ids": [ + "negative_test_query121_00003", + "apositive_test_query307_00010", + "negative_test_query121_00008", + "negative_test_query121_00000", + "negative_test_query256_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query122", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query122", + "ranked_ids": [ + "negative_test_query17_00012", + "negative_test_query86_00001", + "apositive_test_query173_00000", + "negative_test_query86_00006", + "apositive_test_query175_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query123", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.2 + }, + "query_id": "test_query123", + "ranked_ids": [ + "negative_test_query123_00013", + "negative_test_query123_00008", + "apositive_test_query123_00002", + "negative_test_query123_00005", + "negative_test_query155_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query124", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query124", + "ranked_ids": [ + "negative_test_query93_00010", + "negative_test_query123_00009", + "apositive_test_query13_00004", + "negative_test_query262_00011", + "negative_test_query171_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query125", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.44149241373678083, + "recall@5": 0.5 + }, + "query_id": "test_query125", + "ranked_ids": [ + "negative_test_query125_00013", + "apositive_test_query125_00000", + "apositive_test_query125_00003", + "negative_test_query125_00000", + "apositive_test_query286_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query126", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query126", + "ranked_ids": [ + "negative_test_query126_00017", + "negative_test_query190_00017", + "apositive_test_query5_00002", + "negative_test_query27_00013", + "apositive_test_query330_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query127", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.11764705882352941 + }, + "query_id": "test_query127", + "ranked_ids": [ + "apositive_test_query127_00006", + "negative_test_query94_00003", + "apositive_test_query127_00000", + "apositive_test_query94_00005", + "negative_test_query94_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query128", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.125 + }, + "query_id": "test_query128", + "ranked_ids": [ + "apositive_test_query142_00000", + "apositive_test_query216_00002", + "negative_test_query283_00006", + "apositive_test_query128_00001", + "negative_test_query21_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query129", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query129", + "ranked_ids": [ + "negative_test_query211_00012", + "negative_test_query156_00017", + "negative_test_query63_00006", + "apositive_test_query308_00004", + "negative_test_query63_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query13", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.38356636737133565, + "recall@5": 0.15384615384615385 + }, + "query_id": "test_query13", + "ranked_ids": [ + "negative_test_query13_00001", + "apositive_test_query13_00007", + "apositive_test_query13_00002", + "negative_test_query13_00002", + "negative_test_query13_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query130", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query130", + "ranked_ids": [ + "apositive_test_query323_00004", + "apositive_test_query284_00001", + "negative_test_query289_00004", + "apositive_test_query130_00005", + "negative_test_query90_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query131", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6366824387328317, + "recall@5": 0.5 + }, + "query_id": "test_query131", + "ranked_ids": [ + "apositive_test_query131_00001", + "apositive_test_query131_00000", + "apositive_test_query344_00003", + "apositive_test_query50_00008", + "negative_test_query357_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query132", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query132", + "ranked_ids": [ + "negative_test_query132_00000", + "negative_test_query123_00012", + "negative_test_query81_00005", + "apositive_test_query161_00007", + "negative_test_query148_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query133", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.38356636737133565, + "recall@5": 0.13333333333333333 + }, + "query_id": "test_query133", + "ranked_ids": [ + "apositive_test_query287_00001", + "apositive_test_query133_00002", + "apositive_test_query133_00008", + "apositive_test_query129_00007", + "negative_test_query310_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query134", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query134", + "ranked_ids": [ + "negative_test_query359_00011", + "negative_test_query134_00000", + "negative_test_query218_00009", + "apositive_test_query357_00000", + "apositive_test_query142_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query135", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.3065735963827292, + "recall@5": 0.5 + }, + "query_id": "test_query135", + "ranked_ids": [ + "negative_test_query135_00015", + "negative_test_query135_00008", + "apositive_test_query135_00000", + "negative_test_query135_00017", + "negative_test_query135_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query136", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6608397947263839, + "recall@5": 0.26666666666666666 + }, + "query_id": "test_query136", + "ranked_ids": [ + "negative_test_query301_00001", + "apositive_test_query136_00005", + "apositive_test_query136_00002", + "apositive_test_query136_00007", + "apositive_test_query136_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query137", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query137", + "ranked_ids": [ + "negative_test_query137_00012", + "negative_test_query137_00000", + "negative_test_query149_00013", + "negative_test_query137_00014", + "negative_test_query95_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query138", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query138", + "ranked_ids": [ + "negative_test_query138_00013", + "negative_test_query240_00000", + "negative_test_query183_00004", + "negative_test_query138_00004", + "negative_test_query138_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query139", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.46927872602275644, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query139", + "ranked_ids": [ + "apositive_test_query139_00001", + "negative_test_query35_00010", + "negative_test_query163_00000", + "apositive_test_query35_00001", + "negative_test_query81_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query14", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3903800499921017, + "recall@5": 0.25 + }, + "query_id": "test_query14", + "ranked_ids": [ + "apositive_test_query14_00003", + "negative_test_query199_00009", + "negative_test_query14_00005", + "negative_test_query155_00009", + "negative_test_query14_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query140", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query140", + "ranked_ids": [ + "negative_test_query140_00005", + "negative_test_query18_00006", + "apositive_test_query18_00001", + "negative_test_query140_00001", + "negative_test_query140_00018" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query141", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query141", + "ranked_ids": [ + "apositive_test_query141_00005", + "apositive_test_query288_00005", + "negative_test_query141_00010", + "negative_test_query25_00006", + "negative_test_query308_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query142", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query142", + "ranked_ids": [ + "negative_test_query102_00001", + "negative_test_query142_00003", + "negative_test_query46_00002", + "negative_test_query142_00009", + "negative_test_query46_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query143", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6131471927654584, + "recall@5": 0.5 + }, + "query_id": "test_query143", + "ranked_ids": [ + "apositive_test_query143_00000", + "negative_test_query143_00010", + "negative_test_query143_00014", + "negative_test_query90_00014", + "negative_test_query143_00015" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query144", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query144", + "ranked_ids": [ + "negative_test_query315_00011", + "negative_test_query331_00015", + "negative_test_query144_00018", + "negative_test_query166_00001", + "negative_test_query144_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query145", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query145", + "ranked_ids": [ + "negative_test_query190_00012", + "apositive_test_query225_00002", + "negative_test_query171_00003", + "apositive_test_query213_00004", + "apositive_test_query217_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query146", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.0625 + }, + "query_id": "test_query146", + "ranked_ids": [ + "apositive_test_query146_00004", + "apositive_test_query3_00010", + "apositive_test_query314_00004", + "apositive_test_query161_00008", + "apositive_test_query161_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query147", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.4 + }, + "query_id": "test_query147", + "ranked_ids": [ + "apositive_test_query147_00003", + "apositive_test_query76_00001", + "apositive_test_query147_00002", + "negative_test_query258_00008", + "negative_test_query53_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query148", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query148", + "ranked_ids": [ + "negative_test_query148_00005", + "negative_test_query148_00017", + "negative_test_query210_00013", + "negative_test_query112_00013", + "negative_test_query80_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query149", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.3065735963827292, + "recall@5": 0.5 + }, + "query_id": "test_query149", + "ranked_ids": [ + "negative_test_query149_00008", + "negative_test_query149_00017", + "apositive_test_query149_00000", + "negative_test_query332_00004", + "negative_test_query332_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query15", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6131471927654584, + "recall@5": 0.5 + }, + "query_id": "test_query15", + "ranked_ids": [ + "apositive_test_query15_00001", + "negative_test_query15_00015", + "negative_test_query15_00004", + "negative_test_query325_00011", + "negative_test_query266_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query150", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query150", + "ranked_ids": [ + "negative_test_query150_00010", + "negative_test_query62_00009", + "negative_test_query213_00007", + "apositive_test_query112_00000", + "negative_test_query250_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query151", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query151", + "ranked_ids": [ + "negative_test_query126_00017", + "negative_test_query190_00017", + "negative_test_query126_00000", + "negative_test_query151_00003", + "apositive_test_query300_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query152", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.1 + }, + "query_id": "test_query152", + "ranked_ids": [ + "negative_test_query237_00011", + "negative_test_query312_00014", + "negative_test_query95_00017", + "negative_test_query237_00009", + "apositive_test_query152_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query153", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.830419897363192, + "recall@5": 0.25 + }, + "query_id": "test_query153", + "ranked_ids": [ + "apositive_test_query153_00015", + "apositive_test_query153_00001", + "negative_test_query359_00003", + "apositive_test_query153_00003", + "apositive_test_query153_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query154", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query154", + "ranked_ids": [ + "apositive_test_query33_00008", + "apositive_test_query20_00003", + "negative_test_query245_00006", + "apositive_test_query66_00000", + "negative_test_query356_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query155", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query155", + "ranked_ids": [ + "negative_test_query155_00009", + "negative_test_query155_00006", + "apositive_test_query40_00004", + "negative_test_query155_00003", + "apositive_test_query3_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query156", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query156", + "ranked_ids": [ + "negative_test_query49_00005", + "apositive_test_query25_00009", + "negative_test_query1_00012", + "negative_test_query283_00001", + "negative_test_query92_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query157", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query157", + "ranked_ids": [ + "negative_test_query157_00005", + "apositive_test_query237_00002", + "apositive_test_query237_00004", + "apositive_test_query237_00000", + "negative_test_query237_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query158", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.1 + }, + "query_id": "test_query158", + "ranked_ids": [ + "negative_test_query223_00007", + "negative_test_query26_00004", + "apositive_test_query158_00000", + "negative_test_query102_00015", + "negative_test_query334_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query159", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6131471927654584, + "recall@5": 0.5 + }, + "query_id": "test_query159", + "ranked_ids": [ + "apositive_test_query159_00001", + "negative_test_query264_00013", + "negative_test_query159_00017", + "negative_test_query264_00017", + "apositive_test_query212_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query16", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6843515475204855, + "recall@5": 0.6 + }, + "query_id": "test_query16", + "ranked_ids": [ + "apositive_test_query16_00003", + "apositive_test_query16_00001", + "negative_test_query280_00007", + "negative_test_query237_00010", + "apositive_test_query16_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query160", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.07142857142857142 + }, + "query_id": "test_query160", + "ranked_ids": [ + "negative_test_query271_00010", + "negative_test_query160_00004", + "apositive_test_query355_00007", + "apositive_test_query160_00000", + "negative_test_query149_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query161", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query161", + "ranked_ids": [ + "apositive_test_query301_00003", + "negative_test_query358_00001", + "apositive_test_query305_00002", + "apositive_test_query301_00000", + "apositive_test_query37_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query162", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.08333333333333333 + }, + "query_id": "test_query162", + "ranked_ids": [ + "negative_test_query177_00002", + "negative_test_query240_00008", + "apositive_test_query162_00009", + "negative_test_query162_00001", + "negative_test_query343_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query163", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query163", + "ranked_ids": [ + "negative_test_query197_00002", + "apositive_test_query152_00007", + "negative_test_query163_00011", + "negative_test_query224_00010", + "negative_test_query350_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query164", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query164", + "ranked_ids": [ + "negative_test_query165_00003", + "negative_test_query344_00009", + "negative_test_query85_00010", + "negative_test_query131_00014", + "negative_test_query309_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query165", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query165", + "ranked_ids": [ + "negative_test_query159_00017", + "negative_test_query264_00017", + "negative_test_query215_00013", + "negative_test_query327_00010", + "negative_test_query348_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query166", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.3156484524795145, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query166", + "ranked_ids": [ + "negative_test_query166_00013", + "negative_test_query166_00001", + "apositive_test_query166_00002", + "apositive_test_query166_00005", + "negative_test_query166_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query167", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query167", + "ranked_ids": [ + "apositive_test_query288_00005", + "negative_test_query141_00010", + "negative_test_query167_00011", + "negative_test_query278_00010", + "negative_test_query167_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query168", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.16812753627111746, + "recall@5": 0.25 + }, + "query_id": "test_query168", + "ranked_ids": [ + "negative_test_query6_00000", + "negative_test_query344_00009", + "negative_test_query85_00010", + "apositive_test_query168_00002", + "apositive_test_query83_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query169", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query169", + "ranked_ids": [ + "apositive_test_query180_00001", + "apositive_test_query330_00001", + "negative_test_query89_00006", + "negative_test_query72_00001", + "negative_test_query72_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query17", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query17", + "ranked_ids": [ + "negative_test_query17_00002", + "negative_test_query17_00014", + "negative_test_query17_00015", + "negative_test_query10_00005", + "negative_test_query17_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query170", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.7227265726449519, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query170", + "ranked_ids": [ + "apositive_test_query170_00006", + "apositive_test_query170_00001", + "apositive_test_query170_00007", + "negative_test_query170_00000", + "negative_test_query297_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query171", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query171", + "ranked_ids": [ + "negative_test_query171_00004", + "negative_test_query277_00007", + "negative_test_query24_00007", + "negative_test_query346_00005", + "negative_test_query261_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query172", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.34519134224686937, + "recall@5": 0.25 + }, + "query_id": "test_query172", + "ranked_ids": [ + "negative_test_query192_00000", + "apositive_test_query172_00004", + "negative_test_query64_00006", + "negative_test_query350_00009", + "apositive_test_query172_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query173", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query173", + "ranked_ids": [ + "negative_test_query64_00006", + "negative_test_query173_00003", + "negative_test_query173_00013", + "negative_test_query173_00006", + "apositive_test_query173_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query174", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.8687949224876582, + "recall@5": 0.23529411764705882 + }, + "query_id": "test_query174", + "ranked_ids": [ + "apositive_test_query174_00015", + "apositive_test_query174_00001", + "apositive_test_query174_00009", + "apositive_test_query174_00008", + "negative_test_query262_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query175", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.4912596920895758, + "recall@5": 0.5 + }, + "query_id": "test_query175", + "ranked_ids": [ + "negative_test_query175_00010", + "apositive_test_query175_00003", + "negative_test_query175_00013", + "apositive_test_query175_00000", + "apositive_test_query175_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query176", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query176", + "ranked_ids": [ + "negative_test_query144_00018", + "negative_test_query200_00005", + "negative_test_query337_00005", + "negative_test_query224_00003", + "apositive_test_query344_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query177", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.1 + }, + "query_id": "test_query177", + "ranked_ids": [ + "negative_test_query147_00005", + "apositive_test_query177_00004", + "apositive_test_query162_00010", + "apositive_test_query162_00009", + "negative_test_query147_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query178", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query178", + "ranked_ids": [ + "apositive_test_query178_00004", + "negative_test_query327_00004", + "apositive_test_query178_00005", + "negative_test_query178_00007", + "negative_test_query214_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query179", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query179", + "ranked_ids": [ + "negative_test_query260_00012", + "negative_test_query179_00014", + "apositive_test_query316_00001", + "apositive_test_query173_00000", + "negative_test_query14_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query18", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.4 + }, + "query_id": "test_query18", + "ranked_ids": [ + "apositive_test_query18_00001", + "negative_test_query18_00013", + "apositive_test_query18_00000", + "negative_test_query140_00003", + "negative_test_query18_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query180", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.20210734650054757, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query180", + "ranked_ids": [ + "negative_test_query176_00001", + "negative_test_query95_00014", + "negative_test_query180_00004", + "apositive_test_query180_00001", + "negative_test_query180_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query181", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query181", + "ranked_ids": [ + "negative_test_query181_00007", + "negative_test_query181_00006", + "negative_test_query181_00010", + "negative_test_query181_00017", + "negative_test_query181_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query182", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.5, + "recall@5": 1.0 + }, + "query_id": "test_query182", + "ranked_ids": [ + "negative_test_query182_00017", + "negative_test_query182_00011", + "apositive_test_query182_00000", + "negative_test_query237_00001", + "negative_test_query296_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query183", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query183", + "ranked_ids": [ + "negative_test_query7_00007", + "negative_test_query7_00008", + "negative_test_query7_00014", + "negative_test_query183_00018", + "negative_test_query183_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query184", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query184", + "ranked_ids": [ + "apositive_test_query33_00004", + "negative_test_query101_00007", + "apositive_test_query316_00000", + "negative_test_query228_00011", + "negative_test_query322_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query185", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query185", + "ranked_ids": [ + "apositive_test_query307_00008", + "negative_test_query139_00014", + "negative_test_query350_00007", + "negative_test_query103_00000", + "negative_test_query72_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query186", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query186", + "ranked_ids": [ + "negative_test_query247_00012", + "negative_test_query46_00001", + "negative_test_query336_00008", + "negative_test_query29_00003", + "negative_test_query313_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query187", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query187", + "ranked_ids": [ + "negative_test_query306_00004", + "apositive_test_query33_00008", + "apositive_test_query66_00000", + "negative_test_query356_00004", + "negative_test_query13_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query188", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query188", + "ranked_ids": [ + "negative_test_query188_00013", + "negative_test_query20_00006", + "negative_test_query188_00008", + "apositive_test_query20_00009", + "negative_test_query188_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query189", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.36005461457723403, + "recall@5": 0.4 + }, + "query_id": "test_query189", + "ranked_ids": [ + "negative_test_query189_00001", + "apositive_test_query189_00002", + "negative_test_query140_00016", + "apositive_test_query189_00003", + "negative_test_query189_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query19", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query19", + "ranked_ids": [ + "negative_test_query292_00004", + "negative_test_query169_00014", + "negative_test_query106_00007", + "apositive_test_query19_00000", + "apositive_test_query4_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query190", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query190", + "ranked_ids": [ + "negative_test_query85_00006", + "negative_test_query205_00016", + "negative_test_query85_00014", + "negative_test_query233_00008", + "negative_test_query265_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query191", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.15101961822780524, + "recall@5": 0.25 + }, + "query_id": "test_query191", + "ranked_ids": [ + "negative_test_query96_00006", + "negative_test_query241_00007", + "negative_test_query274_00002", + "apositive_test_query63_00003", + "apositive_test_query191_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query192", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query192", + "ranked_ids": [ + "negative_test_query350_00010", + "negative_test_query120_00007", + "negative_test_query350_00013", + "negative_test_query192_00013", + "negative_test_query172_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query193", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6608397947263839, + "recall@5": 0.3076923076923077 + }, + "query_id": "test_query193", + "ranked_ids": [ + "apositive_test_query99_00003", + "apositive_test_query193_00009", + "apositive_test_query193_00002", + "apositive_test_query193_00001", + "apositive_test_query193_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query194", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.48522855511632257, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query194", + "ranked_ids": [ + "apositive_test_query194_00001", + "negative_test_query148_00009", + "negative_test_query194_00003", + "apositive_test_query194_00006", + "negative_test_query194_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query195", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.4912596920895758, + "recall@5": 0.5 + }, + "query_id": "test_query195", + "ranked_ids": [ + "apositive_test_query251_00000", + "apositive_test_query195_00005", + "negative_test_query195_00002", + "apositive_test_query195_00002", + "apositive_test_query195_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query196", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.2 + }, + "query_id": "test_query196", + "ranked_ids": [ + "apositive_test_query196_00003", + "apositive_test_query32_00011", + "apositive_test_query32_00012", + "negative_test_query196_00011", + "negative_test_query64_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query197", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.4367467095119258, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query197", + "ranked_ids": [ + "negative_test_query163_00001", + "negative_test_query163_00002", + "apositive_test_query197_00000", + "apositive_test_query197_00002", + "negative_test_query197_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query198", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query198", + "ranked_ids": [ + "negative_test_query239_00010", + "negative_test_query239_00001", + "negative_test_query198_00008", + "negative_test_query112_00005", + "negative_test_query198_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query199", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query199", + "ranked_ids": [ + "negative_test_query199_00015", + "negative_test_query199_00013", + "negative_test_query305_00007", + "negative_test_query139_00014", + "negative_test_query199_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query2", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query2", + "ranked_ids": [ + "negative_test_query343_00004", + "negative_test_query125_00012", + "negative_test_query2_00010", + "negative_test_query2_00011", + "apositive_test_query57_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query20", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.30078518014914984, + "recall@5": 0.18181818181818182 + }, + "query_id": "test_query20", + "ranked_ids": [ + "negative_test_query154_00002", + "apositive_test_query154_00004", + "apositive_test_query20_00003", + "negative_test_query188_00000", + "apositive_test_query20_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query200", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query200", + "ranked_ids": [ + "negative_test_query303_00009", + "negative_test_query200_00007", + "negative_test_query337_00007", + "negative_test_query337_00004", + "negative_test_query200_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query201", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query201", + "ranked_ids": [ + "negative_test_query209_00008", + "negative_test_query231_00012", + "apositive_test_query20_00005", + "negative_test_query71_00002", + "negative_test_query71_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query202", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query202", + "ranked_ids": [ + "negative_test_query176_00011", + "negative_test_query15_00010", + "negative_test_query202_00009", + "apositive_test_query61_00003", + "apositive_test_query206_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query203", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.4468535299918563, + "recall@5": 0.3 + }, + "query_id": "test_query203", + "ranked_ids": [ + "negative_test_query209_00000", + "negative_test_query250_00011", + "apositive_test_query203_00009", + "apositive_test_query203_00003", + "apositive_test_query203_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query204", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.058823529411764705 + }, + "query_id": "test_query204", + "ranked_ids": [ + "apositive_test_query353_00011", + "apositive_test_query353_00005", + "apositive_test_query204_00002", + "negative_test_query353_00003", + "apositive_test_query175_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query205", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query205", + "ranked_ids": [ + "apositive_test_query256_00002", + "negative_test_query139_00007", + "negative_test_query205_00003", + "negative_test_query256_00006", + "negative_test_query205_00015" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query206", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query206", + "ranked_ids": [ + "negative_test_query206_00000", + "negative_test_query206_00018", + "negative_test_query206_00002", + "negative_test_query206_00013", + "negative_test_query64_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query207", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query207", + "ranked_ids": [ + "apositive_test_query207_00004", + "negative_test_query207_00009", + "apositive_test_query207_00000", + "negative_test_query207_00012", + "negative_test_query207_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query208", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 1.0, + "recall@5": 1.0 + }, + "query_id": "test_query208", + "ranked_ids": [ + "apositive_test_query208_00000", + "negative_test_query208_00008", + "negative_test_query128_00003", + "negative_test_query208_00016", + "apositive_test_query128_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query209", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query209", + "ranked_ids": [ + "apositive_test_query209_00001", + "negative_test_query347_00004", + "negative_test_query102_00010", + "apositive_test_query26_00007", + "negative_test_query223_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query21", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query21", + "ranked_ids": [ + "negative_test_query21_00001", + "negative_test_query224_00015", + "negative_test_query234_00007", + "negative_test_query128_00004", + "negative_test_query21_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query210", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query210", + "ranked_ids": [ + "negative_test_query148_00007", + "negative_test_query210_00013", + "negative_test_query335_00004", + "negative_test_query112_00013", + "negative_test_query80_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query211", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.20210734650054757, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query211", + "ranked_ids": [ + "apositive_test_query163_00004", + "apositive_test_query101_00000", + "negative_test_query211_00000", + "apositive_test_query211_00001", + "negative_test_query293_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query212", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query212", + "ranked_ids": [ + "negative_test_query282_00010", + "negative_test_query344_00008", + "apositive_test_query89_00004", + "apositive_test_query244_00007", + "apositive_test_query71_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query213", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query213", + "ranked_ids": [ + "negative_test_query137_00015", + "negative_test_query213_00011", + "negative_test_query213_00004", + "apositive_test_query213_00004", + "negative_test_query112_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query214", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query214", + "ranked_ids": [ + "negative_test_query17_00013", + "apositive_test_query234_00001", + "apositive_test_query323_00003", + "negative_test_query103_00009", + "negative_test_query214_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query215", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query215", + "ranked_ids": [ + "negative_test_query215_00000", + "negative_test_query257_00003", + "negative_test_query215_00009", + "negative_test_query215_00011", + "negative_test_query263_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query216", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.2 + }, + "query_id": "test_query216", + "ranked_ids": [ + "negative_test_query216_00008", + "apositive_test_query216_00002", + "negative_test_query216_00007", + "apositive_test_query330_00002", + "apositive_test_query154_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query217", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.125 + }, + "query_id": "test_query217", + "ranked_ids": [ + "negative_test_query28_00002", + "apositive_test_query28_00008", + "apositive_test_query213_00004", + "apositive_test_query217_00007", + "apositive_test_query307_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query218", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.36005461457723403, + "recall@5": 0.2857142857142857 + }, + "query_id": "test_query218", + "ranked_ids": [ + "negative_test_query134_00003", + "apositive_test_query218_00000", + "negative_test_query218_00012", + "apositive_test_query218_00004", + "negative_test_query43_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query219", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6934264036172708, + "recall@5": 1.0 + }, + "query_id": "test_query219", + "ranked_ids": [ + "negative_test_query171_00005", + "apositive_test_query219_00001", + "apositive_test_query219_00000", + "negative_test_query219_00016", + "negative_test_query219_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query22", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query22", + "ranked_ids": [ + "negative_test_query22_00008", + "negative_test_query22_00016", + "negative_test_query22_00007", + "negative_test_query22_00013", + "negative_test_query22_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query220", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query220", + "ranked_ids": [ + "negative_test_query188_00004", + "negative_test_query154_00009", + "apositive_test_query220_00000", + "negative_test_query344_00007", + "negative_test_query220_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query221", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query221", + "ranked_ids": [ + "apositive_test_query92_00003", + "apositive_test_query162_00005", + "apositive_test_query180_00002", + "apositive_test_query203_00002", + "negative_test_query65_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query222", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query222", + "ranked_ids": [ + "apositive_test_query275_00004", + "apositive_test_query275_00000", + "negative_test_query289_00006", + "negative_test_query275_00004", + "apositive_test_query302_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query223", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query223", + "ranked_ids": [ + "negative_test_query47_00007", + "apositive_test_query259_00011", + "negative_test_query359_00008", + "negative_test_query47_00002", + "apositive_test_query201_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query224", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.34620964322385606, + "recall@5": 0.5 + }, + "query_id": "test_query224", + "ranked_ids": [ + "negative_test_query224_00012", + "apositive_test_query179_00002", + "apositive_test_query224_00003", + "negative_test_query224_00008", + "apositive_test_query224_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query225", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.18181818181818182 + }, + "query_id": "test_query225", + "ranked_ids": [ + "apositive_test_query225_00001", + "negative_test_query145_00000", + "apositive_test_query225_00000", + "apositive_test_query145_00001", + "negative_test_query225_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query226", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.5296347172140421, + "recall@5": 0.5 + }, + "query_id": "test_query226", + "ranked_ids": [ + "negative_test_query226_00009", + "apositive_test_query226_00002", + "apositive_test_query226_00001", + "apositive_test_query226_00005", + "negative_test_query226_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query227", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query227", + "ranked_ids": [ + "negative_test_query227_00014", + "negative_test_query227_00003", + "negative_test_query227_00002", + "negative_test_query227_00004", + "apositive_test_query348_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query228", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query228", + "ranked_ids": [ + "apositive_test_query318_00001", + "negative_test_query165_00011", + "negative_test_query179_00010", + "negative_test_query228_00011", + "negative_test_query228_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query229", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.1 + }, + "query_id": "test_query229", + "ranked_ids": [ + "negative_test_query229_00009", + "apositive_test_query229_00003", + "apositive_test_query158_00006", + "negative_test_query355_00001", + "negative_test_query229_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query23", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.4 + }, + "query_id": "test_query23", + "ranked_ids": [ + "apositive_test_query23_00000", + "apositive_test_query54_00007", + "apositive_test_query23_00002", + "negative_test_query259_00007", + "negative_test_query23_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query230", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query230", + "ranked_ids": [ + "negative_test_query23_00012", + "negative_test_query3_00005", + "negative_test_query230_00007", + "negative_test_query3_00000", + "negative_test_query230_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query231", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.16812753627111746, + "recall@5": 0.25 + }, + "query_id": "test_query231", + "ranked_ids": [ + "negative_test_query221_00006", + "apositive_test_query130_00000", + "negative_test_query306_00006", + "apositive_test_query231_00003", + "negative_test_query231_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query232", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 1.0, + "recall@5": 1.0 + }, + "query_id": "test_query232", + "ranked_ids": [ + "apositive_test_query232_00000", + "negative_test_query232_00004", + "negative_test_query232_00002", + "negative_test_query232_00006", + "negative_test_query232_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query233", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.2960819109658652, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query233", + "ranked_ids": [ + "negative_test_query2_00014", + "apositive_test_query233_00000", + "negative_test_query130_00001", + "negative_test_query219_00015", + "negative_test_query233_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query234", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.19519002499605084, + "recall@5": 0.25 + }, + "query_id": "test_query234", + "ranked_ids": [ + "apositive_test_query304_00001", + "negative_test_query234_00015", + "apositive_test_query234_00003", + "apositive_test_query304_00002", + "negative_test_query214_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query235", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.3973220069685352, + "recall@5": 0.5 + }, + "query_id": "test_query235", + "ranked_ids": [ + "negative_test_query61_00001", + "apositive_test_query235_00003", + "negative_test_query235_00014", + "negative_test_query235_00009", + "apositive_test_query235_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query236", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query236", + "ranked_ids": [ + "negative_test_query279_00003", + "negative_test_query236_00000", + "negative_test_query236_00013", + "negative_test_query236_00009", + "negative_test_query236_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query237", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.38356636737133565, + "recall@5": 0.4 + }, + "query_id": "test_query237", + "ranked_ids": [ + "negative_test_query152_00008", + "apositive_test_query237_00002", + "apositive_test_query237_00000", + "negative_test_query237_00006", + "negative_test_query312_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query238", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query238", + "ranked_ids": [ + "negative_test_query29_00006", + "negative_test_query102_00007", + "negative_test_query108_00007", + "apositive_test_query146_00013", + "apositive_test_query29_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query239", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query239", + "ranked_ids": [ + "negative_test_query239_00010", + "negative_test_query198_00002", + "negative_test_query352_00002", + "negative_test_query268_00005", + "negative_test_query239_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query24", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.43067655807339306, + "recall@5": 1.0 + }, + "query_id": "test_query24", + "ranked_ids": [ + "negative_test_query24_00002", + "negative_test_query24_00009", + "negative_test_query250_00008", + "apositive_test_query24_00000", + "negative_test_query24_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query240", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.5, + "recall@5": 1.0 + }, + "query_id": "test_query240", + "ranked_ids": [ + "negative_test_query240_00006", + "negative_test_query240_00004", + "apositive_test_query240_00000", + "negative_test_query240_00010", + "negative_test_query240_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query241", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query241", + "ranked_ids": [ + "negative_test_query241_00016", + "negative_test_query241_00010", + "negative_test_query241_00002", + "negative_test_query241_00008", + "negative_test_query191_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query242", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6164336326286644, + "recall@5": 0.2 + }, + "query_id": "test_query242", + "ranked_ids": [ + "apositive_test_query242_00002", + "apositive_test_query247_00004", + "apositive_test_query291_00001", + "apositive_test_query242_00008", + "apositive_test_query242_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query243", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.48522855511632257, + "recall@5": 0.15384615384615385 + }, + "query_id": "test_query243", + "ranked_ids": [ + "apositive_test_query243_00002", + "apositive_test_query160_00003", + "negative_test_query205_00007", + "apositive_test_query243_00003", + "negative_test_query243_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query244", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.1111111111111111 + }, + "query_id": "test_query244", + "ranked_ids": [ + "negative_test_query244_00008", + "apositive_test_query244_00005", + "negative_test_query244_00005", + "negative_test_query244_00001", + "negative_test_query244_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query245", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query245", + "ranked_ids": [ + "negative_test_query245_00000", + "apositive_test_query141_00002", + "apositive_test_query141_00006", + "negative_test_query13_00005", + "negative_test_query245_00015" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query246", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query246", + "ranked_ids": [ + "apositive_test_query216_00000", + "negative_test_query293_00000", + "negative_test_query246_00002", + "negative_test_query246_00001", + "negative_test_query6_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query247", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query247", + "ranked_ids": [ + "negative_test_query247_00005", + "apositive_test_query242_00010", + "negative_test_query291_00003", + "apositive_test_query242_00002", + "apositive_test_query247_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query248", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.7227265726449519, + "recall@5": 0.375 + }, + "query_id": "test_query248", + "ranked_ids": [ + "apositive_test_query248_00001", + "apositive_test_query248_00002", + "apositive_test_query248_00005", + "negative_test_query251_00017", + "apositive_test_query29_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query249", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query249", + "ranked_ids": [ + "negative_test_query72_00017", + "apositive_test_query281_00001", + "negative_test_query193_00001", + "negative_test_query156_00002", + "negative_test_query122_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query25", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.5147714448836774, + "recall@5": 0.2727272727272727 + }, + "query_id": "test_query25", + "ranked_ids": [ + "negative_test_query45_00014", + "apositive_test_query25_00008", + "apositive_test_query25_00003", + "negative_test_query25_00005", + "apositive_test_query25_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query250", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.23463936301137822, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query250", + "ranked_ids": [ + "negative_test_query159_00014", + "negative_test_query250_00011", + "apositive_test_query250_00000", + "apositive_test_query203_00006", + "negative_test_query250_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query251", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.3065735963827292, + "recall@5": 0.5 + }, + "query_id": "test_query251", + "ranked_ids": [ + "negative_test_query326_00000", + "apositive_test_query134_00003", + "apositive_test_query251_00001", + "negative_test_query8_00009", + "negative_test_query269_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query252", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query252", + "ranked_ids": [ + "negative_test_query252_00009", + "negative_test_query252_00011", + "negative_test_query318_00006", + "apositive_test_query110_00001", + "negative_test_query14_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query253", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.1 + }, + "query_id": "test_query253", + "ranked_ids": [ + "apositive_test_query13_00012", + "negative_test_query253_00005", + "apositive_test_query349_00000", + "apositive_test_query253_00003", + "negative_test_query95_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query254", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.09090909090909091 + }, + "query_id": "test_query254", + "ranked_ids": [ + "apositive_test_query254_00005", + "negative_test_query21_00009", + "apositive_test_query333_00004", + "negative_test_query254_00004", + "negative_test_query128_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query255", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query255", + "ranked_ids": [ + "negative_test_query255_00018", + "negative_test_query255_00017", + "apositive_test_query322_00001", + "negative_test_query255_00010", + "negative_test_query246_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query256", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.16812753627111746, + "recall@5": 0.25 + }, + "query_id": "test_query256", + "ranked_ids": [ + "negative_test_query256_00008", + "negative_test_query5_00010", + "apositive_test_query43_00001", + "apositive_test_query256_00002", + "negative_test_query135_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query257", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.5296347172140421, + "recall@5": 0.5 + }, + "query_id": "test_query257", + "ranked_ids": [ + "negative_test_query52_00004", + "apositive_test_query257_00003", + "apositive_test_query257_00004", + "apositive_test_query257_00000", + "negative_test_query257_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query258", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query258", + "ranked_ids": [ + "apositive_test_query301_00002", + "negative_test_query311_00002", + "negative_test_query319_00000", + "negative_test_query82_00000", + "negative_test_query171_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query259", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.08333333333333333 + }, + "query_id": "test_query259", + "ranked_ids": [ + "negative_test_query259_00001", + "apositive_test_query259_00008", + "negative_test_query111_00010", + "negative_test_query30_00009", + "apositive_test_query54_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query26", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.25 + }, + "query_id": "test_query26", + "ranked_ids": [ + "apositive_test_query26_00004", + "apositive_test_query26_00005", + "negative_test_query102_00015", + "negative_test_query334_00016", + "negative_test_query164_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query260", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query260", + "ranked_ids": [ + "negative_test_query6_00004", + "apositive_test_query6_00002", + "negative_test_query125_00001", + "negative_test_query6_00009", + "negative_test_query6_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query261", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.2 + }, + "query_id": "test_query261", + "ranked_ids": [ + "negative_test_query261_00006", + "negative_test_query261_00007", + "negative_test_query261_00002", + "negative_test_query261_00011", + "apositive_test_query261_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query262", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query262", + "ranked_ids": [ + "apositive_test_query194_00002", + "negative_test_query172_00000", + "negative_test_query106_00010", + "apositive_test_query177_00008", + "negative_test_query147_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query263", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query263", + "ranked_ids": [ + "negative_test_query307_00005", + "negative_test_query263_00005", + "negative_test_query135_00004", + "apositive_test_query37_00002", + "negative_test_query88_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query264", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query264", + "ranked_ids": [ + "negative_test_query159_00013", + "negative_test_query200_00017", + "negative_test_query264_00018", + "apositive_test_query38_00003", + "negative_test_query159_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query265", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.23463936301137822, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query265", + "ranked_ids": [ + "negative_test_query265_00007", + "negative_test_query271_00005", + "apositive_test_query265_00000", + "negative_test_query165_00007", + "negative_test_query165_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query266", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query266", + "ranked_ids": [ + "negative_test_query266_00008", + "apositive_test_query19_00002", + "negative_test_query266_00009", + "negative_test_query266_00017", + "negative_test_query266_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query267", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query267", + "ranked_ids": [ + "negative_test_query98_00013", + "negative_test_query186_00006", + "negative_test_query5_00012", + "negative_test_query295_00002", + "apositive_test_query157_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query268", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.2 + }, + "query_id": "test_query268", + "ranked_ids": [ + "negative_test_query47_00017", + "apositive_test_query268_00002", + "negative_test_query268_00012", + "apositive_test_query51_00003", + "negative_test_query268_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query269", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query269", + "ranked_ids": [ + "negative_test_query269_00004", + "negative_test_query269_00001", + "negative_test_query269_00003", + "negative_test_query269_00000", + "negative_test_query269_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query27", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query27", + "ranked_ids": [ + "negative_test_query27_00004", + "negative_test_query27_00009", + "negative_test_query164_00007", + "apositive_test_query127_00010", + "negative_test_query130_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query270", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query270", + "ranked_ids": [ + "negative_test_query270_00012", + "negative_test_query76_00005", + "negative_test_query270_00014", + "negative_test_query270_00005", + "apositive_test_query81_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query271", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6131471927654584, + "recall@5": 0.5 + }, + "query_id": "test_query271", + "ranked_ids": [ + "apositive_test_query271_00001", + "negative_test_query330_00008", + "negative_test_query227_00009", + "apositive_test_query109_00001", + "negative_test_query84_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query272", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query272", + "ranked_ids": [ + "negative_test_query272_00003", + "negative_test_query272_00010", + "negative_test_query272_00015", + "negative_test_query272_00016", + "negative_test_query272_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query273", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.1 + }, + "query_id": "test_query273", + "ranked_ids": [ + "negative_test_query273_00004", + "apositive_test_query18_00000", + "apositive_test_query18_00003", + "negative_test_query273_00008", + "apositive_test_query273_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query274", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6366824387328317, + "recall@5": 0.5 + }, + "query_id": "test_query274", + "ranked_ids": [ + "apositive_test_query274_00000", + "apositive_test_query274_00003", + "negative_test_query274_00005", + "negative_test_query78_00007", + "negative_test_query218_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query275", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.4703652827859579, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query275", + "ranked_ids": [ + "apositive_test_query275_00000", + "negative_test_query202_00005", + "negative_test_query275_00004", + "apositive_test_query110_00001", + "apositive_test_query275_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query276", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query276", + "ranked_ids": [ + "negative_test_query22_00004", + "apositive_test_query153_00006", + "apositive_test_query314_00006", + "apositive_test_query314_00007", + "negative_test_query146_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query277", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.75369761125927, + "recall@5": 0.75 + }, + "query_id": "test_query277", + "ranked_ids": [ + "apositive_test_query277_00003", + "negative_test_query277_00015", + "apositive_test_query277_00000", + "apositive_test_query277_00001", + "negative_test_query277_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query278", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query278", + "ranked_ids": [ + "apositive_test_query263_00005", + "negative_test_query278_00011", + "apositive_test_query4_00013", + "negative_test_query110_00011", + "negative_test_query27_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query279", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query279", + "ranked_ids": [ + "negative_test_query279_00009", + "apositive_test_query3_00000", + "apositive_test_query76_00005", + "negative_test_query279_00007", + "negative_test_query279_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query28", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.34519134224686937, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query28", + "ranked_ids": [ + "negative_test_query28_00002", + "apositive_test_query28_00008", + "negative_test_query72_00018", + "apositive_test_query217_00001", + "apositive_test_query28_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query280", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6843515475204855, + "recall@5": 0.5 + }, + "query_id": "test_query280", + "ranked_ids": [ + "apositive_test_query280_00005", + "apositive_test_query280_00003", + "negative_test_query236_00018", + "negative_test_query129_00008", + "apositive_test_query280_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query281", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.639945385422766, + "recall@5": 0.15789473684210525 + }, + "query_id": "test_query281", + "ranked_ids": [ + "apositive_test_query281_00004", + "negative_test_query273_00003", + "apositive_test_query281_00008", + "negative_test_query273_00004", + "apositive_test_query281_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query282", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6509209298071326, + "recall@5": 1.0 + }, + "query_id": "test_query282", + "ranked_ids": [ + "negative_test_query344_00001", + "apositive_test_query282_00000", + "negative_test_query282_00001", + "apositive_test_query282_00001", + "negative_test_query282_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query283", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query283", + "ranked_ids": [ + "negative_test_query283_00014", + "negative_test_query283_00003", + "negative_test_query283_00012", + "negative_test_query93_00010", + "negative_test_query299_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query284", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.34519134224686937, + "recall@5": 0.4 + }, + "query_id": "test_query284", + "ranked_ids": [ + "apositive_test_query162_00005", + "apositive_test_query284_00000", + "apositive_test_query141_00002", + "negative_test_query284_00001", + "apositive_test_query284_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query285", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.25 + }, + "query_id": "test_query285", + "ranked_ids": [ + "apositive_test_query285_00006", + "apositive_test_query285_00001", + "negative_test_query304_00003", + "negative_test_query285_00002", + "negative_test_query86_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query286", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.13333333333333333 + }, + "query_id": "test_query286", + "ranked_ids": [ + "apositive_test_query286_00014", + "negative_test_query257_00001", + "apositive_test_query286_00010", + "apositive_test_query257_00001", + "apositive_test_query257_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query287", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.30078518014914984, + "recall@5": 0.4 + }, + "query_id": "test_query287", + "ranked_ids": [ + "apositive_test_query133_00000", + "apositive_test_query133_00008", + "apositive_test_query287_00001", + "apositive_test_query133_00013", + "apositive_test_query287_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query288", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query288", + "ranked_ids": [ + "apositive_test_query141_00002", + "apositive_test_query141_00006", + "negative_test_query210_00013", + "negative_test_query93_00000", + "apositive_test_query210_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query289", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.2 + }, + "query_id": "test_query289", + "ranked_ids": [ + "negative_test_query289_00004", + "apositive_test_query289_00004", + "apositive_test_query157_00008", + "negative_test_query47_00009", + "negative_test_query289_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query29", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.8539316501572937, + "recall@5": 0.5 + }, + "query_id": "test_query29", + "ranked_ids": [ + "apositive_test_query29_00001", + "apositive_test_query29_00007", + "apositive_test_query29_00003", + "negative_test_query39_00011", + "apositive_test_query29_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query290", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.24630238874073, + "recall@5": 0.25 + }, + "query_id": "test_query290", + "ranked_ids": [ + "negative_test_query237_00013", + "apositive_test_query290_00001", + "negative_test_query157_00005", + "apositive_test_query237_00002", + "negative_test_query59_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query291", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query291", + "ranked_ids": [ + "apositive_test_query242_00001", + "negative_test_query242_00000", + "negative_test_query291_00000", + "apositive_test_query242_00002", + "apositive_test_query247_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query292", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.20210734650054757, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query292", + "ranked_ids": [ + "negative_test_query292_00012", + "negative_test_query292_00000", + "negative_test_query106_00007", + "apositive_test_query292_00001", + "negative_test_query61_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query293", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.49818925746641285, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query293", + "ranked_ids": [ + "negative_test_query293_00015", + "apositive_test_query293_00001", + "negative_test_query293_00016", + "apositive_test_query293_00002", + "negative_test_query293_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query294", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query294", + "ranked_ids": [ + "negative_test_query294_00012", + "negative_test_query294_00016", + "negative_test_query294_00013", + "negative_test_query314_00001", + "negative_test_query294_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query295", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.2960819109658652, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query295", + "ranked_ids": [ + "negative_test_query295_00013", + "apositive_test_query295_00000", + "negative_test_query295_00011", + "negative_test_query57_00009", + "negative_test_query295_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query296", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.5147714448836774, + "recall@5": 0.5 + }, + "query_id": "test_query296", + "ranked_ids": [ + "negative_test_query296_00009", + "apositive_test_query296_00001", + "apositive_test_query296_00004", + "negative_test_query296_00001", + "apositive_test_query296_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query297", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query297", + "ranked_ids": [ + "negative_test_query297_00014", + "negative_test_query297_00013", + "apositive_test_query139_00001", + "negative_test_query297_00004", + "negative_test_query297_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query298", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.41618115554873086, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query298", + "ranked_ids": [ + "apositive_test_query347_00001", + "negative_test_query202_00011", + "apositive_test_query298_00002", + "negative_test_query298_00004", + "apositive_test_query298_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query299", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query299", + "ranked_ids": [ + "apositive_test_query265_00002", + "negative_test_query299_00005", + "apositive_test_query325_00002", + "negative_test_query125_00005", + "negative_test_query305_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query3", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.7860137352654724, + "recall@5": 0.36363636363636365 + }, + "query_id": "test_query3", + "ranked_ids": [ + "apositive_test_query3_00001", + "apositive_test_query177_00006", + "apositive_test_query3_00008", + "apositive_test_query3_00002", + "apositive_test_query3_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query30", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query30", + "ranked_ids": [ + "negative_test_query231_00008", + "negative_test_query71_00010", + "negative_test_query356_00010", + "apositive_test_query71_00002", + "apositive_test_query30_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query300", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3903800499921017, + "recall@5": 0.25 + }, + "query_id": "test_query300", + "ranked_ids": [ + "apositive_test_query300_00001", + "negative_test_query300_00005", + "negative_test_query300_00013", + "negative_test_query190_00004", + "negative_test_query266_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query301", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.16812753627111746, + "recall@5": 0.25 + }, + "query_id": "test_query301", + "ranked_ids": [ + "negative_test_query301_00011", + "negative_test_query301_00002", + "negative_test_query21_00018", + "apositive_test_query301_00000", + "apositive_test_query171_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query302", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6548086577531307, + "recall@5": 0.42857142857142855 + }, + "query_id": "test_query302", + "ranked_ids": [ + "apositive_test_query302_00000", + "apositive_test_query308_00009", + "apositive_test_query302_00002", + "apositive_test_query302_00003", + "negative_test_query302_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query303", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query303", + "ranked_ids": [ + "negative_test_query303_00017", + "negative_test_query173_00001", + "negative_test_query316_00009", + "negative_test_query303_00015", + "negative_test_query35_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query304", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.24630238874073, + "recall@5": 0.25 + }, + "query_id": "test_query304", + "ranked_ids": [ + "apositive_test_query234_00003", + "apositive_test_query304_00002", + "negative_test_query304_00005", + "negative_test_query214_00002", + "negative_test_query43_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query305", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.15101961822780524, + "recall@5": 0.25 + }, + "query_id": "test_query305", + "ranked_ids": [ + "apositive_test_query127_00005", + "apositive_test_query357_00000", + "negative_test_query0_00007", + "negative_test_query134_00006", + "apositive_test_query305_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query306", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query306", + "ranked_ids": [ + "apositive_test_query71_00001", + "apositive_test_query209_00005", + "negative_test_query71_00011", + "apositive_test_query30_00002", + "apositive_test_query127_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query307", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query307", + "ranked_ids": [ + "negative_test_query163_00010", + "negative_test_query307_00006", + "apositive_test_query177_00004", + "negative_test_query70_00010", + "negative_test_query217_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query308", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6548086577531307, + "recall@5": 0.3 + }, + "query_id": "test_query308", + "ranked_ids": [ + "apositive_test_query308_00009", + "negative_test_query159_00013", + "apositive_test_query308_00001", + "apositive_test_query308_00000", + "negative_test_query94_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query309", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query309", + "ranked_ids": [ + "negative_test_query354_00002", + "negative_test_query309_00005", + "apositive_test_query358_00007", + "negative_test_query81_00002", + "apositive_test_query358_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query31", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.23719771276929622, + "recall@5": 0.5 + }, + "query_id": "test_query31", + "ranked_ids": [ + "apositive_test_query60_00011", + "negative_test_query31_00004", + "negative_test_query31_00008", + "negative_test_query31_00000", + "apositive_test_query31_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query310", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6608397947263839, + "recall@5": 0.4 + }, + "query_id": "test_query310", + "ranked_ids": [ + "negative_test_query148_00010", + "apositive_test_query310_00008", + "apositive_test_query310_00002", + "apositive_test_query310_00001", + "apositive_test_query310_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query311", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query311", + "ranked_ids": [ + "apositive_test_query330_00001", + "apositive_test_query330_00002", + "negative_test_query288_00004", + "negative_test_query330_00011", + "negative_test_query202_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query312", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query312", + "ranked_ids": [ + "apositive_test_query220_00005", + "negative_test_query343_00016", + "negative_test_query312_00006", + "negative_test_query212_00003", + "negative_test_query242_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query313", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query313", + "ranked_ids": [ + "negative_test_query119_00005", + "negative_test_query56_00001", + "apositive_test_query142_00005", + "negative_test_query334_00001", + "apositive_test_query108_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query314", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6608397947263839, + "recall@5": 0.23529411764705882 + }, + "query_id": "test_query314", + "ranked_ids": [ + "apositive_test_query3_00009", + "apositive_test_query314_00011", + "apositive_test_query314_00001", + "apositive_test_query314_00004", + "apositive_test_query314_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query315", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.7039180890341347, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query315", + "ranked_ids": [ + "apositive_test_query315_00001", + "negative_test_query85_00005", + "apositive_test_query315_00002", + "apositive_test_query147_00004", + "negative_test_query76_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query316", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.23719771276929622, + "recall@5": 0.5 + }, + "query_id": "test_query316", + "ranked_ids": [ + "negative_test_query316_00008", + "negative_test_query316_00004", + "negative_test_query316_00000", + "negative_test_query316_00001", + "apositive_test_query316_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query317", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.23719771276929622, + "recall@5": 0.5 + }, + "query_id": "test_query317", + "ranked_ids": [ + "negative_test_query317_00013", + "negative_test_query317_00015", + "negative_test_query317_00004", + "negative_test_query317_00006", + "apositive_test_query317_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query318", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5585075862632192, + "recall@5": 0.5 + }, + "query_id": "test_query318", + "ranked_ids": [ + "apositive_test_query318_00001", + "negative_test_query318_00003", + "negative_test_query73_00000", + "apositive_test_query318_00000", + "negative_test_query318_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query319", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.639945385422766, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query319", + "ranked_ids": [ + "apositive_test_query319_00000", + "apositive_test_query82_00000", + "apositive_test_query319_00001", + "apositive_test_query82_00001", + "apositive_test_query319_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query32", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query32", + "ranked_ids": [ + "apositive_test_query357_00000", + "apositive_test_query196_00000", + "negative_test_query68_00009", + "negative_test_query211_00005", + "apositive_test_query192_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query320", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query320", + "ranked_ids": [ + "negative_test_query100_00001", + "negative_test_query304_00005", + "negative_test_query258_00012", + "negative_test_query53_00014", + "negative_test_query43_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query321", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query321", + "ranked_ids": [ + "negative_test_query322_00002", + "negative_test_query141_00009", + "negative_test_query321_00000", + "negative_test_query103_00011", + "apositive_test_query185_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query322", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.1111111111111111 + }, + "query_id": "test_query322", + "ranked_ids": [ + "apositive_test_query322_00001", + "negative_test_query255_00010", + "negative_test_query318_00014", + "negative_test_query322_00010", + "negative_test_query322_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query323", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query323", + "ranked_ids": [ + "apositive_test_query323_00004", + "negative_test_query201_00009", + "negative_test_query323_00001", + "negative_test_query75_00002", + "negative_test_query323_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query324", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.2960819109658652, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query324", + "ranked_ids": [ + "apositive_test_query308_00000", + "apositive_test_query324_00001", + "negative_test_query324_00016", + "apositive_test_query308_00003", + "negative_test_query218_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query325", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query325", + "ranked_ids": [ + "apositive_test_query19_00002", + "apositive_test_query246_00001", + "negative_test_query15_00016", + "negative_test_query169_00014", + "negative_test_query15_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query326", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.1111111111111111 + }, + "query_id": "test_query326", + "ranked_ids": [ + "negative_test_query178_00012", + "negative_test_query181_00007", + "apositive_test_query326_00000", + "apositive_test_query23_00000", + "negative_test_query156_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query327", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query327", + "ranked_ids": [ + "negative_test_query327_00013", + "negative_test_query327_00014", + "apositive_test_query271_00001", + "negative_test_query299_00010", + "negative_test_query271_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query328", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.48522855511632257, + "recall@5": 0.2222222222222222 + }, + "query_id": "test_query328", + "ranked_ids": [ + "apositive_test_query328_00001", + "negative_test_query328_00002", + "negative_test_query59_00017", + "apositive_test_query328_00007", + "negative_test_query144_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query329", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.20210734650054757, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query329", + "ranked_ids": [ + "negative_test_query269_00000", + "negative_test_query329_00011", + "negative_test_query329_00009", + "apositive_test_query329_00002", + "negative_test_query329_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query33", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.1 + }, + "query_id": "test_query33", + "ranked_ids": [ + "negative_test_query154_00004", + "negative_test_query161_00008", + "apositive_test_query33_00004", + "apositive_test_query284_00004", + "apositive_test_query154_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query330", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query330", + "ranked_ids": [ + "negative_test_query330_00012", + "negative_test_query330_00010", + "negative_test_query288_00006", + "negative_test_query330_00001", + "negative_test_query330_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query331", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.4367467095119258, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query331", + "ranked_ids": [ + "negative_test_query331_00008", + "negative_test_query331_00007", + "apositive_test_query331_00002", + "apositive_test_query331_00001", + "negative_test_query334_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query332", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.4 + }, + "query_id": "test_query332", + "ranked_ids": [ + "apositive_test_query332_00003", + "apositive_test_query332_00001", + "negative_test_query332_00002", + "negative_test_query171_00008", + "apositive_test_query348_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query333", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query333", + "ranked_ids": [ + "apositive_test_query333_00000", + "apositive_test_query333_00002", + "negative_test_query73_00008", + "apositive_test_query322_00003", + "negative_test_query333_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query334", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query334", + "ranked_ids": [ + "negative_test_query155_00013", + "negative_test_query107_00001", + "negative_test_query111_00010", + "negative_test_query164_00005", + "negative_test_query111_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query335", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6992148198508501, + "recall@5": 0.6 + }, + "query_id": "test_query335", + "ranked_ids": [ + "apositive_test_query335_00003", + "apositive_test_query335_00002", + "negative_test_query92_00002", + "apositive_test_query335_00001", + "negative_test_query335_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query336", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query336", + "ranked_ids": [ + "negative_test_query336_00009", + "negative_test_query247_00000", + "apositive_test_query186_00002", + "negative_test_query247_00006", + "negative_test_query108_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query337", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.43067655807339306, + "recall@5": 1.0 + }, + "query_id": "test_query337", + "ranked_ids": [ + "negative_test_query200_00010", + "negative_test_query337_00004", + "negative_test_query200_00000", + "apositive_test_query337_00000", + "negative_test_query200_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query338", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.36005461457723403, + "recall@5": 0.13333333333333333 + }, + "query_id": "test_query338", + "ranked_ids": [ + "negative_test_query338_00003", + "apositive_test_query338_00002", + "apositive_test_query114_00005", + "apositive_test_query338_00005", + "negative_test_query76_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query339", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.3065735963827292, + "recall@5": 0.5 + }, + "query_id": "test_query339", + "ranked_ids": [ + "negative_test_query96_00003", + "negative_test_query339_00004", + "apositive_test_query339_00001", + "apositive_test_query96_00001", + "negative_test_query339_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query34", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query34", + "ranked_ids": [ + "apositive_test_query352_00004", + "negative_test_query34_00013", + "apositive_test_query352_00000", + "apositive_test_query352_00002", + "apositive_test_query352_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query340", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.30078518014914984, + "recall@5": 0.4 + }, + "query_id": "test_query340", + "ranked_ids": [ + "negative_test_query340_00007", + "negative_test_query340_00011", + "apositive_test_query340_00001", + "negative_test_query88_00006", + "apositive_test_query340_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query341", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.125 + }, + "query_id": "test_query341", + "ranked_ids": [ + "negative_test_query101_00003", + "apositive_test_query341_00001", + "negative_test_query73_00014", + "negative_test_query341_00009", + "negative_test_query196_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query342", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.18154179253735267, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query342", + "ranked_ids": [ + "negative_test_query212_00009", + "negative_test_query342_00015", + "negative_test_query98_00011", + "negative_test_query68_00005", + "apositive_test_query342_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query343", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query343", + "ranked_ids": [ + "negative_test_query225_00008", + "apositive_test_query279_00002", + "negative_test_query360_00017", + "apositive_test_query217_00004", + "apositive_test_query28_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query344", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query344", + "ranked_ids": [ + "negative_test_query344_00004", + "negative_test_query344_00005", + "negative_test_query83_00008", + "negative_test_query223_00016", + "negative_test_query345_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query345", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.2 + }, + "query_id": "test_query345", + "ranked_ids": [ + "apositive_test_query203_00005", + "negative_test_query259_00007", + "apositive_test_query345_00001", + "negative_test_query345_00001", + "negative_test_query345_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query346", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query346", + "ranked_ids": [ + "negative_test_query346_00002", + "negative_test_query346_00010", + "negative_test_query346_00006", + "negative_test_query49_00007", + "negative_test_query346_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query347", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query347", + "ranked_ids": [ + "negative_test_query31_00003", + "negative_test_query347_00007", + "negative_test_query298_00006", + "negative_test_query347_00000", + "negative_test_query298_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query348", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.2 + }, + "query_id": "test_query348", + "ranked_ids": [ + "apositive_test_query348_00004", + "apositive_test_query348_00005", + "apositive_test_query115_00004", + "negative_test_query115_00002", + "negative_test_query351_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query349", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query349", + "ranked_ids": [ + "apositive_test_query355_00007", + "negative_test_query349_00015", + "negative_test_query13_00002", + "apositive_test_query253_00009", + "negative_test_query253_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query35", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query35", + "ranked_ids": [ + "apositive_test_query139_00000", + "negative_test_query101_00006", + "negative_test_query350_00005", + "negative_test_query262_00002", + "negative_test_query205_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query350", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query350", + "ranked_ids": [ + "negative_test_query149_00005", + "negative_test_query192_00013", + "apositive_test_query33_00008", + "negative_test_query350_00010", + "negative_test_query120_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query351", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query351", + "ranked_ids": [ + "apositive_test_query60_00012", + "negative_test_query53_00011", + "apositive_test_query335_00004", + "apositive_test_query60_00010", + "apositive_test_query60_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query352", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.2 + }, + "query_id": "test_query352", + "ranked_ids": [ + "negative_test_query34_00002", + "negative_test_query352_00004", + "apositive_test_query34_00001", + "apositive_test_query352_00004", + "negative_test_query34_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query353", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.48522855511632257, + "recall@5": 0.13333333333333333 + }, + "query_id": "test_query353", + "ranked_ids": [ + "apositive_test_query353_00011", + "apositive_test_query204_00008", + "negative_test_query353_00001", + "apositive_test_query353_00005", + "apositive_test_query204_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query354", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.25 + }, + "query_id": "test_query354", + "ranked_ids": [ + "apositive_test_query354_00000", + "apositive_test_query16_00003", + "apositive_test_query354_00003", + "apositive_test_query16_00001", + "negative_test_query331_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query355", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.1111111111111111 + }, + "query_id": "test_query355", + "ranked_ids": [ + "negative_test_query355_00002", + "negative_test_query215_00001", + "apositive_test_query355_00001", + "negative_test_query104_00001", + "negative_test_query68_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query356", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5087403079104241, + "recall@5": 0.2222222222222222 + }, + "query_id": "test_query356", + "ranked_ids": [ + "apositive_test_query356_00004", + "apositive_test_query203_00006", + "apositive_test_query356_00002", + "apositive_test_query20_00001", + "apositive_test_query154_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query357", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query357", + "ranked_ids": [ + "apositive_test_query117_00004", + "negative_test_query300_00007", + "apositive_test_query119_00002", + "apositive_test_query221_00000", + "apositive_test_query119_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query358", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6843515475204855, + "recall@5": 0.3 + }, + "query_id": "test_query358", + "ranked_ids": [ + "apositive_test_query358_00000", + "apositive_test_query358_00007", + "apositive_test_query301_00003", + "negative_test_query358_00001", + "apositive_test_query358_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query359", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query359", + "ranked_ids": [ + "apositive_test_query0_00002", + "apositive_test_query0_00006", + "apositive_test_query259_00008", + "apositive_test_query0_00007", + "apositive_test_query0_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query36", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query36", + "ranked_ids": [ + "negative_test_query36_00014", + "apositive_test_query286_00005", + "negative_test_query260_00009", + "negative_test_query216_00002", + "negative_test_query112_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query360", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query360", + "ranked_ids": [ + "negative_test_query148_00018", + "negative_test_query189_00013", + "negative_test_query360_00005", + "negative_test_query216_00011", + "negative_test_query360_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query37", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.14606834984270645, + "recall@5": 0.125 + }, + "query_id": "test_query37", + "ranked_ids": [ + "apositive_test_query213_00006", + "negative_test_query57_00009", + "apositive_test_query158_00007", + "apositive_test_query37_00001", + "apositive_test_query108_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query38", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6164336326286644, + "recall@5": 0.375 + }, + "query_id": "test_query38", + "ranked_ids": [ + "apositive_test_query38_00007", + "negative_test_query159_00011", + "negative_test_query264_00012", + "apositive_test_query38_00003", + "apositive_test_query38_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query39", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query39", + "ranked_ids": [ + "negative_test_query39_00004", + "apositive_test_query39_00003", + "negative_test_query39_00000", + "negative_test_query18_00004", + "negative_test_query39_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query4", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.30078518014914984, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query4", + "ranked_ids": [ + "apositive_test_query174_00001", + "negative_test_query149_00015", + "apositive_test_query4_00012", + "negative_test_query19_00004", + "apositive_test_query4_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query40", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query40", + "ranked_ids": [ + "negative_test_query40_00005", + "negative_test_query40_00013", + "negative_test_query66_00005", + "negative_test_query40_00004", + "negative_test_query40_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query41", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query41", + "ranked_ids": [ + "negative_test_query98_00000", + "negative_test_query41_00007", + "negative_test_query41_00000", + "negative_test_query41_00010", + "negative_test_query226_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query42", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.125 + }, + "query_id": "test_query42", + "ranked_ids": [ + "apositive_test_query42_00005", + "negative_test_query42_00000", + "negative_test_query250_00010", + "apositive_test_query276_00012", + "negative_test_query137_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query43", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.125 + }, + "query_id": "test_query43", + "ranked_ids": [ + "apositive_test_query161_00002", + "apositive_test_query185_00002", + "negative_test_query48_00017", + "apositive_test_query146_00003", + "apositive_test_query43_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query44", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query44", + "ranked_ids": [ + "negative_test_query293_00003", + "negative_test_query293_00015", + "apositive_test_query20_00003", + "negative_test_query27_00002", + "negative_test_query27_00008" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query45", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query45", + "ranked_ids": [ + "negative_test_query45_00003", + "apositive_test_query165_00000", + "negative_test_query45_00004", + "negative_test_query328_00005", + "negative_test_query143_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query46", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query46", + "ranked_ids": [ + "apositive_test_query146_00013", + "apositive_test_query186_00001", + "negative_test_query56_00001", + "negative_test_query359_00005", + "negative_test_query39_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query47", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query47", + "ranked_ids": [ + "negative_test_query47_00013", + "apositive_test_query20_00005", + "negative_test_query331_00005", + "negative_test_query345_00002", + "negative_test_query328_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query48", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query48", + "ranked_ids": [ + "apositive_test_query28_00013", + "negative_test_query48_00016", + "negative_test_query48_00009", + "negative_test_query307_00005", + "negative_test_query49_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query49", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.23719771276929622, + "recall@5": 0.5 + }, + "query_id": "test_query49", + "ranked_ids": [ + "negative_test_query169_00000", + "apositive_test_query54_00009", + "negative_test_query106_00004", + "apositive_test_query19_00001", + "apositive_test_query49_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query5", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.7653606369886217, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query5", + "ranked_ids": [ + "apositive_test_query5_00000", + "apositive_test_query5_00001", + "negative_test_query67_00003", + "negative_test_query246_00008", + "negative_test_query5_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query50", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.4703652827859579, + "recall@5": 0.11764705882352941 + }, + "query_id": "test_query50", + "ranked_ids": [ + "apositive_test_query50_00014", + "negative_test_query119_00002", + "negative_test_query223_00011", + "apositive_test_query186_00005", + "apositive_test_query50_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query51", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.1 + }, + "query_id": "test_query51", + "ranked_ids": [ + "negative_test_query51_00006", + "negative_test_query97_00007", + "negative_test_query267_00007", + "negative_test_query97_00011", + "apositive_test_query51_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query52", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query52", + "ranked_ids": [ + "negative_test_query257_00007", + "apositive_test_query257_00004", + "negative_test_query54_00004", + "negative_test_query52_00004", + "apositive_test_query257_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query53", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query53", + "ranked_ids": [ + "negative_test_query258_00016", + "apositive_test_query217_00004", + "apositive_test_query28_00006", + "negative_test_query132_00015", + "negative_test_query258_00018" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query54", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query54", + "ranked_ids": [ + "negative_test_query51_00006", + "negative_test_query56_00011", + "negative_test_query200_00006", + "negative_test_query44_00007", + "negative_test_query140_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query55", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.2960819109658652, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query55", + "ranked_ids": [ + "negative_test_query139_00015", + "apositive_test_query55_00001", + "negative_test_query55_00016", + "negative_test_query55_00015", + "negative_test_query55_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query56", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.125 + }, + "query_id": "test_query56", + "ranked_ids": [ + "negative_test_query105_00006", + "negative_test_query46_00014", + "negative_test_query200_00014", + "negative_test_query337_00016", + "apositive_test_query56_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query57", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6608397947263839, + "recall@5": 0.4444444444444444 + }, + "query_id": "test_query57", + "ranked_ids": [ + "negative_test_query295_00003", + "apositive_test_query57_00008", + "apositive_test_query57_00000", + "apositive_test_query57_00005", + "apositive_test_query57_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query58", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query58", + "ranked_ids": [ + "negative_test_query240_00015", + "negative_test_query134_00001", + "negative_test_query218_00004", + "negative_test_query58_00001", + "negative_test_query27_00017" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query59", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query59", + "ranked_ids": [ + "negative_test_query116_00013", + "negative_test_query59_00018", + "negative_test_query59_00014", + "negative_test_query227_00008", + "negative_test_query116_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query6", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.4776237035032179, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query6", + "ranked_ids": [ + "apositive_test_query242_00004", + "apositive_test_query6_00000", + "negative_test_query6_00011", + "apositive_test_query109_00001", + "apositive_test_query6_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query60", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.0625 + }, + "query_id": "test_query60", + "ranked_ids": [ + "apositive_test_query218_00004", + "negative_test_query304_00002", + "apositive_test_query285_00001", + "negative_test_query304_00003", + "apositive_test_query60_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query61", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3903800499921017, + "recall@5": 0.25 + }, + "query_id": "test_query61", + "ranked_ids": [ + "apositive_test_query61_00003", + "negative_test_query61_00005", + "negative_test_query61_00002", + "negative_test_query61_00003", + "negative_test_query61_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query62", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query62", + "ranked_ids": [ + "negative_test_query62_00000", + "negative_test_query352_00005", + "negative_test_query62_00003", + "negative_test_query222_00007", + "negative_test_query250_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query63", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6992148198508501, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query63", + "ranked_ids": [ + "apositive_test_query63_00006", + "apositive_test_query63_00001", + "negative_test_query63_00002", + "apositive_test_query63_00008", + "negative_test_query63_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query64", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query64", + "ranked_ids": [ + "negative_test_query149_00017", + "apositive_test_query34_00003", + "negative_test_query352_00001", + "negative_test_query120_00000", + "negative_test_query64_00015" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query65", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.6934264036172708, + "recall@5": 1.0 + }, + "query_id": "test_query65", + "ranked_ids": [ + "negative_test_query260_00012", + "apositive_test_query65_00001", + "apositive_test_query65_00000", + "negative_test_query6_00003", + "apositive_test_query134_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query66", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query66", + "ranked_ids": [ + "apositive_test_query20_00005", + "negative_test_query188_00006", + "negative_test_query344_00007", + "apositive_test_query220_00000", + "apositive_test_query66_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query67", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query67", + "ranked_ids": [ + "negative_test_query67_00005", + "negative_test_query67_00001", + "negative_test_query67_00008", + "negative_test_query246_00016", + "negative_test_query67_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query68", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.4367467095119258, + "recall@5": 0.6666666666666666 + }, + "query_id": "test_query68", + "ranked_ids": [ + "negative_test_query68_00001", + "negative_test_query68_00007", + "apositive_test_query68_00002", + "apositive_test_query68_00000", + "apositive_test_query5_00002" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query69", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.639945385422766, + "recall@5": 0.15789473684210525 + }, + "query_id": "test_query69", + "ranked_ids": [ + "apositive_test_query69_00012", + "negative_test_query277_00001", + "apositive_test_query69_00002", + "apositive_test_query257_00003", + "apositive_test_query69_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query7", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query7", + "ranked_ids": [ + "apositive_test_query212_00002", + "negative_test_query306_00008", + "negative_test_query79_00002", + "negative_test_query327_00011", + "negative_test_query7_00005" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query70", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query70", + "ranked_ids": [ + "negative_test_query227_00004", + "negative_test_query227_00006", + "apositive_test_query170_00001", + "negative_test_query70_00000", + "negative_test_query227_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query71", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6366824387328317, + "recall@5": 0.5 + }, + "query_id": "test_query71", + "ranked_ids": [ + "apositive_test_query71_00001", + "apositive_test_query71_00000", + "negative_test_query231_00014", + "apositive_test_query209_00005", + "negative_test_query71_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query72", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query72", + "ranked_ids": [ + "negative_test_query72_00001", + "negative_test_query72_00016", + "negative_test_query72_00013", + "negative_test_query72_00015", + "negative_test_query228_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query73", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query73", + "ranked_ids": [ + "negative_test_query73_00013", + "negative_test_query73_00001", + "apositive_test_query322_00003", + "negative_test_query73_00008", + "apositive_test_query254_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query74", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.19519002499605084, + "recall@5": 0.25 + }, + "query_id": "test_query74", + "ranked_ids": [ + "negative_test_query74_00003", + "negative_test_query74_00015", + "apositive_test_query74_00002", + "negative_test_query238_00013", + "apositive_test_query113_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query75", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.2857142857142857 + }, + "query_id": "test_query75", + "ranked_ids": [ + "apositive_test_query75_00003", + "apositive_test_query75_00005", + "negative_test_query75_00012", + "negative_test_query67_00017", + "negative_test_query319_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query76", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query76", + "ranked_ids": [ + "negative_test_query76_00007", + "negative_test_query91_00004", + "negative_test_query76_00008", + "negative_test_query76_00009", + "negative_test_query53_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query77", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query77", + "ranked_ids": [ + "negative_test_query47_00010", + "negative_test_query148_00013", + "negative_test_query150_00011", + "negative_test_query216_00012", + "negative_test_query326_00009" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query78", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query78", + "ranked_ids": [ + "negative_test_query290_00013", + "negative_test_query101_00008", + "negative_test_query61_00007", + "negative_test_query144_00000", + "negative_test_query272_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query79", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query79", + "ranked_ids": [ + "negative_test_query38_00007", + "negative_test_query56_00002", + "apositive_test_query221_00004", + "negative_test_query79_00013", + "negative_test_query157_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query8", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query8", + "ranked_ids": [ + "negative_test_query192_00003", + "negative_test_query8_00012", + "negative_test_query205_00007", + "negative_test_query8_00008", + "apositive_test_query243_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query80", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query80", + "ranked_ids": [ + "negative_test_query85_00007", + "negative_test_query287_00005", + "negative_test_query80_00001", + "negative_test_query179_00005", + "negative_test_query80_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query81", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5531464700081437, + "recall@5": 0.2222222222222222 + }, + "query_id": "test_query81", + "ranked_ids": [ + "apositive_test_query81_00000", + "apositive_test_query81_00008", + "negative_test_query81_00006", + "negative_test_query81_00008", + "negative_test_query81_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query82", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.36005461457723403, + "recall@5": 0.2222222222222222 + }, + "query_id": "test_query82", + "ranked_ids": [ + "apositive_test_query319_00000", + "apositive_test_query82_00000", + "apositive_test_query319_00001", + "apositive_test_query82_00001", + "apositive_test_query319_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query83", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.48522855511632257, + "recall@5": 0.2857142857142857 + }, + "query_id": "test_query83", + "ranked_ids": [ + "apositive_test_query83_00002", + "negative_test_query348_00006", + "apositive_test_query229_00001", + "apositive_test_query83_00000", + "negative_test_query309_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query84", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.5012658353418871, + "recall@5": 1.0 + }, + "query_id": "test_query84", + "ranked_ids": [ + "negative_test_query268_00009", + "negative_test_query84_00006", + "negative_test_query84_00017", + "apositive_test_query84_00000", + "apositive_test_query84_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query85", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.19519002499605084, + "recall@5": 0.25 + }, + "query_id": "test_query85", + "ranked_ids": [ + "negative_test_query85_00003", + "apositive_test_query284_00004", + "apositive_test_query85_00000", + "apositive_test_query133_00013", + "apositive_test_query287_00004" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query86", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.5855700749881525, + "recall@5": 0.5 + }, + "query_id": "test_query86", + "ranked_ids": [ + "apositive_test_query86_00001", + "negative_test_query86_00014", + "apositive_test_query86_00000", + "negative_test_query86_00001", + "negative_test_query188_00013" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query87", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query87", + "ranked_ids": [ + "negative_test_query87_00008", + "negative_test_query87_00015", + "negative_test_query87_00013", + "negative_test_query87_00006", + "negative_test_query275_00006" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query88", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query88", + "ranked_ids": [ + "apositive_test_query250_00002", + "negative_test_query88_00002", + "negative_test_query88_00008", + "negative_test_query88_00005", + "negative_test_query88_00016" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query89", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query89", + "ranked_ids": [ + "apositive_test_query89_00004", + "negative_test_query110_00011", + "negative_test_query326_00006", + "negative_test_query89_00004", + "negative_test_query89_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query9", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query9", + "ranked_ids": [ + "negative_test_query221_00007", + "negative_test_query9_00003", + "negative_test_query9_00015", + "negative_test_query305_00009", + "negative_test_query221_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query90", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.6131471927654584, + "recall@5": 0.5 + }, + "query_id": "test_query90", + "ranked_ids": [ + "apositive_test_query90_00000", + "negative_test_query90_00017", + "negative_test_query90_00010", + "negative_test_query90_00002", + "apositive_test_query304_00000" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query91", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.3391602052736161, + "recall@5": 0.1111111111111111 + }, + "query_id": "test_query91", + "ranked_ids": [ + "apositive_test_query91_00000", + "apositive_test_query342_00001", + "negative_test_query292_00009", + "negative_test_query303_00011", + "negative_test_query91_00010" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query92", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query92", + "ranked_ids": [ + "apositive_test_query198_00001", + "apositive_test_query92_00005", + "apositive_test_query258_00000", + "negative_test_query230_00006", + "negative_test_query92_00003" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query93", + "metrics": { + "mrr@5": 0.3333333333333333, + "ndcg@5": 0.16958010263680806, + "recall@5": 0.14285714285714285 + }, + "query_id": "test_query93", + "ranked_ids": [ + "negative_test_query335_00011", + "negative_test_query195_00001", + "apositive_test_query93_00004", + "negative_test_query335_00003", + "negative_test_query92_00012" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query94", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.8539316501572937, + "recall@5": 0.5 + }, + "query_id": "test_query94", + "ranked_ids": [ + "apositive_test_query94_00000", + "apositive_test_query94_00002", + "apositive_test_query94_00006", + "apositive_test_query127_00001", + "apositive_test_query94_00001" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query95", + "metrics": { + "mrr@5": 0.25, + "ndcg@5": 0.2640681225725909, + "recall@5": 0.5 + }, + "query_id": "test_query95", + "ranked_ids": [ + "negative_test_query95_00012", + "negative_test_query95_00005", + "negative_test_query95_00002", + "apositive_test_query95_00000", + "apositive_test_query253_00007" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query96", + "metrics": { + "mrr@5": 1.0, + "ndcg@5": 0.46927872602275644, + "recall@5": 0.3333333333333333 + }, + "query_id": "test_query96", + "ranked_ids": [ + "apositive_test_query96_00002", + "negative_test_query339_00005", + "negative_test_query96_00003", + "apositive_test_query339_00001", + "negative_test_query339_00014" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query97", + "metrics": { + "mrr@5": 0.5, + "ndcg@5": 0.21398626473452756, + "recall@5": 0.16666666666666666 + }, + "query_id": "test_query97", + "ranked_ids": [ + "negative_test_query51_00009", + "apositive_test_query97_00003", + "negative_test_query113_00009", + "negative_test_query113_00006", + "negative_test_query97_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query98", + "metrics": { + "mrr@5": 0.0, + "ndcg@5": 0.0, + "recall@5": 0.0 + }, + "query_id": "test_query98", + "ranked_ids": [ + "apositive_test_query14_00003", + "apositive_test_query201_00004", + "negative_test_query345_00002", + "negative_test_query98_00004", + "negative_test_query98_00011" + ], + "valid": true + }, + { + "algorithm": "cbdr", + "fixture_id": "askubuntu-bm25-test-test_query99", + "metrics": { + "mrr@5": 0.2, + "ndcg@5": 0.13120507751234178, + "recall@5": 0.1111111111111111 + }, + "query_id": "test_query99", + "ranked_ids": [ + "apositive_test_query253_00003", + "apositive_test_query333_00003", + "negative_test_query193_00002", + "apositive_test_query193_00010", + "apositive_test_query99_00001" + ], + "valid": true + } + ] +} \ No newline at end of file diff --git a/docs/specs/spec_cbdr_strategy.md b/docs/specs/spec_cbdr_strategy.md new file mode 100644 index 0000000..d46365c --- /dev/null +++ b/docs/specs/spec_cbdr_strategy.md @@ -0,0 +1,402 @@ +# Spec: CBDR Strategy + +## 1. 개요 (Overview) +- **작업 목적**: query-only confidence가 충분히 높으면 context reranking을 건너뛰고, 낮으면 `Conf(Q+C)-Conf(Q)` confidence gain으로 documents를 rerank하는 CBDR 본체 Strategy를 추가한다. +- **Reference**: + - `docs/wiki/references/parametric_post_retrieval_confidence.md` + - `docs/specs/spec_confidence_runtime_readiness.md` + - `docs/specs/spec_confidence_gain_reranking.md` +- **상태**: `[ ] Draft` | `[ ] In Progress` | `[x] Completed` + +이 스펙은 논문 원형을 그대로 재현하지 않는다. + +논문 원형의 CBDR은 query-only confidence가 높으면 retrieval 자체를 skip한다. +ranksmith는 retriever, indexer, vector search를 소유하지 않으므로 true pre-retrieval skip은 구현하지 않는다. +이번 스펙은 **reranking-side CBDR router**만 구현한다. + +즉, 이미 documents가 전달된 `rerank()` 호출 안에서 context reranking을 skip할 수 있다. +외부 retriever가 retrieval 호출 전에 사용할 query-only decision API는 이번 범위에 포함하지 않는다. + +핵심 목표: + +```text +base = Conf(Q) + +if base >= skip_threshold: + context scoring과 reranking을 수행하지 않음 + original order를 보존하고 cbdr_skipped metadata를 남김 +else: + after_i = Conf(Q + C_i) + gain_i = after_i - base + documents sorted by gain_i desc +``` + +## 2. 요구 사항 및 제약 (Requirements & Constraints) + +### 포함 범위 +- sync `CBDRStrategy` 추가. +- query-only confidence 기반 skip decision 추가. +- low-confidence query에서 confidence gain reranking 수행. +- `ConfidenceGainStrategy`와 공유 가능한 계산/검증 로직 정리. +- `AzureOpenAIReranker` sync facade에서 built-in strategy로 처리. +- `ranksmith.strategies` submodule export 추가. +- README/README.ko 및 architecture wiki 문서 반영. + +### 제외 범위 +- retriever integration. +- vector search/indexing. +- upstream retrieval 호출 자체를 중단하는 orchestration API. +- query-only `should_retrieve()` helper. +- async `CBDRStrategy`. +- closed model provider 병렬 호출. +- answer generation cache. +- confidence scorer 학습 또는 artifact 생성. +- benchmark 수치 README 반영. +- CBDR threshold 자동 튜닝. +- reranker fine-tuning. + +### 입력 (Inputs) + +```python +CBDRStrategy( + base_estimator: ConfidenceEstimator, + context_estimator: ConfidenceEstimator, + answer_generator: AnswerGenerator, + skip_threshold: float = 0.8, + max_document_chars: int = 4000, + algorithm: Literal["cbdr"] = "cbdr", +) +``` + +필수 task type: + +```text +base_estimator.task_type == "query_answerability_confidence" +context_estimator.task_type == "query_context_answerability_confidence" +``` + +`answer_generator`는 기존 `ConfidenceGainStrategy`와 같은 contract를 사용한다. + +```python +class AnswerGenerator(Protocol): + def answer_query(self, query: str) -> str: ... + def answer_with_context(self, query: str, context: str) -> str: ... +``` + +`model_client`는 `RerankStrategy` protocol 때문에 `rerank()` 인자로 받지만 사용하지 않는다. +answer generation은 `answer_generator` 책임이다. +CBDRStrategy는 `model_client` capability를 검사하지 않는다. + +### 출력 (Outputs) + +#### skip path + +`Conf(Q) >= skip_threshold`이면 context answer generation과 context confidence scoring을 수행하지 않는다. +입력 documents의 original order를 보존해 `RerankResult`를 반환한다. +rank는 반환 결과 기준 1-based로 부여한다. +original_index는 입력 documents 기준 0-based를 보존한다. + +```python +{ + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": True, + "base_confidence": 0.91, + "skip_threshold": 0.8, + "context_confidence": None, + "confidence_gain": None, +} +``` + +#### rerank path + +`Conf(Q) < skip_threshold`이면 confidence gain으로 정렬한다. +rank는 반환 결과 기준 1-based로 부여한다. +original_index는 입력 documents 기준 0-based를 보존한다. + +```python +{ + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": False, + "base_confidence": 0.42, + "skip_threshold": 0.8, + "context_confidence": 0.81, + "confidence_gain": 0.39, +} +``` + +### 제약 사항 (Constraints) +- hidden state, logits, attention에 직접 접근하지 않는다. +- runtime Strategy는 scorer artifact를 학습하지 않는다. +- `Conf(Q)`는 query당 한 번만 계산한다. +- 빈 documents 또는 `top_k == 0`이면 base answer generation과 confidence scoring을 수행하지 않고 `[]`를 반환한다. +- skip path에서는 `answer_with_context()`와 `context_estimator.score()`를 호출하지 않는다. +- skip path에서는 document text를 context로 읽지 않으므로 `max_document_chars` 검증도 수행하지 않는다. +- rerank path에서는 문서별로 `Conf(Q+C_i)`를 계산한다. +- skip 여부를 metadata에 반드시 남긴다. +- skip path에서 original order를 조용히 수정하지 않는다. +- rerank path에서 gain 동점은 original index 오름차순으로 안정 정렬한다. +- `top_k > 0`은 skip/rerank path 모두 최종 결과에 slicing으로만 적용한다. +- score는 finite float이며 `[0, 1]` 범위여야 한다. +- `skip_threshold`는 finite float이며 `[0, 1]` 범위여야 한다. +- bool은 numeric score 또는 `skip_threshold`로 인정하지 않는다. +- `skip_threshold=0.0`이면 non-empty documents에서 항상 skip된다. +- `skip_threshold=1.0`이면 `base_confidence == 1.0`일 때만 skip된다. +- skip path는 answer generation 1회와 confidence scoring 1회를 수행한다. +- rerank path는 문서 수가 `N`이면 answer generation `N + 1`회와 confidence scoring `N + 1`회를 수행한다. +- rerank path에서는 `top_k`가 작아도 모든 문서를 scoring한 뒤 slicing한다. +- confidence scoring 실패 시 기존 ranking으로 fallback하지 않는다. +- rerank path에서 `max_document_chars`를 초과하면 `DocumentTooLongError`로 실패한다. +- metadata에 answer text, scorer artifact path, HuggingFace token, raw hidden state, raw feature vector를 넣지 않는다. +- root import 확장은 사용자 승인 없이는 하지 않는다. + +## 3. 상세 설계 (Architecture & Design) + +### 동작 메커니즘 +1. query와 `top_k`를 검증한다. +2. `answer_generator.answer_query(query)`로 base answer를 생성한다. +3. `base_estimator.score(QueryAnswerabilityConfidenceInput(...))`로 `Conf(Q)`를 계산한다. +4. `base_confidence >= skip_threshold`이면 skip path로 간다. +5. skip path는 document length 검증과 context 호출 없이 original order를 보존해 결과를 만든다. +6. `base_confidence < skip_threshold`이면 rerank path로 간다. +7. rerank path에서 document length를 검증한다. +8. 문서별 `answer_with_context(query, document.text)`를 호출한다. +9. 문서별 `Conf(Q+C_i)`를 계산한다. +10. `gain_i = context_confidence_i - base_confidence`를 계산한다. +11. gain 내림차순, original index 오름차순으로 정렬한다. + +### 의사 알고리즘 (Pseudo-algorithm) + +```text +cbdr_rerank(query, documents, top_k): + validate query, top_k + + if documents is empty or top_k == 0: + return [] + + base_answer = answer_generator.answer_query(query) + base = score_base(query, base_answer) + + if base >= skip_threshold: + results = original_order_results( + cbdr_skipped=True, + base_confidence=base, + context_confidence=None, + confidence_gain=None, + ) + if top_k is not None: + results = results[:top_k] + assign rank from 1 over returned results + return results + + validate document lengths + scored = [] + for each document with original_index: + context_answer = answer_generator.answer_with_context(query, document.text) + context = score_context(query, document.text, context_answer) + gain = context - base + scored.append(original_index, context, gain) + + scored = sort scored by (-gain, original_index) + results = scored_results(scored) + if top_k is not None: + results = results[:top_k] + assign rank from 1 over returned results + return results +``` + +### 의사 코드 (Pseudo-code) + +```python +from ranksmith.strategies import CBDRStrategy + +strategy = CBDRStrategy( + base_estimator=query_estimator, + context_estimator=query_context_estimator, + answer_generator=answer_generator, + skip_threshold=0.8, +) + +reranker = AzureOpenAIReranker( + model_client=model_client, + strategy=strategy, +) + +results = reranker.rerank(query, documents, top_k=10) +``` + +### 통합 지점 (Integration Points) + +Strategy: +- `src/ranksmith/strategies/_cbdr.py` +- `src/ranksmith/strategies/_confidence_gain.py` +- `src/ranksmith/strategies/__init__.py` + +Facade: +- `src/ranksmith/azure.py` + +Tests: +- `tests/test_cbdr_strategy.py` + +Docs: +- `docs/wiki/02_architecture.md` +- `docs/specs/spec_cbdr_strategy.md` +- `README.md` +- `README.ko.md` + +## 4. 재사용 및 모듈화 (Reusability & Modularization) + +### Metadata contract +공통 confidence key: + +```python +{ + "base_confidence": float, + "context_confidence": float | None, + "confidence_gain": float | None, +} +``` + +CBDR 전용 key: + +```python +{ + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": bool, + "skip_threshold": float, +} +``` + +metadata에는 다음 값을 넣지 않는다. +- generated answer text +- scorer artifact path +- HuggingFace token +- raw hidden state +- raw structural feature vector + +### 공통 컴포넌트 식별 (Shared Components) +- `AnswerGenerator` protocol은 `ConfidenceGainStrategy`와 공유한다. +- `ConfidenceEstimator` protocol은 `ConfidenceGainStrategy`와 공유한다. +- task type 상수는 중복 정의하지 않는다. +- answer validation과 confidence score validation은 공유 helper로 분리한다. +- context gain 계산은 CBDR과 confidence gain strategy가 같은 helper를 사용한다. + +### 추상화 방안 (Abstraction Plan) +- `src/ranksmith/strategies/_confidence_gain.py`에 이미 있는 공통 요소를 private helper로 정리한다. +- `CBDRStrategy`는 `ConfidenceGainStrategy`를 상속하지 않는다. + - 이유: skip path가 있는 router와 pure confidence gain reranker는 의미가 다르다. +- 대신 shared helper를 사용해 검증, answer 호출, confidence gain 계산의 중복을 줄인다. + +권장 구조: + +```text +_confidence_gain.py + AnswerGenerator + ConfidenceEstimator + ConfidenceGainResult + validate_answer + validate_confidence_score + score_base_answerability + score_context_answerability + +_cbdr.py + CBDRStrategy +``` + +## 5. 에러 핸들링 (Error Handling) +- 빈 query: `RerankInputError` +- 빈 documents: `[]` +- `top_k == 0`: `[]` +- `top_k < 0`: `RerankInputError` +- `skip_threshold`가 finite probability가 아니거나 bool이면 `ValueError` +- `max_document_chars < 1`: `ValueError` +- low-confidence rerank path에서 document length 초과: `DocumentTooLongError` +- base/context estimator task mismatch: `RerankInputError` +- answer generator가 빈 answer 반환: `RerankProviderError` +- answer generator가 예외 발생: `RerankProviderError`로 래핑 +- direct `CBDRStrategy.rerank()`에서 confidence estimator의 기존 `RerankError`는 그대로 전파 +- direct `CBDRStrategy.rerank()`에서 confidence estimator의 unexpected exception은 그대로 전파 +- `AzureOpenAIReranker` facade에서 built-in strategy의 unexpected exception은 `RerankProviderError`로 래핑 +- confidence score가 finite probability가 아님: `RerankStrategyError` +- gain이 finite float가 아님: `RerankStrategyError` +- output ranking 보정 필요 상황: 조용히 보정하지 않고 실패 + +## 6. 테스트 계획 (Test Plan) + +### 성공 케이스 (Happy Paths) +- 빈 documents이면 `[]` 반환. +- 빈 documents에서 `answer_query()`와 estimator가 호출되지 않음. +- `top_k == 0`이면 `[]` 반환. +- `top_k == 0`에서 `answer_query()`와 estimator가 호출되지 않음. +- `base_confidence >= skip_threshold`이면 original order 보존. +- skip path rank는 반환 결과 기준 1-based. +- skip path original_index는 입력 documents 기준 0-based. +- skip path에서 `answer_with_context()`가 호출되지 않음. +- skip path에서 `context_estimator.score()`가 호출되지 않음. +- skip path metadata에 `cbdr_skipped=True` 기록. +- skip path에서 `top_k` slicing 적용. +- skip path에서 긴 document가 있어도 `DocumentTooLongError`가 발생하지 않음. +- `base_confidence < skip_threshold`이면 confidence gain으로 rerank. +- rerank path metadata에 `cbdr_skipped=False`, base/context/gain 기록. +- gain 동점 시 original index 유지. +- rerank path에서 `top_k`가 작아도 모든 문서를 scoring한 뒤 slicing. +- `skip_threshold=0.0`이면 non-empty documents에서 skip. +- `skip_threshold=1.0`이면 `base_confidence == 1.0`일 때만 skip. +- `AzureOpenAIReranker` facade에서 `CBDRStrategy` 예외 surface가 built-in strategy와 동일함. + +### 엣지/실패 케이스 (Edge & Failure Cases) +- 잘못된 `skip_threshold` 실패. +- bool `skip_threshold` 실패. +- 잘못된 estimator task type 실패. +- 빈 query 실패. +- low-confidence rerank path에서만 긴 document 실패. +- answer generator empty output 실패. +- base confidence score 범위 밖 실패. +- context confidence score 범위 밖 실패. +- base scoring 실패가 skip fallback으로 숨겨지지 않음. +- context scoring 실패가 original order fallback으로 숨겨지지 않음. + +### 공통 Reranking Smoke/Benchmark +- fake answer generator와 fake confidence estimator로 deterministic smoke test를 추가한다. +- artifact load 기반 skip path E2E smoke를 추가해 `from_artifact()` -> `CBDRStrategy` -> `AzureOpenAIReranker` 경로를 검증한다. +- artifact load 기반 rerank path E2E smoke를 추가해 `from_artifact()` -> `CBDRStrategy` -> `AzureOpenAIReranker` 경로를 검증한다. +- 실제 closed model live test는 credential/cost 때문에 opt-in으로 분리한다. +- README benchmark 수치는 추가하지 않는다. + +검증 명령: + +```bash +uv run pytest tests/test_cbdr_strategy.py tests/test_confidence_gain_strategy.py -q +./scripts/verify.sh +``` + +--- + +## 7. 작업 태스크 추적 (Task Checklist) + +### Phase 1: 컨텍스트 및 설계 확인 +- [x] 관련 기존 코드베이스 및 Wiki 문서 확인 +- [x] CBDR을 ranksmith에서 reranking-side router로 정의 +- [x] 스펙 문서 작성 + +### Phase 2: 로직 구현 (Implementation) +- [x] `src/ranksmith/strategies/_confidence_gain.py`: 공통 protocol/helper 정리 +- [x] `src/ranksmith/strategies/_cbdr.py`: `CBDRStrategy` 구현 +- [x] `src/ranksmith/strategies/__init__.py`: strategy export 추가 +- [x] `src/ranksmith/azure.py`: built-in sync strategy 처리 추가 + +### Phase 3: 검증 (Verification) +- [x] `tests/test_cbdr_strategy.py`: skip path 정상 케이스 추가 +- [x] `tests/test_cbdr_strategy.py`: rerank path 정상 케이스 추가 +- [x] `tests/test_cbdr_strategy.py`: 엣지/실패 케이스 추가 +- [x] `tests/test_cbdr_strategy.py`: Azure facade smoke 추가 +- [x] `tests/test_cbdr_strategy.py`: artifact load 기반 skip path E2E smoke 추가 +- [x] `tests/test_cbdr_strategy.py`: artifact load 기반 rerank path E2E smoke 추가 +- [x] `./scripts/verify.sh` 스크립트를 통한 린트/타입/전체 테스트 통과 확인 + +### Phase 4: 완료 및 정리 +- [x] `docs/wiki/02_architecture.md`: CBDR strategy 위치 추가 +- [x] `README.md` / `README.ko.md`: benchmark 없는 usage 문서 추가 +- [x] 본 문서 최상단의 **상태**를 `Completed`로 변경 diff --git a/docs/specs/spec_cbdr_user_facing_integration.md b/docs/specs/spec_cbdr_user_facing_integration.md new file mode 100644 index 0000000..a44b9e3 --- /dev/null +++ b/docs/specs/spec_cbdr_user_facing_integration.md @@ -0,0 +1,105 @@ +# Spec: CBDR User-Facing Integration Layer + +## 1. 개요 (Overview) +- **작업 목적**: scorer artifact와 Azure 설정만으로 `CBDRStrategy`를 쉽게 생성하고, 기존 benchmark runner에서 `--algorithm cbdr`로 실행할 수 있게 한다. +- **Reference**: + - `docs/specs/spec_cbdr_strategy.md` + - `docs/specs/spec_confidence_gain_reranking.md` + - `docs/wiki/references/parametric_post_retrieval_confidence.md` +- **상태**: `[ ] Draft` | `[ ] In Progress` | `[x] Completed` + +## 2. 요구 사항 및 제약 (Requirements & Constraints) +- **입력 (Inputs)**: + - `CBDRStrategy.from_artifacts(...)`: base/context scorer artifact path, optional metadata path, answer generator, threshold, HF 로딩 옵션. + - `AzureAnswerGenerator`: Azure OpenAI credential/config 또는 기존 환경 변수, no-answer sentinel. + - `compare_reranking.py`: `--algorithm cbdr`, CBDR artifact flags, HF/runtime flags. +- **출력 (Outputs)**: + - 기존 `CBDRStrategy.rerank(...)`와 동일한 `list[RerankResult]`. + - benchmark report에는 CBDR method settings와 provider call upper bound estimate를 기록한다. +- **제약 사항 (Constraints)**: + - CBDR은 sync만 지원한다. + - root import는 확장하지 않는다. + - scorer training, retriever integration, async CBDR은 제외한다. + - 잘못된 설정과 malformed closed-model output은 fast fail한다. + +## 3. 상세 설계 (Architecture & Design) +- `CBDRStrategy.from_artifacts(...)`는 `StructuralConfidenceEstimator.from_artifact(...)`를 두 번 호출해 base/context estimator를 만든다. +- `ranksmith.integrations.AzureAnswerGenerator`는 query-only와 query+context answer 생성을 담당한다. +- `AzureAnswerGenerator`는 generation pipeline과 같은 no-answer sentinel 계약을 prompt에 포함하고, JSON object 응답에서 `answer` 문자열만 파싱한다. +- `compare_reranking.py`는 CBDR일 때 factory와 Azure answer generator를 조립한 뒤 `CBDRStrategy.rerank(...)`를 직접 호출한다. + +### Pseudo-algorithm +```text +create CBDR: + base_estimator = StructuralConfidenceEstimator.from_artifact(base_artifact, task=query_answerability_confidence, ...) + context_estimator = StructuralConfidenceEstimator.from_artifact(context_artifact, task=query_context_answerability_confidence, ...) + return CBDRStrategy(base_estimator, context_estimator, answer_generator, threshold) + +benchmark cbdr: + validate live opt-in + validate artifact flags + answer_generator = AzureAnswerGenerator.from_env(...) + strategy = CBDRStrategy.from_artifacts(...) + results = strategy.rerank(query=query, documents=documents) +``` + +### Integration Points +- `src/ranksmith/strategies/_cbdr.py` +- `src/ranksmith/integrations/` +- `scripts/compare_reranking.py` +- `tests/test_cbdr_strategy.py` +- `tests/test_azure_answer_generator.py` +- `tests/test_compare_reranking.py` + +## 4. 재사용 및 모듈화 (Reusability & Modularization) +- `CBDRStrategy.from_artifacts(...)`는 estimator 조립만 담당하고 reranking logic은 기존 `CBDRStrategy.rerank(...)`를 재사용한다. +- `AzureAnswerGenerator`는 Strategy가 아니라 integration helper다. +- benchmark runner는 CBDR 전용 설정만 추가하고 기존 algorithm selection/report 구조를 유지한다. + +## 5. 에러 핸들링 (Error Handling) +- artifact path 누락: `SystemExit` 또는 기존 artifact error. +- 잘못된 confidence task type: 기존 `ConfidenceArtifactError`. +- malformed answer JSON: `RerankParseError`. +- missing/empty answer: `RerankParseError`. +- Azure env 누락: `RerankInputError`. +- invalid no-answer sentinel: `ValueError`. + +## 6. 테스트 계획 (Test Plan) +- **성공 케이스**: + - `CBDRStrategy.from_artifacts(...)`가 base/context artifact를 올바른 task type으로 로드한다. + - `AzureAnswerGenerator`가 `{"answer": "..."}`를 파싱한다. + - `AzureAnswerGenerator` prompt가 no-answer sentinel 계약을 포함한다. + - `compare_reranking.py --algorithm cbdr`가 CBDR strategy를 생성한다. +- **엣지/실패 케이스**: + - artifact flag 누락 시 실패. + - `--allow-live` 없으면 CBDR 실행 차단. + - `--cbdr-max-document-chars`가 strategy factory로 전달됨. + - malformed/missing/empty answer는 실패. + - HF token env 이름은 실제 env 값으로 resolve한다. +- **공통 Reranking Smoke/Benchmark**: + - live provider는 opt-in으로만 실행한다. + - CBDR provider call estimate는 upper bound로 기록한다. + +--- + +## 7. 작업 태스크 추적 (Task Checklist) + +### Phase 1: 컨텍스트 및 설계 확인 +- [x] 관련 기존 코드베이스 및 Wiki 문서 확인 +- [x] 스펙 문서(본 문서) 상의 의사 코드 설계 검토 및 확정 + +### Phase 2: 로직 구현 (Implementation) +- [x] `src/ranksmith/strategies/_cbdr.py`: `from_artifacts(...)` factory 추가 +- [x] `src/ranksmith/integrations/`: `AzureAnswerGenerator` 추가 +- [x] `scripts/compare_reranking.py`: CBDR algorithm/flags/estimate 연결 + +### Phase 3: 검증 (Verification) +- [x] `tests/test_cbdr_strategy.py`: factory 테스트 추가 +- [x] `tests/test_azure_answer_generator.py`: answer generator 테스트 추가 +- [x] `tests/test_compare_reranking.py`: benchmark 연결 테스트 추가 +- [x] `./scripts/verify.sh` 스크립트를 통한 린트/타입/전체 테스트 통과 확인 + +### Phase 4: 완료 및 정리 +- [x] `docs/wiki/02_architecture.md` 업데이트 +- [x] `README.md`, `README.ko.md` 업데이트 +- [x] 본 문서 최상단의 **상태**를 `Completed`로 변경 diff --git a/docs/specs/spec_confidence_gain_reranking.md b/docs/specs/spec_confidence_gain_reranking.md new file mode 100644 index 0000000..e84445d --- /dev/null +++ b/docs/specs/spec_confidence_gain_reranking.md @@ -0,0 +1,415 @@ +# Spec: Confidence Gain Reranking + +## 1. 개요 (Overview) +- **작업 목적**: post-retrieval context가 closed model의 answerability confidence를 얼마나 올리는지 계산하고, 그 변화량으로 documents를 rerank한다. +- **Reference**: + - `docs/wiki/references/parametric_post_retrieval_confidence.md` + - `docs/wiki/references/structural_confidence.md` + - `docs/specs/spec_structural_confidence.md` + - `docs/specs/spec_confidence_runtime_readiness.md` + - `docs/specs/spec_confidence_training_pipeline.md` + - `docs/specs/spec_confidence_generation_pipeline.md` +- **상태**: `[ ] Draft` | `[ ] In Progress` | `[x] Completed` + +이 스펙은 논문 원형을 그대로 재현하지 않는다. + +논문 원형은 target LLM의 hidden state 기반 confidence detector와 reranker fine-tuning을 사용한다. +ranksmith는 closed model API와 runtime training-free reranking을 지향하므로, 기존 structural confidence module을 confidence proxy로 사용한다. +runtime reranking은 training-free다. +단, confidence scorer artifact는 사전에 generation/training pipeline으로 준비되어 있어야 한다. + +핵심 목표: + +```text +base = Conf(Q) +after_i = Conf(Q + C_i) +gain_i = after_i - base +documents sorted by gain_i desc +``` + +## 2. 요구 사항 및 제약 (Requirements & Constraints) + +### 포함 범위 +- query-only answerability confidence task 추가. +- query+context answerability confidence task 추가. +- confidence generation pipeline에서 두 task의 canonical JSONL 생성 지원. +- confidence training pipeline에서 두 task 학습 지원. +- confidence runtime에서 두 task inference 지원. +- confidence gain 계산 utility 추가. +- confidence gain 기반 sync reranking Strategy 추가. + +### 제외 범위 +- CBDR retrieval skip 구현. +- retriever integration. +- vector search/indexing. +- async confidence gain strategy. +- closed model provider 병렬 호출. +- answer generation cache. +- reranker fine-tuning. +- benchmark 수치 README 반영. +- semantic feature fusion. +- hidden state, logits, attention 직접 사용. + +CBDR은 이 스펙의 직접 범위가 아니다. +다만 `Conf(Q)`와 `gain_i`를 metadata로 남겨 후속 CBDR spec에서 재사용 가능하게 한다. + +### 입력 (Inputs) + +#### 새 confidence task type +기존: + +```python +TaskType = Literal["answer_confidence", "judgment_confidence"] +``` + +확장: + +```python +TaskType = Literal[ + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", +] +``` + +#### 새 runtime input type + +```python +@dataclass(frozen=True) +class QueryAnswerabilityConfidenceInput: + query: str + answer: str + +@dataclass(frozen=True) +class QueryContextAnswerabilityConfidenceInput: + query: str + context: str + answer: str +``` + +`answer`는 closed model이 생성한 답변이다. +confidence scorer는 “이 입력 조건에서 생성된 answer가 맞을 가능성”을 추정한다. + +#### generation raw JSONL + +Query-only answerability input: + +```json +{ + "id": "sample-1::base", + "query": "who played karen in married to the mob?", + "gold_answer": "Nancy Travis", + "source": "nq", + "group_id": "sample-1", + "metadata": {} +} +``` + +Query+context answerability input: + +```json +{ + "id": "sample-1::doc-1", + "query": "who played karen in married to the mob?", + "context": "Angela de Marco ... Karen (Nancy Travis).", + "gold_answer": "Nancy Travis", + "source": "nq", + "group_id": "sample-1", + "metadata": {} +} +``` + +### 출력 (Outputs) + +#### canonical JSONL + +`query_answerability_confidence`: + +```json +{ + "id": "sample-1::base", + "task_type": "query_answerability_confidence", + "query": "...", + "answer": "Nancy Travis", + "gold_answer": "Nancy Travis", + "label": 1, + "source": "nq", + "group_id": "sample-1", + "metadata": { + "generation": {}, + "input_metadata": {} + } +} +``` + +`query_context_answerability_confidence`: + +```json +{ + "id": "sample-1::doc-1", + "task_type": "query_context_answerability_confidence", + "query": "...", + "context": "...", + "answer": "Nancy Travis", + "gold_answer": "Nancy Travis", + "label": 1, + "source": "nq", + "group_id": "sample-1", + "metadata": { + "generation": {}, + "input_metadata": {} + } +} +``` + +#### runtime confidence gain result + +```python +@dataclass(frozen=True) +class ConfidenceGainResult: + base_score: float + context_score: float + gain: float + base_result: StructuralConfidenceResult + context_result: StructuralConfidenceResult +``` + +#### reranking result metadata + +`ConfidenceGainStrategy`가 반환하는 `RerankResult.metadata`: + +```python +{ + "strategy": "confidence_gain", + "algorithm": "confidence_gain", + "base_confidence": 0.42, + "context_confidence": 0.81, + "confidence_gain": 0.39, +} +``` + +### 제약 사항 (Constraints) +- hidden state, logits, attention에 직접 접근하지 않는다. +- runtime Strategy는 scorer artifact를 학습하지 않는다. +- scorer artifact는 task type별로 별도 사용한다. +- `Conf(Q)`는 query당 한 번만 계산한다. +- `Conf(Q+C_i)`는 문서별로 계산한다. +- runtime reranking은 기본적으로 answer generation `N + 1`회와 confidence scoring `N + 1`회를 수행한다. +- 호출 수를 조용히 줄이기 위해 일부 문서를 skip하지 않는다. +- score는 finite float이며 `[0, 1]` 범위여야 한다. +- gain은 `[-1, 1]` 범위의 finite float이어야 한다. +- gain 동점은 original index 오름차순으로 안정 정렬한다. +- confidence scoring 실패 시 기존 ranking으로 fallback하지 않는다. +- `top_k`는 정렬 후 slicing만 수행한다. +- `max_document_chars`를 초과하면 `DocumentTooLongError`로 실패한다. +- root import 확장은 사용자 승인 없이는 하지 않는다. + +## 3. 상세 설계 (Architecture & Design) + +### 동작 메커니즘 +1. closed model generation pipeline이 query-only 답변을 생성한다. +2. 같은 pipeline이 query+context 답변을 문서별로 생성한다. +3. gold answer와 normalized exact match로 canonical label을 만든다. +4. training pipeline이 task type별 scorer artifact를 학습한다. +5. runtime에서 base estimator와 context estimator를 로드한다. +6. Strategy 호출 시 base answer를 생성하거나 caller가 제공한다. +7. Strategy 호출 시 각 context answer를 생성하거나 caller가 제공한다. +8. base confidence와 context confidence를 계산한다. +9. `gain = context_confidence - base_confidence`를 계산한다. +10. gain 내림차순, original index 오름차순으로 정렬한다. + +### Runtime answer 생성 정책 +confidence gain에는 answer text가 필요하다. + +1차 구현은 Strategy 내부에서 closed model answer generation까지 수행하지 않는다. + +대신 Strategy는 answer generator hook을 받는다. + +```python +AnswerGenerator = Protocol: + def answer_query(self, query: str) -> str: ... + def answer_with_context(self, query: str, context: str) -> str: ... +``` + +이렇게 분리하면 confidence reranking과 answer generation 책임이 섞이지 않는다. +기존 `ModelClient.rank/compare/select` 계약도 변경하지 않는다. + +호출 비용: +- 문서 수가 `N`이면 `answer_query` 1회, `answer_with_context` `N`회를 호출한다. +- confidence scoring도 base 1회, context `N`회를 수행한다. +- 호출 수 절감을 위한 cache/batching은 이번 범위에서 제외한다. + +### 의사 알고리즘 (Pseudo-algorithm) + +```text +confidence_gain_rerank(query, documents): + validate inputs + base_answer = answer_generator.answer_query(query) + base_score = base_estimator.score(QueryAnswerabilityInput(query, base_answer)) + + scored = [] + for each document with original_index: + context_answer = answer_generator.answer_with_context(query, document.text) + context_score = context_estimator.score( + QueryContextAnswerabilityInput(query, document.text, context_answer) + ) + gain = context_score - base_score + scored.append(original_index, context_score, gain) + + order = sort scored by (-gain, original_index) + return RerankResult list +``` + +### 의사 코드 (Pseudo-code) + +```python +strategy = ConfidenceGainStrategy( + base_estimator=query_estimator, + context_estimator=query_context_estimator, + answer_generator=answer_generator, +) + +results = strategy.rerank( + query="who played karen in married to the mob?", + documents=documents, + model_client=model_client, + top_k=5, +) +``` + +`model_client`는 Strategy protocol 때문에 인자로 받지만, 1차 구현에서는 사용하지 않는다. +대신 `answer_generator`가 answer generation 책임을 가진다. + +### 통합 지점 (Integration Points) + +Confidence runtime: +- `src/ranksmith/confidence/_types.py` +- `src/ranksmith/confidence/_templates.py` +- `src/ranksmith/confidence/_scorer.py` +- `src/ranksmith/confidence/_structural.py` +- `src/ranksmith/confidence/__init__.py` + +Confidence generation: +- `src/ranksmith/confidence_generation/_types.py` +- `src/ranksmith/confidence_generation/_io.py` +- `src/ranksmith/confidence_generation/_prompts.py` +- `src/ranksmith/confidence_generation/_pipeline.py` +- `src/ranksmith/confidence_generation/__init__.py` + +Confidence training: +- `src/ranksmith/confidence_training/_types.py` +- `src/ranksmith/confidence_training/_dataset.py` +- `src/ranksmith/confidence_training/_features.py` +- `src/ranksmith/confidence_training/_artifact.py` + +Strategy: +- `src/ranksmith/strategies/_confidence_gain.py` +- `src/ranksmith/strategies/__init__.py` + +Docs: +- `docs/wiki/02_architecture.md` +- `docs/wiki/04_references_index.md` +- `README.md` +- `README.ko.md` + +## 4. 재사용 및 모듈화 (Reusability & Modularization) + +### 공통 컴포넌트 식별 (Shared Components) +- normalized exact match labeling은 기존 confidence generation labeling helper를 재사용한다. +- canonical JSONL validation은 기존 confidence training dataset validator 패턴을 확장한다. +- structural feature extraction은 기존 `structural-v1`을 그대로 재사용한다. +- batch scoring은 기존 `StructuralConfidenceEstimator.score_batch()`를 재사용한다. + +### 추상화 방안 (Abstraction Plan) +- answer generation은 Strategy 내부 private prompt로 직접 만들지 않는다. +- `AnswerGenerator` protocol로 분리한다. +- confidence gain 계산은 Strategy와 분리된 utility로 둔다. + +후속 CBDR spec은 같은 utility를 사용해 `Conf(Q) >= beta` retrieval skip을 구현할 수 있다. + +## 5. 에러 핸들링 (Error Handling) +- 빈 query: `RerankInputError` +- 빈 documents: `[]` +- `top_k < 0`: `RerankInputError` +- document length 초과: `DocumentTooLongError` +- base/context estimator task mismatch: `RerankInputError` +- answer generator가 빈 answer 반환: `RerankProviderError` +- answer generator가 예외 발생: `RerankProviderError`로 래핑 +- confidence estimator score 실패: 해당 confidence error를 감싸지 않고 전파 +- confidence score가 finite probability가 아님: `ConfidenceArtifactError` +- gain이 finite float가 아님: `RerankStrategyError` +- output ranking 보정 필요 상황: 조용히 보정하지 않고 실패 + +## 6. 테스트 계획 (Test Plan) + +### 성공 케이스 (Happy Paths) +- query-only input template 생성. +- query+context input template 생성. +- 새 task type scorer metadata validation 성공. +- canonical JSONL validation 성공. +- base/context confidence로 gain 계산. +- gain 내림차순 정렬. +- gain 동점 시 original index 유지. +- `top_k` slicing. +- metadata에 base/context/gain 기록. +- answer generator 호출 수가 `N + 1`인지 확인. + +### 엣지/실패 케이스 (Edge & Failure Cases) +- unsupported task type 실패. +- task type과 input dataclass mismatch 실패. +- scorer artifact task mismatch 실패. +- 빈 query/context/answer 실패. +- answer generator empty output 실패. +- context estimator를 query-only task로 주입하면 실패. +- base estimator를 query+context task로 주입하면 실패. +- score 범위 밖 실패. +- `max_document_chars` 초과 실패. + +### 공통 Reranking Smoke/Benchmark +- synthetic answer generator와 fake confidence estimator로 deterministic reranking smoke test를 추가한다. +- 실제 LLM live test는 credential/cost 때문에 opt-in으로 분리한다. +- README benchmark 수치는 추가하지 않는다. + +검증 명령: + +```bash +uv run pytest tests/test_confidence_*.py tests/test_confidence_generation_*.py tests/test_confidence_training_*.py tests/test_confidence_gain_strategy.py -q +uv run ruff check src/ranksmith tests +uv run mypy src/ranksmith tests +./scripts/verify.sh +``` + +--- + +## 7. 작업 태스크 추적 (Task Checklist) + +### Phase 1: 컨텍스트 및 설계 확인 +- [x] 관련 기존 코드베이스 및 Wiki 문서 확인 +- [x] 스펙 문서(본 문서) 상의 의사 코드 설계 검토 및 확정 + +### Phase 2: Confidence task 확장 +- [x] `src/ranksmith/confidence/_types.py`: query-only/contextual answerability input type 추가 +- [x] `src/ranksmith/confidence/_templates.py`: 새 input template 추가 +- [x] `src/ranksmith/confidence/_scorer.py`: metadata task validation 확장 +- [x] `src/ranksmith/confidence/__init__.py`: submodule export 추가 +- [x] `tests/test_confidence_*.py`: 새 task runtime tests 추가 + +### Phase 3: Generation/Training 확장 +- [x] `src/ranksmith/confidence_generation/*`: answerability generation config/pipeline 추가 +- [x] `src/ranksmith/confidence_training/*`: canonical schema/task validation 확장 +- [x] `tests/test_confidence_generation_*.py`: 새 generation tests 추가 +- [x] `tests/test_confidence_training_*.py`: 새 training tests 추가 + +### Phase 4: Confidence gain Strategy +- [x] `src/ranksmith/strategies/_confidence_gain.py`: `ConfidenceGainStrategy` 구현 +- [x] `src/ranksmith/strategies/__init__.py`: strategy export 추가 +- [x] `tests/test_confidence_gain_strategy.py`: deterministic unit tests 추가 +- [x] 필요 시 `tests/fixtures/reranking_smoke_fixture.jsonl` 기반 smoke test 추가/검토 + +### Phase 5: 문서 및 검증 +- [x] `docs/wiki/02_architecture.md`: confidence gain strategy 위치 추가 +- [x] `docs/wiki/04_references_index.md`: reference 상태 갱신 +- [x] `README.md` / `README.ko.md`: benchmark 없는 usage 문서 추가 +- [x] `./scripts/verify.sh` 스크립트를 통한 린트/타입/전체 테스트 통과 확인 +- [x] 본 문서 최상단의 **상태**를 `Completed`로 변경 diff --git a/docs/specs/spec_lmstudio_confidence_training_pipeline.md b/docs/specs/spec_lmstudio_confidence_training_pipeline.md new file mode 100644 index 0000000..db1ae16 --- /dev/null +++ b/docs/specs/spec_lmstudio_confidence_training_pipeline.md @@ -0,0 +1,513 @@ +# Spec: LM Studio Confidence Training Pipeline + +## 1. 개요 (Overview) +- **작업 목적**: MacBook에서 LM Studio로 로컬 LLM을 배포해 answerability confidence 학습 데이터를 생성하고, CBDR이 사용할 `Conf(Q)` / `Conf(Q+C)` scorer artifact를 재현 가능하게 만든다. +- **Reference**: + - `docs/wiki/references/structural_confidence.md` + - `docs/wiki/references/parametric_post_retrieval_confidence.md` + - `docs/specs/spec_confidence_generation_pipeline.md` + - `docs/specs/spec_confidence_training_pipeline.md` + - `docs/specs/spec_confidence_gain_reranking.md` + - `docs/specs/spec_cbdr_strategy.md` + - `docs/specs/spec_cbdr_user_facing_integration.md` + - LM Studio model page: `https://lmstudio.ai/models/google/gemma-4-12b` +- **상태**: `[ ] Draft` | `[ ] In Progress` | `[x] Completed` + +이번 작업은 새 reranking algorithm이 아니다. +이미 구현된 `confidence_generation`, `confidence_training`, `CBDRStrategy`를 로컬 LM Studio 환경에서 실제로 운용하기 위한 integration / CLI / run artifact layer다. + +## 2. 요구 사항 및 제약 (Requirements & Constraints) + +### 2.1 목표 +- LM Studio OpenAI-compatible server를 `ModelProvider`로 사용할 수 있어야 한다. +- query-only answerability dataset과 query+context answerability dataset을 생성할 수 있어야 한다. +- 두 dataset에서 각각 scorer artifact를 학습할 수 있어야 한다. +- 학습 결과는 `CBDRStrategy.from_artifacts(...)`에 바로 연결 가능해야 한다. +- 단일 도메인 과적합을 피하기 위해 source / group 기반 리포트를 남겨야 한다. + +### 2.2 입력 (Inputs) +- LM Studio runtime: + - `base_url`: 기본값 `http://localhost:1234/v1` + - `model`: 명시 인자 또는 환경 변수 + - `api_key`: LM Studio 호환용 optional 값 + - `timeout` + - 권장 context length: `16384` + - 권장 max output tokens: `128` +- raw generation dataset: + - 권장 생성 경로: `scripts/build_qa_confidence_raw_dataset.py` + - TriviaQA-style QA examples를 입력으로 사용한다. + - positive context는 answer alias를 포함하는 evidence로 제한한다. + - negative context는 질문 기준 TF-IDF retrieved context 중 현재 answer alias를 포함하지 않는 context로 생성한다. + - synthetic fixed seed QA는 실제 학습 데이터로 사용하지 않는다. + - `query_answerability_confidence` + - `id` + - `query` + - `gold_answer` + - optional `source`, `group_id`, `metadata` + - `query_context_answerability_confidence` + - `id` + - `query` + - `context` + - `gold_answer` + - optional `source`, `group_id`, `metadata` +- training config: + - `task_type` + - `dataset_path` + - `output_dir` + - `export_path` + - HuggingFace encoder options + - split options + +### 2.3 출력 (Outputs) +권장 run directory: + +```text +runs/confidence// + raw/ + query_answerability.jsonl + query_context_answerability.jsonl + canonical/ + query_answerability_confidence.jsonl + query_context_answerability_confidence.jsonl + training/ + query_answerability/ + model.joblib + metadata.json + report.json + report.md + query_context_answerability/ + model.joblib + metadata.json + report.json + report.md + artifacts/ + query_answerability.joblib + query_context_answerability.joblib + reports/ + generation_summary.json + dataset_balance.json + generalization_report.json +``` + +### 2.4 제약 사항 (Constraints) +- Fast fail을 유지한다. +- JSON 응답을 조용히 보정하지 않는다. +- hidden state, logits, attention, logprobs에 의존하지 않는다. +- LM Studio 모델은 scorer가 아니라 label 생성을 위한 answer generator다. +- confidence scorer 학습은 기존 `FrozenAutoEncoder + structural-v1 features + LightGBM + sigmoid calibration`을 사용한다. +- public root import는 늘리지 않는다. +- `ranksmith.integrations` submodule export는 허용한다. +- scorer training 결과를 README benchmark 수치로 쓰려면 실제 benchmark summary artifact가 필요하다. + +## 3. 상세 설계 (Architecture & Design) + +### 3.1 전체 흐름 +```text +TriviaQA/NQ-style QA evidence examples + -> build_qa_confidence_raw_dataset.py + -> raw QA/context examples + -> LMStudioModelProvider + -> confidence_generation canonical JSONL + -> confidence_training scorer artifacts + -> CBDRStrategy.from_artifacts(...) + -> compare_reranking.py --algorithm cbdr +``` + +### 3.2 LM Studio Provider +새 provider는 `ModelProvider` protocol만 만족한다. + +통합 지점: + +```text +src/ranksmith/integrations/_lmstudio_provider.py +src/ranksmith/integrations/__init__.py +tests/test_lmstudio_provider.py +``` + +Public submodule API: + +```python +from ranksmith.integrations import LMStudioModelProvider + +provider = LMStudioModelProvider( + base_url="http://localhost:1234/v1", + model="google/gemma-4-12b", + api_key="lm-studio", + max_tokens=128, + timeout=60, +) +``` + +기본 정책: +- `base_url`: `LMSTUDIO_BASE_URL` 또는 `http://localhost:1234/v1` +- `model`: 명시 인자 또는 `LMSTUDIO_MODEL` +- `api_key`: `LMSTUDIO_API_KEY` 또는 `"lm-studio"` +- `temperature`: `ModelRequest.temperature` 그대로 사용. generation pipeline에서는 이미 `0`을 전달한다. +- `response_format`: ranksmith의 `json_object` 요청을 LM Studio의 `json_schema` 요청으로 변환한다. +- `max_tokens`: 기본값 `128`, 명시 인자로 조정 가능. + +LM Studio 0.4.16 기준 실제 확인 결과: + +```text +response_format={"type":"json_object"} -> 실패 +response_format={"type":"json_schema", ...} -> 성공 +``` + +따라서 `LMStudioModelProvider`는 `ModelRequest.response_format == "json_object"`일 때 아래 schema를 전달한다. + +```json +{ + "type": "json_schema", + "json_schema": { + "name": "ranksmith_json_response", + "schema": { + "type": "object" + } + } +} +``` + +answerability task의 최종 출력 형태 `{"answer":"..."}`는 기존 prompt와 strict parser가 검증한다. + +LM Studio / Gemma 4 12B 운영 권장값: + +```text +model: google/gemma-4-12b +context length: 16384 +max output tokens: 128 +temperature: 0 +thinking: disabled when supported by LM Studio runtime +response_format: json_schema +output contract: {"answer":"..."} +``` + +`thinking`은 OpenAI-compatible 표준 필드가 아니므로 provider public config에 넣지 않는다. +LM Studio 런타임 UI 또는 모델 설정에서 끄는 운영 조건으로 문서화한다. + +`query_context_answerability_confidence` 생성에서는 기본 `max_context_chars`를 `8000`으로 권장한다. +이는 LM Studio context length `16384`에서 system prompt, JSON schema, query 여유를 확보하기 위한 값이다. +단, ranksmith 원칙상 숨은 truncation은 하지 않는다. +`max_context_chars` 초과 입력은 기존 generation loader 정책대로 명시적으로 실패하거나, 사용자가 CLI 옵션으로 한도를 조정해야 한다. + +### 3.3 QA Raw Dataset Builder +LM Studio 호출 전에 원천 QA dataset을 ranksmith generation raw schema로 변환한다. + +통합 지점: + +```text +scripts/build_qa_confidence_raw_dataset.py +``` + +초기 공식 지원: +- `--source triviaqa` +- HuggingFace `mandarjoshi/trivia_qa`, 기본 config `rc` +- 기본 split `train[:20000]` +- 기본 출력: + - `query_answerability_raw.jsonl` + - `query_context_answerability_raw.jsonl` + - `dataset_manifest.json` + +정책: +- positive query-context row는 정답 alias가 들어 있는 evidence context만 사용한다. +- negative query-context row는 질문과 유사한 retrieved context를 쓰되, 현재 정답 alias가 포함되면 제외한다. +- context가 `--max-context-chars`를 넘으면 조용히 자르지 않고 제외한다. +- 요청한 row 수를 만들 수 없으면 fast-fail한다. + +예시: + +```bash +uv run --with datasets python scripts/build_qa_confidence_raw_dataset.py \ + --source triviaqa \ + --dataset-name mandarjoshi/trivia_qa \ + --dataset-config rc \ + --split 'train[:20000]' \ + --output-dir runs/confidence/local/raw \ + --max-source-items 20000 \ + --max-query-items 5000 \ + --max-query-context-items 5000 \ + --max-context-chars 8000 +``` + +### 3.4 Dataset Generation CLI +기존 `ranksmith.confidence_generation`은 library API만 제공한다. +로컬 실험을 재현 가능하게 하려면 얇은 CLI가 필요하다. + +통합 지점: + +```text +scripts/generate_confidence_dataset.py +tests/test_generate_confidence_dataset_script.py +``` + +지원 task: +- `query_answerability_confidence` +- `query_context_answerability_confidence` + +초기 범위에서는 `answer_confidence`, `judgment_confidence` CLI는 추가하지 않는다. +CBDR에 필요한 두 task만 먼저 고정한다. + +예시: + +```bash +uv run python scripts/generate_confidence_dataset.py \ + --task query_answerability_confidence \ + --provider lmstudio \ + --lmstudio-base-url http://localhost:1234/v1 \ + --lmstudio-model google/gemma-4-12b \ + --input runs/confidence/local/raw/query_answerability.jsonl \ + --output runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --resume +``` + +```bash +uv run python scripts/generate_confidence_dataset.py \ + --task query_context_answerability_confidence \ + --provider lmstudio \ + --lmstudio-base-url http://localhost:1234/v1 \ + --lmstudio-model google/gemma-4-12b \ + --input runs/confidence/local/raw/query_context_answerability.jsonl \ + --output runs/confidence/local/canonical/query_context_answerability_confidence.jsonl \ + --max-context-chars 8000 \ + --resume +``` + +### 3.5 Training CLI +기존 `ranksmith.confidence_training`도 library API만 제공한다. +학습 재현성을 위해 CLI를 추가한다. + +통합 지점: + +```text +scripts/train_confidence_scorer.py +tests/test_train_confidence_scorer_script.py +``` + +예시: + +```bash +uv run python scripts/train_confidence_scorer.py \ + --task query_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --output-dir runs/confidence/local/training/query_answerability \ + --export-path runs/confidence/local/artifacts/query_answerability.joblib \ + --encoder-name bert-base-uncased \ + --max-length 256 +``` + +```bash +uv run python scripts/train_confidence_scorer.py \ + --task query_context_answerability_confidence \ + --dataset runs/confidence/local/canonical/query_context_answerability_confidence.jsonl \ + --output-dir runs/confidence/local/training/query_context_answerability \ + --export-path runs/confidence/local/artifacts/query_context_answerability.joblib \ + --encoder-name bert-base-uncased \ + --max-length 256 +``` + +### 3.6 Dataset Balance / Generalization Report +범용성을 주장하려면 단순 train/test metric만으로 부족하다. +source별, group별, held-out source별 리포트가 필요하다. + +초기 구현은 새 학습 알고리즘을 만들지 않고 report utility만 추가한다. + +통합 지점: + +```text +src/ranksmith/confidence_training/_dataset_report.py +scripts/report_confidence_dataset.py +tests/test_confidence_training_dataset_report.py +``` + +리포트 항목: +- total sample count +- positive / negative count +- positive rate +- source별 count / positive rate +- group_id count +- duplicate id 여부 +- missing source 비율 +- missing group_id 비율 + +held-out generalization metric은 학습 pipeline 내부의 split 방식만으로는 충분하지 않다. +이번 범위에서는 다음을 명시한다. + +```text +generalization claim = source-balanced dataset report + source-heldout manual run evidence +``` + +즉, CLI는 source별 분포를 보여주고, benchmark claim은 별도 실행 artifact가 있을 때만 문서화한다. + +### 3.7 CBDR 연결 +기존 `compare_reranking.py --algorithm cbdr`를 그대로 사용한다. +이번 작업은 CBDR algorithm을 변경하지 않는다. + +예시: + +```bash +uv run python scripts/compare_reranking.py \ + --dataset fixture \ + --algorithm cbdr \ + --cbdr-base-artifact runs/confidence/local/artifacts/query_answerability.joblib \ + --cbdr-context-artifact runs/confidence/local/artifacts/query_context_answerability.joblib \ + --allow-live +``` + +## 4. 재사용 및 모듈화 (Reusability & Modularization) + +### 4.1 재사용 컴포넌트 +- `ModelProvider` +- `ModelRequest` +- `ModelResponse` +- `confidence_generation` prompt / parser / labeling +- `confidence_training` dataset loader / feature extraction / trainer / artifact export +- `CBDRStrategy.from_artifacts(...)` + +### 4.2 새 모듈 책임 +- `LMStudioModelProvider` + - LM Studio OpenAI-compatible API 호출만 담당한다. + - answerability prompt 의미를 알지 않는다. + - ranksmith의 `json_object` 요청을 LM Studio의 `json_schema` 요청으로 변환한다. +- `generate_confidence_dataset.py` + - provider 조립과 generation config 생성을 담당한다. + - label 계산은 기존 generation pipeline에 위임한다. +- `train_confidence_scorer.py` + - CLI arg를 `ConfidenceTrainingConfig`로 변환한다. + - 학습 본체는 기존 training pipeline에 위임한다. +- `dataset_report` + - dataset 품질/분포 리포트만 담당한다. + - scorer 학습이나 ranking correction을 하지 않는다. +- `build_qa_confidence_raw_dataset.py` + - QA 원천 dataset을 ranksmith raw generation schema로 변환한다. + - retrieved negative context 생성까지만 담당한다. + - LM Studio 호출, canonical label 생성, scorer 학습은 하지 않는다. + +## 5. 에러 핸들링 (Error Handling) + +### 5.1 LM Studio Provider +- LM Studio 서버 연결 실패: `RerankProviderError` +- HTTP non-2xx: `RerankProviderError` +- 응답 JSON 파싱 실패: `RerankProviderError` +- `choices[0].message.content` 누락: `RerankProviderError` +- 빈 content: `RerankProviderError` +- `model` 누락: `RerankInputError` + +### 5.2 Generation CLI +- 지원하지 않는 task: CLI parser error +- provider가 `lmstudio`가 아님: CLI parser error +- input schema 오류: 기존 `ConfidenceGenerationInputError` +- output 존재 + `--overwrite/--resume` 없음: 기존 fast fail +- malformed model output: 기존 `ConfidenceGenerationParseError` + +### 5.2.1 QA Raw Dataset Builder +- HuggingFace `datasets` 미설치: 명시적 실행 방법과 함께 fast-fail +- usable evidence 부족: 요청 row 수를 채울 수 없으면 fast-fail +- context 길이 초과: 숨은 truncation 없이 제외 +- retrieved negative 부족: 요청 row 수를 채울 수 없으면 fast-fail + +### 5.3 Training CLI +- unsupported task: 기존 `ConfidenceTrainingConfigError` +- label이 한쪽 클래스만 존재: 기존 training error +- HF model load 실패: 기존 training error 또는 원 예외를 wrapped error로 유지 +- export path 쓰기 실패: fast fail + +### 5.4 Dataset Report +- canonical JSONL schema 오류: 기존 dataset loader error +- source/group 누락은 실패가 아니라 warning count로 기록한다. +- duplicate id는 실패로 처리한다. + +## 6. 테스트 계획 (Test Plan) + +### 6.1 Unit Tests +- `LMStudioModelProvider` + - 정상 OpenAI-compatible response를 `ModelResponse`로 변환한다. + - `LMSTUDIO_MODEL` env fallback을 사용한다. + - `json_object` 요청을 `json_schema`로 변환한다. + - `max_tokens` 기본값 `128`을 전달한다. + - model 누락 시 `RerankInputError`. + - HTTP error / malformed response / empty content는 `RerankProviderError`. +- `generate_confidence_dataset.py` + - query-only task가 올바른 generation function을 호출한다. + - query+context task가 올바른 generation function을 호출한다. + - LM Studio provider 옵션이 전달된다. + - `--resume`, `--overwrite`, `--max-items`, `--max-context-chars`가 config에 반영된다. +- `train_confidence_scorer.py` + - CLI args가 `ConfidenceTrainingConfig`로 변환된다. + - HF options가 전달된다. + - task mismatch / ratio 오류는 fast fail. +- dataset report + - source별 count와 positive rate를 계산한다. + - duplicate id를 실패 처리한다. + - missing source/group count를 기록한다. +- QA raw dataset builder + - TriviaQA-style local JSONL fixture에서 query raw와 query-context raw를 생성한다. + - query-context row는 positive/negative 균형을 유지한다. + - negative row metadata에 `negative_retrieved_tfidf`를 기록한다. + +### 6.2 Smoke Tests +- fake LM Studio provider로 query-only canonical JSONL 생성. +- fake LM Studio provider로 query+context canonical JSONL 생성. +- small synthetic canonical dataset으로 training CLI가 artifact를 생성하는지 확인. +- 생성된 artifact가 `CBDRStrategy.from_artifacts(...)`에서 task type 검증을 통과하는지 확인. + +### 6.3 Live Verification +LM Studio 서버가 떠 있을 때만 수동 실행한다. +CI 기본 경로에는 포함하지 않는다. + +```bash +uv run python scripts/generate_confidence_dataset.py \ + --task query_answerability_confidence \ + --provider lmstudio \ + --lmstudio-base-url http://localhost:1234/v1 \ + --lmstudio-model google/gemma-4-12b \ + --input tests/fixtures/confidence_query_answerability_raw.jsonl \ + --output /tmp/ranksmith-lmstudio-query-answerability.jsonl \ + --overwrite \ + --max-items 3 +``` + +검증 기준: +- 응답이 strict JSON이다. +- `answer`가 비어 있지 않다. +- canonical row의 `label`이 0 또는 1이다. +- positive/negative count가 리포트된다. +- LM Studio usage에서 reasoning tokens가 `0`이거나 낮게 유지된다. + +### 6.4 최종 검증 +```bash +uv run pytest tests/test_lmstudio_provider.py tests/test_generate_confidence_dataset_script.py tests/test_train_confidence_scorer_script.py tests/test_confidence_training_dataset_report.py -q +./scripts/verify.sh +``` + +## 7. 작업 태스크 추적 (Task Checklist) + +### Phase 1: 컨텍스트 및 설계 확인 +- [x] 관련 기존 코드베이스 및 Wiki 문서 확인 +- [x] LM Studio / Gemma 4 12B 운영 조건 확인 +- [x] 스펙 문서(본 문서) 사용자 검토 및 확정 + +### Phase 2: 로직 구현 (Implementation) +- [x] `src/ranksmith/integrations/_lmstudio_provider.py`: LM Studio OpenAI-compatible `ModelProvider` 구현 +- [x] `src/ranksmith/integrations/__init__.py`: `LMStudioModelProvider` submodule export 추가 +- [x] `scripts/generate_confidence_dataset.py`: CBDR용 answerability generation CLI 추가 +- [x] `scripts/train_confidence_scorer.py`: confidence training CLI 추가 +- [x] `src/ranksmith/confidence_training/_dataset_report.py`: dataset balance/generalization helper 추가 +- [x] `scripts/report_confidence_dataset.py`: dataset report CLI 추가 +- [x] `scripts/build_qa_confidence_raw_dataset.py`: TriviaQA 기반 raw dataset builder 추가 +- [x] `scripts/run_lmstudio_confidence_pipeline.py`: raw 생성 → LM Studio generation → training → optional benchmark 통합 runner 추가 + +### Phase 3: 검증 (Verification) +- [x] `tests/test_lmstudio_provider.py`: provider 정상/실패 케이스 테스트 추가 +- [x] `tests/test_generate_confidence_dataset_script.py`: generation CLI 테스트 추가 +- [x] `tests/test_train_confidence_scorer_script.py`: training CLI 테스트 추가 +- [x] `tests/test_confidence_training_dataset_report.py`: dataset report 테스트 추가 +- [x] fake provider 기반 canonical generation smoke 실행 +- [x] synthetic artifact 기반 CBDR loading smoke 실행 +- [x] LM Studio live smoke는 수동 opt-in으로 실행 +- [x] TriviaQA-style local fixture 기반 raw builder smoke 실행 +- [x] `./scripts/verify.sh` 스크립트를 통한 린트/타입/전체 테스트 통과 확인 + +### Phase 4: 완료 및 정리 +- [x] `README.md`: LM Studio confidence pipeline 최소 예시 추가 +- [x] `README.ko.md`: 동일 구조의 한국어 예시 추가 +- [x] `docs/wiki/02_architecture.md`: LM Studio integration과 confidence pipeline CLI 위치 반영 +- [x] 본 문서 최상단의 **상태**를 `Completed`로 변경 diff --git a/docs/superpowers/plans/2026-06-04-cbdr-strategy.md b/docs/superpowers/plans/2026-06-04-cbdr-strategy.md new file mode 100644 index 0000000..3db8507 --- /dev/null +++ b/docs/superpowers/plans/2026-06-04-cbdr-strategy.md @@ -0,0 +1,1307 @@ +# CBDR Strategy Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a sync `CBDRStrategy` that skips context reranking when `Conf(Q) >= skip_threshold`, otherwise reranks by `Conf(Q+C)-Conf(Q)`. + +**Architecture:** Reuse the existing query-only and query+context answerability confidence contracts. Keep CBDR as a separate Strategy, not a subclass of `ConfidenceGainStrategy`, while sharing private helper functions for answer calls, task validation, score validation, and confidence gain scoring. Export only through `ranksmith.strategies`, and register it as a built-in sync strategy in `AzureOpenAIReranker`. + +**Tech Stack:** Python 3.10+, dataclasses, pytest, ranksmith confidence runtime, existing `AzureOpenAIReranker` facade. + +--- + +## File Structure + +- Modify: `src/ranksmith/strategies/_confidence_gain.py` + - Expose private helper functions for task validation, answer generation calls, base scoring, context scoring, and confidence score validation. + - Keep public exports unchanged. +- Create: `src/ranksmith/strategies/_cbdr.py` + - Implement sync `CBDRStrategy`. + - Use shared helpers from `_confidence_gain.py`. +- Modify: `src/ranksmith/strategies/__init__.py` + - Export `CBDRStrategy` from the strategies submodule only. +- Modify: `src/ranksmith/azure.py` + - Import `CBDRStrategy`. + - Treat `CBDRStrategy` as built-in sync strategy for facade exception handling. +- Create: `tests/test_cbdr_strategy.py` + - Cover skip path, rerank path, no-call paths, invalid inputs, facade behavior, and artifact-load E2E smoke for both paths. +- Modify: `docs/wiki/02_architecture.md` + - Add CBDR strategy and algorithm placement. +- Modify: `README.md` + - Add benchmark-free CBDR usage docs. +- Modify: `README.ko.md` + - Mirror README structure and content in Korean. +- Modify: `docs/specs/spec_cbdr_strategy.md` + - Mark implementation checklist items completed only after exit code 0 with no test failures. + +--- + +### Task 1: Shared Confidence Gain Helpers + +**Files:** +- Modify: `src/ranksmith/strategies/_confidence_gain.py` +- Test: `tests/test_confidence_gain_strategy.py` + +- [ ] **Step 1: Run existing confidence gain tests before changing helpers** + +Run: + +```bash +uv run pytest tests/test_confidence_gain_strategy.py -q +``` + +Expected: + +```text +39 passed +``` + +- [ ] **Step 2: Refactor helper functions without behavior change** + +Modify `src/ranksmith/strategies/_confidence_gain.py` so shared private helpers have these signatures and behavior: + +```python +def _validate_estimator_tasks( + *, + base_estimator: ConfidenceEstimator, + context_estimator: ConfidenceEstimator, +) -> None: + if base_estimator.task_type != QUERY_ANSWERABILITY_TASK: + raise RerankInputError( + f"base_estimator task_type must be {QUERY_ANSWERABILITY_TASK!r}" + ) + if context_estimator.task_type != QUERY_CONTEXT_ANSWERABILITY_TASK: + raise RerankInputError( + "context_estimator task_type must be " + f"{QUERY_CONTEXT_ANSWERABILITY_TASK!r}" + ) + + +def _score_base_answerability( + *, + estimator: ConfidenceEstimator, + query: str, + answer: str, +) -> StructuralConfidenceResult: + return estimator.score( + QueryAnswerabilityConfidenceInput(query=query, answer=answer) + ) + + +def _score_context_answerability( + *, + estimator: ConfidenceEstimator, + query: str, + context: str, + answer: str, +) -> StructuralConfidenceResult: + return estimator.score( + QueryContextAnswerabilityConfidenceInput( + query=query, + context=context, + answer=answer, + ) + ) + + +def _confidence_gain( + *, + base_score: float, + context_score: float, +) -> float: + gain = context_score - base_score + if not math.isfinite(gain) or gain < -1.0 or gain > 1.0: + raise RerankStrategyError("confidence gain must be finite in [-1, 1]") + return gain +``` + +Keep `_call_answer_query`, `_call_answer_with_context`, `_validate_answer`, and `_validate_confidence_score` private and reusable from `_cbdr.py`. + +Update `ConfidenceGainStrategy.__post_init__()` to call `_validate_estimator_tasks(...)`. + +Update `ConfidenceGainStrategy.rerank()` to call `_score_base_answerability(...)`, `_score_context_answerability(...)`, and `_confidence_gain(...)`. + +- [ ] **Step 3: Run confidence gain regression tests** + +Run: + +```bash +uv run pytest tests/test_confidence_gain_strategy.py -q +``` + +Expected: + +```text +39 passed +``` + +- [ ] **Step 4: Commit helper refactor** + +Run: + +```bash +git add src/ranksmith/strategies/_confidence_gain.py tests/test_confidence_gain_strategy.py +git commit -m "refactor: share confidence gain helpers" +``` + +Expected: + +```text +commit succeeds +``` + +--- + +### Task 2: CBDR Unit Tests and Strategy + +**Files:** +- Create: `src/ranksmith/strategies/_cbdr.py` +- Modify: `src/ranksmith/strategies/__init__.py` +- Modify: `src/ranksmith/azure.py` +- Create: `tests/test_cbdr_strategy.py` + +- [ ] **Step 1: Write failing CBDR tests** + +Create `tests/test_cbdr_strategy.py` with this structure: + +```python +from __future__ import annotations + +import math +from dataclasses import dataclass +from typing import Any, cast + +import pytest + +from ranksmith import AzureOpenAIReranker +from ranksmith.confidence import StructuralConfidenceResult, TaskType +from ranksmith.errors import ( + DocumentTooLongError, + RerankInputError, + RerankProviderError, + RerankStrategyError, +) +from ranksmith.strategies import CBDRStrategy +from ranksmith.types import Document + + +@dataclass +class FakeEstimator: + task_type: TaskType + scores: list[Any] + calls: list[object] | None = None + + def score(self, item: object) -> StructuralConfidenceResult: + if self.calls is not None: + self.calls.append(item) + value = self.scores.pop(0) + if isinstance(value, BaseException): + raise value + return StructuralConfidenceResult( + score=value, + task_type=self.task_type, + feature_schema_version="structural-v1", + ) + + +class FakeGenerator: + def __init__( + self, + *, + base_answer: object = "base answer", + context_answers: list[object] | None = None, + ) -> None: + self.base_answer = base_answer + self.context_answers = context_answers or [] + self.query_calls: list[str] = [] + self.context_calls: list[tuple[str, str]] = [] + + def answer_query(self, query: str) -> Any: + self.query_calls.append(query) + if isinstance(self.base_answer, BaseException): + raise self.base_answer + return self.base_answer + + def answer_with_context(self, query: str, context: str) -> Any: + self.context_calls.append((query, context)) + answer = self.context_answers.pop(0) + if isinstance(answer, BaseException): + raise answer + return answer + + +def _strategy( + *, + base_scores: list[Any] | None = None, + context_scores: list[Any] | None = None, + generator: FakeGenerator | None = None, + skip_threshold: float = 0.8, + max_document_chars: int = 4000, +) -> CBDRStrategy: + return CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=base_scores or [0.2], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=context_scores or [0.7], + ), + answer_generator=generator or FakeGenerator(context_answers=["context answer"]), + skip_threshold=skip_threshold, + max_document_chars=max_document_chars, + ) + + +def _unused_model_client() -> Any: + return object() +``` + +Add these tests in the same file: + +```python +def test_cbdr_exports_are_submodule_only() -> None: + import importlib + + strategies = importlib.import_module("ranksmith.strategies") + root = importlib.import_module("ranksmith") + + assert strategies.CBDRStrategy is not None + assert not hasattr(root, "CBDRStrategy") + + +def test_cbdr_empty_documents_returns_empty_without_calls() -> None: + generator = FakeGenerator(context_answers=[]) + strategy = _strategy(generator=generator) + + assert strategy.rerank( + query="Who?", + documents=[], + model_client=object(), + ) == [] + assert generator.query_calls == [] + assert generator.context_calls == [] + + +def test_cbdr_top_k_zero_returns_empty_without_calls() -> None: + generator = FakeGenerator(context_answers=["a"]) + strategy = _strategy(generator=generator) + + assert strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + top_k=0, + ) == [] + assert generator.query_calls == [] + assert generator.context_calls == [] + + +def test_cbdr_skip_path_preserves_original_order_and_metadata() -> None: + generator = FakeGenerator(context_answers=[]) + strategy = _strategy(base_scores=[0.91], generator=generator, skip_threshold=0.8) + documents = [ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + ] + + results = strategy.rerank(query="Who?", documents=documents, model_client=object()) + + assert [result.document.id for result in results] == ["a", "b"] + assert [result.rank for result in results] == [1, 2] + assert [result.original_index for result in results] == [0, 1] + assert [dict(result.metadata) for result in results] == [ + { + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": True, + "base_confidence": 0.91, + "skip_threshold": 0.8, + "context_confidence": None, + "confidence_gain": None, + }, + { + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": True, + "base_confidence": 0.91, + "skip_threshold": 0.8, + "context_confidence": None, + "confidence_gain": None, + }, + ] + assert generator.query_calls == ["Who?"] + assert generator.context_calls == [] + + +def test_cbdr_skip_path_applies_top_k_after_original_order() -> None: + strategy = _strategy(base_scores=[0.9], skip_threshold=0.8) + + results = strategy.rerank( + query="Who?", + documents=[ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + ], + model_client=object(), + top_k=1, + ) + + assert [result.document.id for result in results] == ["a"] + assert [result.rank for result in results] == [1] + assert [result.original_index for result in results] == [0] + + +def test_cbdr_skip_path_does_not_validate_long_documents() -> None: + strategy = _strategy( + base_scores=[0.9], + context_scores=[], + generator=FakeGenerator(context_answers=[]), + skip_threshold=0.8, + max_document_chars=3, + ) + + results = strategy.rerank( + query="Who?", + documents=[Document(text="abcdef")], + model_client=object(), + ) + + assert len(results) == 1 + assert results[0].metadata["cbdr_skipped"] is True + + +def test_cbdr_rerank_path_sorts_by_gain_and_preserves_ties() -> None: + generator = FakeGenerator(context_answers=["answer a", "answer b", "answer c"]) + strategy = _strategy( + base_scores=[0.4], + context_scores=[0.6, 0.8, 0.8], + generator=generator, + skip_threshold=0.9, + ) + + results = strategy.rerank( + query="Who?", + documents=[ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + Document(id="c", text="gamma"), + ], + model_client=object(), + top_k=2, + ) + + assert [result.document.id for result in results] == ["b", "c"] + assert [result.rank for result in results] == [1, 2] + assert [result.original_index for result in results] == [1, 2] + assert [result.metadata["cbdr_skipped"] for result in results] == [False, False] + assert [result.metadata["base_confidence"] for result in results] == [0.4, 0.4] + assert [result.metadata["context_confidence"] for result in results] == [0.8, 0.8] + assert [result.metadata["confidence_gain"] for result in results] == pytest.approx( + [0.4, 0.4] + ) + assert generator.context_calls == [ + ("Who?", "alpha"), + ("Who?", "beta"), + ("Who?", "gamma"), + ] + + +@pytest.mark.parametrize("skip_threshold", [math.nan, math.inf, -0.1, 1.1, True]) +def test_cbdr_invalid_skip_threshold_fails(skip_threshold: object) -> None: + with pytest.raises(ValueError, match="skip_threshold"): + CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + skip_threshold=cast(float, skip_threshold), + ) + + +def test_cbdr_threshold_zero_always_skips_non_empty_documents() -> None: + strategy = _strategy(base_scores=[0.0], skip_threshold=0.0) + + results = strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert results[0].metadata["cbdr_skipped"] is True + + +def test_cbdr_threshold_one_skips_only_at_exact_one() -> None: + skipped = _strategy(base_scores=[1.0], skip_threshold=1.0).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + reranked = _strategy( + base_scores=[0.999], + context_scores=[1.0], + generator=FakeGenerator(context_answers=["a"]), + skip_threshold=1.0, + ).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert skipped[0].metadata["cbdr_skipped"] is True + assert reranked[0].metadata["cbdr_skipped"] is False + + +def test_cbdr_rerank_path_validates_long_documents() -> None: + strategy = _strategy(base_scores=[0.2], skip_threshold=0.8, max_document_chars=3) + + with pytest.raises(DocumentTooLongError): + strategy.rerank( + query="Who?", + documents=[Document(text="abcdef")], + model_client=object(), + ) + + +def test_cbdr_empty_query_fails() -> None: + with pytest.raises(RerankInputError, match="query"): + _strategy().rerank( + query=" ", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_negative_top_k_fails() -> None: + with pytest.raises(RerankInputError, match="top_k"): + _strategy().rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + top_k=-1, + ) + + +def test_cbdr_invalid_task_types_fail() -> None: + with pytest.raises(RerankInputError, match="base_estimator"): + CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + ) + + with pytest.raises(RerankInputError, match="context_estimator"): + CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + ) + + +@pytest.mark.parametrize("score", [math.nan, math.inf, -0.1, 1.1, "0.5", True]) +def test_cbdr_invalid_base_score_fails(score: object) -> None: + with pytest.raises(RerankStrategyError): + _strategy(base_scores=[score]).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +@pytest.mark.parametrize("score", [math.nan, math.inf, -0.1, 1.1, "0.5", True]) +def test_cbdr_invalid_context_score_fails(score: object) -> None: + with pytest.raises(RerankStrategyError): + _strategy( + base_scores=[0.1], + context_scores=[score], + skip_threshold=0.8, + ).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_answer_generator_empty_output_fails() -> None: + with pytest.raises(RerankProviderError): + _strategy(generator=FakeGenerator(base_answer="")).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_generator_unexpected_exception_wraps_provider_error() -> None: + with pytest.raises(RerankProviderError) as exc_info: + _strategy(generator=FakeGenerator(base_answer=RuntimeError("boom"))).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert isinstance(exc_info.value.__cause__, RuntimeError) + + +def test_cbdr_direct_estimator_unexpected_exception_propagates() -> None: + error = RuntimeError("confidence failed") + + with pytest.raises(RuntimeError, match="confidence failed"): + _strategy(base_scores=[error]).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_facade_wraps_unexpected_estimator_error() -> None: + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=_strategy(base_scores=[RuntimeError("confidence failed")]), + ) + + with pytest.raises(RerankProviderError) as exc_info: + reranker.rerank("Who?", [Document(text="alpha")]) + + assert isinstance(exc_info.value.__cause__, RuntimeError) +``` + +- [ ] **Step 2: Run tests and verify import failure** + +Run: + +```bash +uv run pytest tests/test_cbdr_strategy.py -q +``` + +Expected: + +```text +ImportError: cannot import name 'CBDRStrategy' +``` + +- [ ] **Step 3: Implement `CBDRStrategy`** + +Create `src/ranksmith/strategies/_cbdr.py`: + +```python +from __future__ import annotations + +import math +from collections.abc import Sequence +from dataclasses import dataclass +from numbers import Real +from typing import Literal + +from ranksmith.errors import RerankInputError, RerankStrategyError +from ranksmith.types import Document, RerankResult + +from ._common import validate_documents_max_chars, validate_top_k +from ._confidence_gain import ( + AnswerGenerator, + ConfidenceEstimator, + _call_answer_query, + _call_answer_with_context, + _confidence_gain, + _score_base_answerability, + _score_context_answerability, + _validate_confidence_score, + _validate_estimator_tasks, +) + +CBDRAlgorithm = Literal["cbdr"] + + +@dataclass(frozen=True) +class CBDRStrategy: + base_estimator: ConfidenceEstimator + context_estimator: ConfidenceEstimator + answer_generator: AnswerGenerator + skip_threshold: float = 0.8 + max_document_chars: int = 4000 + algorithm: CBDRAlgorithm = "cbdr" + + def __post_init__(self) -> None: + if self.algorithm != "cbdr": + raise ValueError('algorithm must be "cbdr"') + if self.max_document_chars < 1: + raise ValueError("max_document_chars must be greater than 0") + _validate_probability_config(self.skip_threshold, "skip_threshold") + _validate_estimator_tasks( + base_estimator=self.base_estimator, + context_estimator=self.context_estimator, + ) + + def rerank( + self, + *, + query: str, + documents: Sequence[Document], + model_client: object, + top_k: int | None = None, + ) -> list[RerankResult]: + del model_client + validate_top_k(top_k) + if query.strip() == "": + raise RerankInputError("query must not be empty") + if not documents or top_k == 0: + return [] + + base_answer = _call_answer_query(self.answer_generator, query) + base_result = _score_base_answerability( + estimator=self.base_estimator, + query=query, + answer=base_answer, + ) + base_score = _validate_confidence_score(base_result.score, "base") + + if base_score >= self.skip_threshold: + return _original_order_results( + documents=documents, + top_k=top_k, + algorithm=self.algorithm, + base_score=base_score, + skip_threshold=self.skip_threshold, + ) + + validate_documents_max_chars( + documents, + max_document_chars=self.max_document_chars, + ) + scored = [] + for original_index, document in enumerate(documents): + context_answer = _call_answer_with_context( + self.answer_generator, + query, + document.text, + ) + context_result = _score_context_answerability( + estimator=self.context_estimator, + query=query, + context=document.text, + answer=context_answer, + ) + context_score = _validate_confidence_score( + context_result.score, + "context", + ) + scored.append( + ( + original_index, + context_score, + _confidence_gain( + base_score=base_score, + context_score=context_score, + ), + ) + ) + + scored.sort(key=lambda item: (-item[2], item[0])) + if top_k is not None: + scored = scored[:top_k] + + return [ + RerankResult( + document=documents[original_index], + rank=rank, + original_index=original_index, + metadata={ + "strategy": "cbdr", + "algorithm": self.algorithm, + "cbdr_skipped": False, + "base_confidence": base_score, + "skip_threshold": self.skip_threshold, + "context_confidence": context_score, + "confidence_gain": gain, + }, + ) + for rank, (original_index, context_score, gain) in enumerate( + scored, + start=1, + ) + ] + + +def _validate_probability_config(value: object, name: str) -> float: + if isinstance(value, bool) or not isinstance(value, Real): + raise ValueError(f"{name} must be a finite probability in [0, 1]") + probability = float(value) + if not math.isfinite(probability) or probability < 0.0 or probability > 1.0: + raise ValueError(f"{name} must be a finite probability in [0, 1]") + return probability + + +def _original_order_results( + *, + documents: Sequence[Document], + top_k: int | None, + algorithm: CBDRAlgorithm, + base_score: float, + skip_threshold: float, +) -> list[RerankResult]: + indexed = list(enumerate(documents)) + if top_k is not None: + indexed = indexed[:top_k] + return [ + RerankResult( + document=document, + rank=rank, + original_index=original_index, + metadata={ + "strategy": "cbdr", + "algorithm": algorithm, + "cbdr_skipped": True, + "base_confidence": base_score, + "skip_threshold": skip_threshold, + "context_confidence": None, + "confidence_gain": None, + }, + ) + for rank, (original_index, document) in enumerate(indexed, start=1) + ] +``` + +- [ ] **Step 4: Export CBDR strategy and register facade behavior** + +Modify `src/ranksmith/strategies/__init__.py`: + +```python +from ranksmith.strategies._cbdr import CBDRStrategy +``` + +Add `"CBDRStrategy"` to `__all__`. + +Modify `src/ranksmith/azure.py`: + +```python +from ranksmith.strategies import ( + AcuRankStrategy, + AsyncAcuRankStrategy, + AsyncListwiseStrategy, + AsyncPairwiseStrategy, + AsyncSetwiseStrategy, + AsyncTourRankStrategy, + CBDRStrategy, + ConfidenceGainStrategy, + ListwiseStrategy, + PairwiseStrategy, + SetwiseStrategy, + TourRankStrategy, +) +``` + +Add `CBDRStrategy` to `_is_builtin_sync_strategy(...)`: + +```python +def _is_builtin_sync_strategy(strategy: object) -> bool: + return type(strategy) in { + AcuRankStrategy, + CBDRStrategy, + ConfidenceGainStrategy, + ListwiseStrategy, + PairwiseStrategy, + SetwiseStrategy, + TourRankStrategy, + } +``` + +- [ ] **Step 5: Run CBDR and confidence gain tests** + +Run: + +```bash +uv run pytest tests/test_cbdr_strategy.py tests/test_confidence_gain_strategy.py -q +``` + +Expected: + +```text +exit code 0 with no test failures +``` + +- [ ] **Step 6: Commit CBDR strategy** + +Run: + +```bash +git add src/ranksmith/strategies/_confidence_gain.py src/ranksmith/strategies/_cbdr.py src/ranksmith/strategies/__init__.py src/ranksmith/azure.py tests/test_cbdr_strategy.py +git commit -m "feat: add cbdr strategy" +``` + +Expected: + +```text +commit succeeds +``` + +--- + +### Task 3: Artifact-Load E2E Smoke Tests + +**Files:** +- Modify: `tests/test_cbdr_strategy.py` + +- [ ] **Step 1: Add artifact smoke helpers** + +Append these imports near the top of `tests/test_cbdr_strategy.py`: + +```python +import sys +from pathlib import Path +from types import ModuleType + +from ranksmith.confidence import StructuralConfidenceEstimator +from ranksmith.confidence._scorer import ARTIFACT_SCHEMA_VERSION +``` + +Add these helper classes and functions: + +```python +class ArtifactScorer: + def __init__(self, scores: list[float]) -> None: + self.scores = scores + + def predict_confidence(self, features: object) -> float: + del features + return self.scores.pop(0) + + +class ArtifactEncoder: + encoder_name = "bert-base-uncased" + encoder_revision = None + tokenizer_name = "bert-base-uncased" + tokenizer_revision = None + + def __init__(self, *, max_length: int) -> None: + self.max_length = max_length + + def encode(self, text: str) -> tuple[list[list[float]], list[int]]: + seed = float(len(text) % 7 + 1) + hidden = [[seed + row * 0.01, row * 0.02, seed * 0.03] for row in range(40)] + return hidden, [1] * len(hidden) + + +def _artifact_metadata(task_type: TaskType) -> dict[str, object]: + return { + "artifact_schema_version": ARTIFACT_SCHEMA_VERSION, + "scorer_type": "joblib-wrapper", + "task_type": task_type, + "encoder_name": "bert-base-uncased", + "encoder_revision": None, + "tokenizer_name": "bert-base-uncased", + "tokenizer_revision": None, + "input_template_version": "structural-template-v1", + "feature_schema_version": "structural-v1", + "feature_dim": 70, + "feature_dtype": "float64", + "max_length": 64, + "granularity": "two_scale", + "local_window_size": 5, + "local_stride": 2, + "score_output": "probability", + "positive_class_index": 1, + } + + +def _install_artifact_joblib( + monkeypatch: pytest.MonkeyPatch, + artifacts: dict[Path, object], +) -> None: + module = ModuleType("joblib") + + def load(path: str | Path) -> object: + return artifacts[Path(path)] + + module.load = load # type: ignore[attr-defined] + monkeypatch.setitem(sys.modules, "joblib", module) + + +def _artifact_strategy( + *, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + base_scores: list[float], + context_scores: list[float], + generator: FakeGenerator, + skip_threshold: float, +) -> CBDRStrategy: + base_artifact_path = tmp_path / "query_answerability.joblib" + context_artifact_path = tmp_path / "query_context_answerability.joblib" + _install_artifact_joblib( + monkeypatch, + { + base_artifact_path: { + "metadata": _artifact_metadata("query_answerability_confidence"), + "scorer": ArtifactScorer(base_scores), + }, + context_artifact_path: { + "metadata": _artifact_metadata( + "query_context_answerability_confidence" + ), + "scorer": ArtifactScorer(context_scores), + }, + }, + ) + + def fake_from_pretrained(**kwargs: object) -> ArtifactEncoder: + return ArtifactEncoder(max_length=cast(int, kwargs["max_length"])) + + monkeypatch.setattr( + "ranksmith.confidence._structural.FrozenAutoEncoder.from_pretrained", + fake_from_pretrained, + ) + + return CBDRStrategy( + base_estimator=StructuralConfidenceEstimator.from_artifact(base_artifact_path), + context_estimator=StructuralConfidenceEstimator.from_artifact( + context_artifact_path + ), + answer_generator=generator, + skip_threshold=skip_threshold, + ) +``` + +- [ ] **Step 2: Add artifact-load skip path smoke** + +Append this test to `tests/test_cbdr_strategy.py`: + +```python +def test_cbdr_artifact_e2e_skip_path_through_azure_facade( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + strategy = _artifact_strategy( + monkeypatch=monkeypatch, + tmp_path=tmp_path, + base_scores=[0.91], + context_scores=[], + generator=FakeGenerator(context_answers=[]), + skip_threshold=0.8, + ) + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=strategy, + ) + + results = reranker.rerank( + "who played karen in married to the mob?", + [ + Document( + id="similar-but-weak", + text="Michelle Pfeiffer appears in the film.", + ), + Document(id="direct-evidence", text="Nancy Travis played Karen."), + ], + ) + + assert [result.document.id for result in results] == [ + "similar-but-weak", + "direct-evidence", + ] + assert [result.metadata["cbdr_skipped"] for result in results] == [True, True] + assert [result.metadata["base_confidence"] for result in results] == [0.91, 0.91] + assert [result.metadata["context_confidence"] for result in results] == [None, None] + assert [result.metadata["confidence_gain"] for result in results] == [None, None] +``` + +- [ ] **Step 3: Add artifact-load rerank path smoke** + +Append this test to `tests/test_cbdr_strategy.py`: + +```python +def test_cbdr_artifact_e2e_rerank_path_through_azure_facade( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + strategy = _artifact_strategy( + monkeypatch=monkeypatch, + tmp_path=tmp_path, + base_scores=[0.3], + context_scores=[0.45, 0.9], + generator=FakeGenerator( + base_answer="base answer", + context_answers=["low answer", "high answer"], + ), + skip_threshold=0.8, + ) + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=strategy, + ) + + results = reranker.rerank( + "who played karen in married to the mob?", + [ + Document( + id="similar-but-weak", + text="Michelle Pfeiffer appears in the film.", + ), + Document(id="direct-evidence", text="Nancy Travis played Karen."), + ], + ) + + assert [result.document.id for result in results] == [ + "direct-evidence", + "similar-but-weak", + ] + assert [result.metadata["cbdr_skipped"] for result in results] == [False, False] + assert [result.metadata["base_confidence"] for result in results] == [0.3, 0.3] + assert [result.metadata["context_confidence"] for result in results] == [0.9, 0.45] + assert [result.metadata["confidence_gain"] for result in results] == pytest.approx( + [0.6, 0.15] + ) +``` + +- [ ] **Step 4: Run CBDR tests** + +Run: + +```bash +uv run pytest tests/test_cbdr_strategy.py -q +``` + +Expected: + +```text +exit code 0 with no test failures +``` + +- [ ] **Step 5: Run targeted regression tests** + +Run: + +```bash +uv run pytest tests/test_cbdr_strategy.py tests/test_confidence_gain_strategy.py tests/test_ranksmith.py -q +``` + +Expected: + +```text +exit code 0 with no test failures +``` + +- [ ] **Step 6: Commit E2E smoke tests** + +Run: + +```bash +git add tests/test_cbdr_strategy.py +git commit -m "test: add cbdr artifact smoke tests" +``` + +Expected: + +```text +commit succeeds +``` + +--- + +### Task 4: Documentation, Spec Checklist, and Final Verification + +**Files:** +- Modify: `docs/wiki/02_architecture.md` +- Modify: `README.md` +- Modify: `README.ko.md` +- Modify: `docs/specs/spec_cbdr_strategy.md` + +- [ ] **Step 1: Update architecture wiki** + +Modify `docs/wiki/02_architecture.md`: + +Add `CBDRStrategy` to the v1 public strategy list after `ConfidenceGainStrategy`. + +Add `cbdr` to the v1 algorithm list after `confidence_gain`. + +Update the confidence section with: + +```markdown +`CBDRStrategy`는 `Conf(Q)`가 `skip_threshold` 이상이면 context reranking을 skip하고 original order를 보존한다. +`Conf(Q)`가 threshold보다 낮으면 `Conf(Q+C)-Conf(Q)` confidence gain으로 문서를 정렬한다. +true pre-retrieval skip, retriever integration, async CBDR은 구현하지 않는다. +``` + +- [ ] **Step 2: Update README method table and usage** + +Modify `README.md` method table by adding this row after `confidence_gain`: + +```markdown +| `cbdr` | `CBDRStrategy` | You have trained answerability confidence scorers and want to skip context reranking when `Conf(Q)` is already high, otherwise rerank by confidence gain. | Requires scorer artifacts and an answer generator hook. Skip path uses 1 answer generation call and 1 confidence score; rerank path uses `N+1` answer generations and `N+1` confidence scores. | +``` + +Add a short usage block near the existing Confidence Gain section: + +```markdown +`CBDRStrategy` is a sync reranking-side router. It does not integrate with a +retriever or stop upstream retrieval calls; it only skips context reranking once +documents have already been passed to `rerank(...)`. + +```python +from ranksmith import AzureOpenAIReranker +from ranksmith.strategies import CBDRStrategy + +strategy = CBDRStrategy( + base_estimator=query_estimator, + context_estimator=query_context_estimator, + answer_generator=answer_generator, + skip_threshold=0.8, +) + +reranker = AzureOpenAIReranker( + model_client=model_client, + strategy=strategy, +) + +results = reranker.rerank(query, documents) +``` + +When `Conf(Q) >= skip_threshold`, results preserve original document order and +include `metadata["cbdr_skipped"] == True`. When `Conf(Q) < skip_threshold`, all +documents are scored before `top_k` slicing. +``` + +Ensure Markdown fences are balanced. + +- [ ] **Step 3: Mirror README.ko** + +Modify `README.ko.md` with the same section structure and table position. + +Use this table row: + +```markdown +| `cbdr` | `CBDRStrategy` | answerability confidence scorer를 학습했고 `Conf(Q)`가 충분히 높으면 context reranking을 건너뛰고, 낮으면 confidence gain으로 정렬하고 싶을 때 | scorer artifact와 answer generator hook이 필요함. skip path는 answer generation 1회와 confidence scoring 1회, rerank path는 각각 `N+1`회를 수행함 | +``` + +Use this usage text: + +```markdown +`CBDRStrategy`는 sync reranking-side router입니다. retriever와 통합하거나 upstream +retrieval 호출 자체를 멈추지는 않습니다. 이미 `rerank(...)`에 documents가 전달된 +뒤 context reranking을 건너뛸지 결정합니다. + +```python +from ranksmith import AzureOpenAIReranker +from ranksmith.strategies import CBDRStrategy + +strategy = CBDRStrategy( + base_estimator=query_estimator, + context_estimator=query_context_estimator, + answer_generator=answer_generator, + skip_threshold=0.8, +) + +reranker = AzureOpenAIReranker( + model_client=model_client, + strategy=strategy, +) + +results = reranker.rerank(query, documents) +``` + +`Conf(Q) >= skip_threshold`이면 original document order를 보존하고 +`metadata["cbdr_skipped"] == True`를 남깁니다. `Conf(Q) < skip_threshold`이면 +모든 문서를 scoring한 뒤 `top_k`를 적용합니다. +``` + +Ensure README.md and README.ko.md keep the same section and table structure. + +- [ ] **Step 4: Update spec checklist** + +Modify `docs/specs/spec_cbdr_strategy.md`: + +Set status: + +```markdown +- **상태**: `[ ] Draft` | `[ ] In Progress` | `[x] Completed` +``` + +Mark all implementation, verification, and docs checklist items complete: + +```markdown +- [x] `src/ranksmith/strategies/_confidence_gain.py`: 공통 protocol/helper 정리 +- [x] `src/ranksmith/strategies/_cbdr.py`: `CBDRStrategy` 구현 +- [x] `src/ranksmith/strategies/__init__.py`: strategy export 추가 +- [x] `src/ranksmith/azure.py`: built-in sync strategy 처리 추가 +- [x] `tests/test_cbdr_strategy.py`: skip path 정상 케이스 추가 +- [x] `tests/test_cbdr_strategy.py`: rerank path 정상 케이스 추가 +- [x] `tests/test_cbdr_strategy.py`: 엣지/실패 케이스 추가 +- [x] `tests/test_cbdr_strategy.py`: Azure facade smoke 추가 +- [x] `tests/test_cbdr_strategy.py`: artifact load 기반 skip path E2E smoke 추가 +- [x] `tests/test_cbdr_strategy.py`: artifact load 기반 rerank path E2E smoke 추가 +- [x] `./scripts/verify.sh` 스크립트를 통한 린트/타입/전체 테스트 통과 확인 +- [x] `docs/wiki/02_architecture.md`: CBDR strategy 위치 추가 +- [x] `README.md` / `README.ko.md`: benchmark 없는 usage 문서 추가 +- [x] 본 문서 최상단의 **상태**를 `Completed`로 변경 +``` + +- [ ] **Step 5: Run final verification** + +Run: + +```bash +./scripts/verify.sh +``` + +Expected: + +```text +pytest passes +ruff passes +format check passes +mypy passes +sdist and wheel build +``` + +- [ ] **Step 6: Commit docs and final spec** + +Run: + +```bash +git add docs/wiki/02_architecture.md README.md README.ko.md docs/specs/spec_cbdr_strategy.md +git commit -m "docs: document cbdr strategy" +``` + +Expected: + +```text +commit succeeds +``` + +--- + +## Self-Review + +- Spec coverage: + - sync `CBDRStrategy`: Task 2. + - skip decision: Task 2 skip tests and implementation. + - low-confidence confidence gain reranking: Task 2 rerank tests and implementation. + - shared helper refactor: Task 1. + - `AzureOpenAIReranker` built-in behavior: Task 2 facade import and tests. + - submodule-only export: Task 2 export test. + - docs and README: Task 4. + - no upstream retrieval skip, no `should_retrieve()`, no async: preserved by not adding those APIs. + - no scorer training or artifact creation: preserved by using existing `from_artifact()` only in smoke tests. + - no benchmark numbers: Task 4 explicitly avoids benchmark metrics. +- Placeholder scan: + - No placeholder implementation steps remain. + - Every new test and code path has concrete snippets. +- Type consistency: + - `CBDRStrategy.rerank(...)` matches `RerankStrategy`. + - `AnswerGenerator` and `ConfidenceEstimator` reuse existing strategy contracts. + - Metadata keys match `docs/specs/spec_cbdr_strategy.md`. diff --git a/docs/superpowers/plans/2026-06-04-confidence-gain-reranking.md b/docs/superpowers/plans/2026-06-04-confidence-gain-reranking.md new file mode 100644 index 0000000..a16a6ed --- /dev/null +++ b/docs/superpowers/plans/2026-06-04-confidence-gain-reranking.md @@ -0,0 +1,1256 @@ +# Confidence Gain Reranking Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add `Inc(Q, C) = Conf(Q+C) - Conf(Q)` support by extending confidence tasks and adding a sync `ConfidenceGainStrategy`. + +**Architecture:** Reuse the existing `StructuralConfidenceEstimator.from_artifact()`, `score()`, and `score_batch()` runtime. Add two answerability task types, extend generation/training validation, then add a small Strategy that consumes two estimators plus an answer generator hook and sorts by confidence gain. + +**Tech Stack:** Python 3.10+, dataclasses, existing `ranksmith.confidence`, existing `ranksmith.confidence_generation`, existing `ranksmith.confidence_training`, existing Strategy protocol, pytest, ruff, mypy. + +--- + +## File Map + +- Modify `src/ranksmith/confidence/_types.py` + - Add `QueryAnswerabilityConfidenceInput` and `QueryContextAnswerabilityConfidenceInput`. + - Extend `TaskType` and `StructuralConfidenceInput`. +- Modify `src/ranksmith/confidence/_templates.py` + - Add templates for query-only and query+context answerability confidence. +- Modify `src/ranksmith/confidence/_scorer.py` + - Accept the new task types in scorer metadata. +- Modify `src/ranksmith/confidence/__init__.py` + - Export the new input types from the confidence submodule. +- Modify `src/ranksmith/confidence_training/_types.py` + - Extend training config and canonical sample fields. +- Modify `src/ranksmith/confidence_training/_dataset.py` + - Validate and parse the new canonical JSONL schemas. +- Modify `src/ranksmith/confidence_training/_features.py` + - Convert new canonical samples into new runtime input dataclasses. +- Modify `src/ranksmith/confidence_training/_artifact.py` + - Ensure metadata export accepts new task types through existing `ScorerMetadata`. +- Modify `src/ranksmith/confidence_generation/_types.py` + - Add configs and raw sample dataclasses for query-only and query+context answerability generation. +- Modify `src/ranksmith/confidence_generation/_io.py` + - Load the new raw JSONL schemas and validate resume output rows. +- Modify `src/ranksmith/confidence_generation/_prompts.py` + - Add prompts for base answer and context-conditioned answer. +- Modify `src/ranksmith/confidence_generation/_pipeline.py` + - Add `generate_query_answerability_confidence_dataset()` and `generate_query_context_answerability_confidence_dataset()`. +- Modify `src/ranksmith/confidence_generation/__init__.py` + - Export the new generation configs/functions. +- Create `src/ranksmith/strategies/_confidence_gain.py` + - Implement `AnswerGenerator`, `ConfidenceGainResult`, and `ConfidenceGainStrategy`. +- Modify `src/ranksmith/strategies/__init__.py` + - Export `ConfidenceGainStrategy`. +- Modify docs: + - `docs/wiki/02_architecture.md` + - `docs/wiki/04_references_index.md` + - `README.md` + - `README.ko.md` + - `docs/specs/spec_confidence_gain_reranking.md` +- Tests: + - `tests/test_confidence_answerability_tasks.py` + - `tests/test_confidence_training_answerability.py` + - `tests/test_confidence_generation_answerability.py` + - `tests/test_confidence_gain_strategy.py` + +--- + +### Task 1: Confidence Runtime Answerability Tasks + +**Files:** +- Modify: `src/ranksmith/confidence/_types.py` +- Modify: `src/ranksmith/confidence/_templates.py` +- Modify: `src/ranksmith/confidence/_scorer.py` +- Modify: `src/ranksmith/confidence/__init__.py` +- Test: `tests/test_confidence_answerability_tasks.py` + +- [ ] **Step 1: Write failing runtime task tests** + +Create `tests/test_confidence_answerability_tasks.py`: + +```python +from __future__ import annotations + +import importlib +from collections.abc import Sequence + +import pytest + +from ranksmith.confidence import ( + ConfidenceInputError, + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + ScorerMetadata, + StructuralConfidenceEstimator, +) + + +class FakeEncoder: + encoder_name = "bert-base-uncased" + encoder_revision = None + tokenizer_name = "bert-base-uncased" + tokenizer_revision = None + max_length = 256 + + def __init__(self) -> None: + self.texts: list[str] = [] + + def encode(self, text: str) -> tuple[list[list[float]], list[int]]: + self.texts.append(text) + return [[0.1, 0.2], [0.3, 0.4]], [1, 1] + + +class FakeScorer: + def __init__(self, task_type: str) -> None: + self.metadata = ScorerMetadata( + artifact_schema_version="structural-artifact-v1", + scorer_type="fake", + task_type=task_type, # type: ignore[arg-type] + encoder_name="bert-base-uncased", + encoder_revision=None, + tokenizer_name="bert-base-uncased", + tokenizer_revision=None, + input_template_version="structural-template-v1", + feature_schema_version="structural-v1", + feature_dim=70, + feature_dtype="float64", + max_length=256, + granularity="token", + local_window_size=5, + local_stride=2, + score_output="probability", + positive_class_index=1, + ) + + def predict_confidence(self, features: Sequence[float]) -> float: + return 0.75 + + +def test_confidence_submodule_exports_answerability_inputs() -> None: + confidence = importlib.import_module("ranksmith.confidence") + + assert hasattr(confidence, "QueryAnswerabilityConfidenceInput") + assert hasattr(confidence, "QueryContextAnswerabilityConfidenceInput") + + +def test_query_answerability_template_is_scored(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + "ranksmith.confidence._structural.extract_structural_features", + lambda hidden, mask, *, max_length: [0.0] * 70, + ) + encoder = FakeEncoder() + estimator = StructuralConfidenceEstimator( + encoder=encoder, + scorer=FakeScorer("query_answerability_confidence"), + task_type="query_answerability_confidence", + ) + + result = estimator.score( + QueryAnswerabilityConfidenceInput(query="Who?", answer="Nancy Travis") + ) + + assert result.score == 0.75 + assert encoder.texts == ["Query:\nWho?\n\nAnswer:\nNancy Travis"] + + +def test_query_context_answerability_template_is_scored( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + "ranksmith.confidence._structural.extract_structural_features", + lambda hidden, mask, *, max_length: [0.0] * 70, + ) + encoder = FakeEncoder() + estimator = StructuralConfidenceEstimator( + encoder=encoder, + scorer=FakeScorer("query_context_answerability_confidence"), + task_type="query_context_answerability_confidence", + ) + + result = estimator.score( + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="Karen was played by Nancy Travis.", + answer="Nancy Travis", + ) + ) + + assert result.score == 0.75 + assert encoder.texts == [ + "Query:\nWho?\n\nContext:\nKaren was played by Nancy Travis.\n\nAnswer:\nNancy Travis" + ] + + +def test_answerability_task_rejects_wrong_input_type() -> None: + estimator = StructuralConfidenceEstimator( + encoder=FakeEncoder(), + scorer=FakeScorer("query_answerability_confidence"), + task_type="query_answerability_confidence", + ) + + with pytest.raises(ConfidenceInputError): + estimator.score( + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="Context", + answer="Answer", + ) + ) +``` + +- [ ] **Step 2: Run runtime task tests and verify failure** + +Run: + +```bash +uv run pytest tests/test_confidence_answerability_tasks.py -q +``` + +Expected: FAIL because `QueryAnswerabilityConfidenceInput` is not exported. + +- [ ] **Step 3: Implement runtime task types and templates** + +Modify `src/ranksmith/confidence/_types.py`: + +```python +TaskType: TypeAlias = Literal[ + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", +] + + +@dataclass(frozen=True) +class QueryAnswerabilityConfidenceInput: + query: str + answer: str + + +@dataclass(frozen=True) +class QueryContextAnswerabilityConfidenceInput: + query: str + context: str + answer: str + + +StructuralConfidenceInput: TypeAlias = ( + AnswerConfidenceInput + | JudgmentConfidenceInput + | QueryAnswerabilityConfidenceInput + | QueryContextAnswerabilityConfidenceInput +) +``` + +Modify `src/ranksmith/confidence/_templates.py`: + +```python +from ranksmith.confidence._types import ( + AnswerConfidenceInput, + JudgmentConfidenceInput, + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + StructuralConfidenceInput, + TaskType, +) +``` + +Add branches in `format_confidence_input(...)`: + +```python + if task_type == "query_answerability_confidence": + if not isinstance(item, QueryAnswerabilityConfidenceInput): + raise ConfidenceInputError( + "query_answerability_confidence requires " + "QueryAnswerabilityConfidenceInput" + ) + query = _require_non_empty(item.query, field_name="query") + answer = _require_non_empty(item.answer, field_name="answer") + return f"Query:\n{query}\n\nAnswer:\n{answer}" + + if task_type == "query_context_answerability_confidence": + if not isinstance(item, QueryContextAnswerabilityConfidenceInput): + raise ConfidenceInputError( + "query_context_answerability_confidence requires " + "QueryContextAnswerabilityConfidenceInput" + ) + query = _require_non_empty(item.query, field_name="query") + context = _require_non_empty(item.context, field_name="context") + answer = _require_non_empty(item.answer, field_name="answer") + return f"Query:\n{query}\n\nContext:\n{context}\n\nAnswer:\n{answer}" +``` + +Modify `_task_type(...)` in `src/ranksmith/confidence/_scorer.py` to accept the two new literals. + +Modify `src/ranksmith/confidence/__init__.py` to import/export both new dataclasses. + +- [ ] **Step 4: Run runtime task tests and targeted existing tests** + +Run: + +```bash +uv run pytest tests/test_confidence_answerability_tasks.py tests/test_confidence_estimator.py tests/test_confidence_scorer.py -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit Task 1** + +```bash +git add src/ranksmith/confidence tests/test_confidence_answerability_tasks.py +git commit -m "feat: add answerability confidence tasks" +``` + +--- + +### Task 2: Training Dataset And Feature Support + +**Files:** +- Modify: `src/ranksmith/confidence_training/_types.py` +- Modify: `src/ranksmith/confidence_training/_dataset.py` +- Modify: `src/ranksmith/confidence_training/_features.py` +- Test: `tests/test_confidence_training_answerability.py` + +- [ ] **Step 1: Write failing training tests** + +Create `tests/test_confidence_training_answerability.py`: + +```python +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from ranksmith.confidence import ( + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, +) +from ranksmith.confidence_training import ConfidenceTrainingConfig +from ranksmith.confidence_training._dataset import load_canonical_dataset +from ranksmith.confidence_training._errors import ConfidenceDatasetError +from ranksmith.confidence_training._features import sample_to_confidence_input + + +def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None: + path.write_text( + "".join(json.dumps(row) + "\n" for row in rows), + encoding="utf-8", + ) + + +def test_training_config_accepts_query_answerability_task(tmp_path: Path) -> None: + config = ConfidenceTrainingConfig( + task_type="query_answerability_confidence", + dataset_path=tmp_path / "data.jsonl", + output_dir=tmp_path / "run", + export_path=tmp_path / "scorer.joblib", + ) + + assert config.task_type == "query_answerability_confidence" + + +def test_loads_query_answerability_canonical_dataset(tmp_path: Path) -> None: + path = tmp_path / "query.jsonl" + _write_jsonl( + path, + [ + { + "id": "q1::base", + "task_type": "query_answerability_confidence", + "query": "Who?", + "answer": "Nancy Travis", + "gold_answer": "Nancy Travis", + "label": 1, + "metadata": {"input_metadata": {}}, + } + ], + ) + + samples = load_canonical_dataset( + path, + task_type="query_answerability_confidence", + ) + + assert samples[0].query == "Who?" + assert samples[0].answer == "Nancy Travis" + assert isinstance(sample_to_confidence_input(samples[0]), QueryAnswerabilityConfidenceInput) + + +def test_loads_query_context_answerability_canonical_dataset(tmp_path: Path) -> None: + path = tmp_path / "query_context.jsonl" + _write_jsonl( + path, + [ + { + "id": "q1::doc1", + "task_type": "query_context_answerability_confidence", + "query": "Who?", + "context": "Karen was played by Nancy Travis.", + "answer": "Nancy Travis", + "gold_answer": "Nancy Travis", + "label": 1, + "metadata": {"input_metadata": {}}, + } + ], + ) + + samples = load_canonical_dataset( + path, + task_type="query_context_answerability_confidence", + ) + + assert samples[0].context == "Karen was played by Nancy Travis." + assert isinstance( + sample_to_confidence_input(samples[0]), + QueryContextAnswerabilityConfidenceInput, + ) + + +def test_query_context_dataset_rejects_missing_context(tmp_path: Path) -> None: + path = tmp_path / "bad.jsonl" + _write_jsonl( + path, + [ + { + "id": "q1::doc1", + "task_type": "query_context_answerability_confidence", + "query": "Who?", + "answer": "Nancy Travis", + "label": 1, + } + ], + ) + + with pytest.raises(ConfidenceDatasetError, match="missing required field: context"): + load_canonical_dataset( + path, + task_type="query_context_answerability_confidence", + ) +``` + +- [ ] **Step 2: Run training tests and verify failure** + +Run: + +```bash +uv run pytest tests/test_confidence_training_answerability.py -q +``` + +Expected: FAIL because `ConfidenceTrainingConfig` rejects the new task type or dataset schemas are missing. + +- [ ] **Step 3: Implement training schema support** + +Modify `ConfidenceTrainingConfig.__post_init__` in `src/ranksmith/confidence_training/_types.py`: + +```python +if self.task_type not in { + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", +}: + raise ConfidenceTrainingConfigError("unsupported task_type") +``` + +Extend `CanonicalConfidenceSample` only if `query`, `context`, and `answer` fields already cannot represent the new tasks. Prefer reusing existing fields. + +Modify `src/ranksmith/confidence_training/_dataset.py`: + +```python +_QUERY_ANSWERABILITY_REQUIRED = ("id", "query", "answer", "label") +_QUERY_CONTEXT_ANSWERABILITY_REQUIRED = ("id", "query", "context", "answer", "label") +_QUERY_ANSWERABILITY_ALLOWED = { + *_QUERY_ANSWERABILITY_REQUIRED, + "task_type", + "gold_answer", + "source", + "group_id", + "metadata", +} +_QUERY_CONTEXT_ANSWERABILITY_ALLOWED = { + *_QUERY_CONTEXT_ANSWERABILITY_REQUIRED, + "task_type", + "gold_answer", + "source", + "group_id", + "metadata", +} +``` + +Add `_TASK_SCHEMAS` entries that construct `CanonicalConfidenceSample` with the matching `task_type`, `query`, `context`, `answer`, `gold_answer`, `source`, `group_id`, and `metadata`. + +Modify `src/ranksmith/confidence_training/_features.py` to map: + +```python +if sample.task_type == "query_answerability_confidence": + return QueryAnswerabilityConfidenceInput( + query=_required(sample.query, "query"), + answer=_required(sample.answer, "answer"), + ) +if sample.task_type == "query_context_answerability_confidence": + return QueryContextAnswerabilityConfidenceInput( + query=_required(sample.query, "query"), + context=_required(sample.context, "context"), + answer=_required(sample.answer, "answer"), + ) +``` + +- [ ] **Step 4: Run training tests and existing confidence training tests** + +Run: + +```bash +uv run pytest tests/test_confidence_training_answerability.py tests/test_confidence_training_*.py -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit Task 2** + +```bash +git add src/ranksmith/confidence_training tests/test_confidence_training_answerability.py +git commit -m "feat: support answerability confidence training" +``` + +--- + +### Task 3: Generation Pipeline Answerability Datasets + +**Files:** +- Modify: `src/ranksmith/confidence_generation/_types.py` +- Modify: `src/ranksmith/confidence_generation/_io.py` +- Modify: `src/ranksmith/confidence_generation/_prompts.py` +- Modify: `src/ranksmith/confidence_generation/_pipeline.py` +- Modify: `src/ranksmith/confidence_generation/__init__.py` +- Test: `tests/test_confidence_generation_answerability.py` + +- [ ] **Step 1: Write failing generation tests** + +Create `tests/test_confidence_generation_answerability.py`: + +```python +from __future__ import annotations + +import json +from pathlib import Path + +from ranksmith.confidence_generation import ( + QueryAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationConfig, + generate_query_answerability_confidence_dataset, + generate_query_context_answerability_confidence_dataset, +) +from ranksmith.model import ModelResponse + + +class RecordingProvider: + def __init__(self, answer: str = "Nancy Travis") -> None: + self.answer = answer + self.requests = [] + + def complete(self, request): # noqa: ANN001 + self.requests.append(request) + return ModelResponse(content=json.dumps({"answer": self.answer})) + + +def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None: + path.write_text( + "".join(json.dumps(row) + "\n" for row in rows), + encoding="utf-8", + ) + + +def _read_jsonl(path: Path) -> list[dict[str, object]]: + return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()] + + +def test_generates_query_answerability_dataset(tmp_path: Path) -> None: + input_path = tmp_path / "input.jsonl" + output_path = tmp_path / "out.jsonl" + _write_jsonl( + input_path, + [ + { + "id": "q1::base", + "query": "Who played Karen?", + "gold_answer": "Nancy Travis", + "metadata": {"split": "tiny"}, + } + ], + ) + provider = RecordingProvider() + + result = generate_query_answerability_confidence_dataset( + QueryAnswerabilityGenerationConfig( + input_path=input_path, + output_path=output_path, + provider=provider, + ) + ) + + rows = _read_jsonl(output_path) + assert result.generated_count == 1 + assert rows[0]["task_type"] == "query_answerability_confidence" + assert rows[0]["query"] == "Who played Karen?" + assert rows[0]["answer"] == "Nancy Travis" + assert rows[0]["label"] == 1 + + +def test_generates_query_context_answerability_dataset(tmp_path: Path) -> None: + input_path = tmp_path / "input.jsonl" + output_path = tmp_path / "out.jsonl" + _write_jsonl( + input_path, + [ + { + "id": "q1::doc1", + "query": "Who played Karen?", + "context": "Karen was played by Nancy Travis.", + "gold_answer": "Nancy Travis", + "group_id": "q1", + } + ], + ) + provider = RecordingProvider() + + result = generate_query_context_answerability_confidence_dataset( + QueryContextAnswerabilityGenerationConfig( + input_path=input_path, + output_path=output_path, + provider=provider, + ) + ) + + rows = _read_jsonl(output_path) + assert result.generated_count == 1 + assert rows[0]["task_type"] == "query_context_answerability_confidence" + assert rows[0]["context"] == "Karen was played by Nancy Travis." + assert rows[0]["answer"] == "Nancy Travis" + assert rows[0]["label"] == 1 +``` + +- [ ] **Step 2: Run generation tests and verify failure** + +Run: + +```bash +uv run pytest tests/test_confidence_generation_answerability.py -q +``` + +Expected: FAIL because new generation configs/functions are not exported. + +- [ ] **Step 3: Implement generation types, IO, prompts, and pipelines** + +Add config/sample dataclasses to `src/ranksmith/confidence_generation/_types.py`: + +```python +@dataclass(frozen=True) +class QueryAnswerabilityGenerationConfig: + input_path: str | Path + output_path: str | Path + provider: ModelProvider + overwrite: bool = False + resume: bool = False + max_items: int | None = None + include_raw_model_output: bool = True + on_usage: UsageCallback | None = None + source: str | None = None + no_answer_value: str = "__NO_ANSWER__" + + +@dataclass(frozen=True) +class QueryContextAnswerabilityGenerationConfig: + input_path: str | Path + output_path: str | Path + provider: ModelProvider + overwrite: bool = False + resume: bool = False + max_items: int | None = None + max_context_chars: int = 4000 + include_raw_model_output: bool = True + on_usage: UsageCallback | None = None + source: str | None = None + no_answer_value: str = "__NO_ANSWER__" +``` + +Reuse existing `_validate_common_config`, `no_answer_value`, and positive integer validation. + +Add raw sample dataclasses: + +```python +@dataclass(frozen=True) +class QueryAnswerabilityGenerationSample: + id: str + query: str + gold_answer: str | list[str] + source: str | None = None + group_id: str | None = None + metadata: Mapping[str, Any] = field(default_factory=dict) + + +@dataclass(frozen=True) +class QueryContextAnswerabilityGenerationSample: + id: str + query: str + context: str + gold_answer: str | list[str] + source: str | None = None + group_id: str | None = None + metadata: Mapping[str, Any] = field(default_factory=dict) +``` + +In `_io.py`, add loaders mirroring existing answer sample loading: +- query-only requires `id`, `query`, `gold_answer`. +- query+context requires `id`, `query`, `context`, `gold_answer`. +- unexpected fields fail. +- whitespace-only text fails. +- context length uses raw string length and `max_context_chars`. + +In `_prompts.py`, add: +- `QUERY_ANSWERABILITY_SYSTEM_PROMPT` +- `QUERY_CONTEXT_ANSWERABILITY_SYSTEM_PROMPT` +- `build_query_answerability_prompt(sample, no_answer_value=...)` +- `build_query_context_answerability_prompt(sample, no_answer_value=...)` + +Both prompts require JSON response `{"answer": "..."}`. + +In `_pipeline.py`, add: +- `generate_query_answerability_confidence_dataset(config)` +- `generate_query_context_answerability_confidence_dataset(config)` + +Build canonical rows with task types: +- `query_answerability_confidence` +- `query_context_answerability_confidence` + +Use existing `parse_answer_output()` and `normalized_exact_match()`. + +Update `__init__.py` exports. + +- [ ] **Step 4: Run generation tests and existing generation tests** + +Run: + +```bash +uv run pytest tests/test_confidence_generation_answerability.py tests/test_confidence_generation_*.py -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit Task 3** + +```bash +git add src/ranksmith/confidence_generation tests/test_confidence_generation_answerability.py +git commit -m "feat: generate answerability confidence datasets" +``` + +--- + +### Task 4: Confidence Gain Strategy + +**Files:** +- Create: `src/ranksmith/strategies/_confidence_gain.py` +- Modify: `src/ranksmith/strategies/__init__.py` +- Test: `tests/test_confidence_gain_strategy.py` + +- [ ] **Step 1: Write failing strategy tests** + +Create `tests/test_confidence_gain_strategy.py`: + +```python +from __future__ import annotations + +from dataclasses import dataclass + +import pytest + +from ranksmith.errors import DocumentTooLongError, RerankInputError, RerankProviderError +from ranksmith.strategies import ConfidenceGainStrategy +from ranksmith.types import Document + + +@dataclass +class FakeConfidenceResult: + score: float + task_type: str + feature_schema_version: str = "structural-v1" + metadata: dict[str, object] | None = None + + +class BaseEstimator: + task_type = "query_answerability_confidence" + + def __init__(self, score: float) -> None: + self.score_value = score + self.items = [] + + def score(self, item): # noqa: ANN001 + self.items.append(item) + return FakeConfidenceResult(score=self.score_value, task_type=self.task_type) + + +class ContextEstimator: + task_type = "query_context_answerability_confidence" + + def __init__(self, scores: list[float]) -> None: + self.scores = list(scores) + self.items = [] + + def score(self, item): # noqa: ANN001 + self.items.append(item) + return FakeConfidenceResult( + score=self.scores.pop(0), + task_type=self.task_type, + ) + + +class AnswerGenerator: + def __init__(self) -> None: + self.query_calls: list[str] = [] + self.context_calls: list[tuple[str, str]] = [] + + def answer_query(self, query: str) -> str: + self.query_calls.append(query) + return "base answer" + + def answer_with_context(self, query: str, context: str) -> str: + self.context_calls.append((query, context)) + return f"answer from {context}" + + +def test_confidence_gain_strategy_sorts_by_gain_and_preserves_ties() -> None: + generator = AnswerGenerator() + strategy = ConfidenceGainStrategy( + base_estimator=BaseEstimator(0.4), + context_estimator=ContextEstimator([0.9, 0.5, 0.5]), + answer_generator=generator, + ) + documents = [Document("doc-a"), Document("doc-b"), Document("doc-c")] + + results = strategy.rerank(query="Who?", documents=documents, model_client=object()) + + assert [result.original_index for result in results] == [0, 1, 2] + assert [result.metadata["confidence_gain"] for result in results] == [ + pytest.approx(0.5), + pytest.approx(0.1), + pytest.approx(0.1), + ] + assert len(generator.query_calls) == 1 + assert len(generator.context_calls) == 3 + + +def test_confidence_gain_strategy_applies_top_k() -> None: + strategy = ConfidenceGainStrategy( + base_estimator=BaseEstimator(0.2), + context_estimator=ContextEstimator([0.4, 0.9]), + answer_generator=AnswerGenerator(), + ) + + results = strategy.rerank( + query="Who?", + documents=[Document("a"), Document("b")], + model_client=object(), + top_k=1, + ) + + assert [result.original_index for result in results] == [1] + + +def test_confidence_gain_strategy_rejects_wrong_estimator_task() -> None: + bad_base = ContextEstimator([0.1]) + + with pytest.raises(RerankInputError, match="base_estimator"): + ConfidenceGainStrategy( + base_estimator=bad_base, + context_estimator=ContextEstimator([0.2]), + answer_generator=AnswerGenerator(), + ) + + +def test_confidence_gain_strategy_wraps_empty_answer() -> None: + class EmptyAnswerGenerator(AnswerGenerator): + def answer_query(self, query: str) -> str: + return " " + + strategy = ConfidenceGainStrategy( + base_estimator=BaseEstimator(0.2), + context_estimator=ContextEstimator([0.4]), + answer_generator=EmptyAnswerGenerator(), + ) + + with pytest.raises(RerankProviderError, match="answer_query returned empty"): + strategy.rerank(query="Who?", documents=[Document("a")], model_client=object()) + + +def test_confidence_gain_strategy_rejects_long_document() -> None: + strategy = ConfidenceGainStrategy( + base_estimator=BaseEstimator(0.2), + context_estimator=ContextEstimator([0.4]), + answer_generator=AnswerGenerator(), + max_document_chars=3, + ) + + with pytest.raises(DocumentTooLongError): + strategy.rerank(query="Who?", documents=[Document("abcd")], model_client=object()) +``` + +- [ ] **Step 2: Run strategy tests and verify failure** + +Run: + +```bash +uv run pytest tests/test_confidence_gain_strategy.py -q +``` + +Expected: FAIL because `ConfidenceGainStrategy` is not exported. + +- [ ] **Step 3: Implement strategy** + +Create `src/ranksmith/strategies/_confidence_gain.py`: + +```python +from __future__ import annotations + +import math +from collections.abc import Sequence +from dataclasses import dataclass +from typing import Protocol + +from ranksmith.confidence import ( + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + StructuralConfidenceResult, +) +from ranksmith.errors import ( + DocumentTooLongError, + RerankInputError, + RerankProviderError, + RerankStrategyError, +) +from ranksmith.types import Document, RerankResult + +from ._common import validate_top_k + + +class AnswerGenerator(Protocol): + def answer_query(self, query: str) -> str: ... + + def answer_with_context(self, query: str, context: str) -> str: ... + + +class ConfidenceEstimator(Protocol): + task_type: str + + def score(self, item: object) -> StructuralConfidenceResult: ... + + +@dataclass(frozen=True) +class ConfidenceGainResult: + base_score: float + context_score: float + gain: float + base_result: StructuralConfidenceResult + context_result: StructuralConfidenceResult + + +@dataclass(frozen=True) +class ConfidenceGainStrategy: + base_estimator: ConfidenceEstimator + context_estimator: ConfidenceEstimator + answer_generator: AnswerGenerator + max_document_chars: int = 4000 + algorithm: str = "confidence_gain" + + def __post_init__(self) -> None: + if self.algorithm != "confidence_gain": + raise ValueError('algorithm must be "confidence_gain"') + if self.max_document_chars < 1: + raise ValueError("max_document_chars must be greater than 0") + if self.base_estimator.task_type != "query_answerability_confidence": + raise RerankInputError( + "base_estimator must use query_answerability_confidence" + ) + if ( + self.context_estimator.task_type + != "query_context_answerability_confidence" + ): + raise RerankInputError( + "context_estimator must use " + "query_context_answerability_confidence" + ) + + def rerank( + self, + *, + query: str, + documents: Sequence[Document], + model_client: object, + top_k: int | None = None, + ) -> list[RerankResult]: + del model_client + validate_top_k(top_k) + _require_non_empty(query, "query") + self._validate_documents(documents) + if not documents: + return [] + + base_answer = _call_answer_query(self.answer_generator, query) + base_result = self.base_estimator.score( + QueryAnswerabilityConfidenceInput(query=query, answer=base_answer) + ) + base_score = _probability(base_result.score, "base confidence") + + scored: list[tuple[int, ConfidenceGainResult]] = [] + for original_index, document in enumerate(documents): + context_answer = _call_answer_with_context( + self.answer_generator, + query, + document.text, + ) + context_result = self.context_estimator.score( + QueryContextAnswerabilityConfidenceInput( + query=query, + context=document.text, + answer=context_answer, + ) + ) + context_score = _probability( + context_result.score, + f"context confidence at index {original_index}", + ) + gain = context_score - base_score + if not math.isfinite(gain) or gain < -1 or gain > 1: + raise RerankStrategyError("confidence gain must be finite in [-1, 1]") + scored.append( + ( + original_index, + ConfidenceGainResult( + base_score=base_score, + context_score=context_score, + gain=gain, + base_result=base_result, + context_result=context_result, + ), + ) + ) + + ordered = sorted(scored, key=lambda item: (-item[1].gain, item[0])) + if top_k is not None: + ordered = ordered[:top_k] + return [ + RerankResult( + document=documents[original_index], + rank=rank, + original_index=original_index, + metadata={ + "strategy": "confidence_gain", + "algorithm": self.algorithm, + "base_confidence": gain_result.base_score, + "context_confidence": gain_result.context_score, + "confidence_gain": gain_result.gain, + }, + ) + for rank, (original_index, gain_result) in enumerate(ordered, start=1) + ] + + def _validate_documents(self, documents: Sequence[Document]) -> None: + for index, document in enumerate(documents): + if len(document.text) > self.max_document_chars: + raise DocumentTooLongError( + f"Document at index {index} has {len(document.text)} " + f"characters, exceeding max_document_chars={self.max_document_chars}." + ) + + +def _require_non_empty(value: str, name: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise RerankInputError(f"{name} must not be empty") + return value + + +def _call_answer_query(generator: AnswerGenerator, query: str) -> str: + try: + answer = generator.answer_query(query) + except RerankProviderError: + raise + except Exception as exc: + raise RerankProviderError(str(exc)) from exc + return _answer_text(answer, "answer_query") + + +def _call_answer_with_context( + generator: AnswerGenerator, + query: str, + context: str, +) -> str: + try: + answer = generator.answer_with_context(query, context) + except RerankProviderError: + raise + except Exception as exc: + raise RerankProviderError(str(exc)) from exc + return _answer_text(answer, "answer_with_context") + + +def _answer_text(value: object, caller: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise RerankProviderError(f"{caller} returned empty answer.") + return value + + +def _probability(value: object, name: str) -> float: + if isinstance(value, bool) or not isinstance(value, int | float): + raise RerankStrategyError(f"{name} must be numeric") + score = float(value) + if not math.isfinite(score) or score < 0 or score > 1: + raise RerankStrategyError(f"{name} must be finite in [0, 1]") + return score +``` + +Modify `src/ranksmith/strategies/__init__.py` to export: + +```python +from ranksmith.strategies._confidence_gain import ( + AnswerGenerator, + ConfidenceGainResult, + ConfidenceGainStrategy, +) +``` + +- [ ] **Step 4: Run strategy tests and existing strategy tests** + +Run: + +```bash +uv run pytest tests/test_confidence_gain_strategy.py tests/test_*strategy*.py -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit Task 4** + +```bash +git add src/ranksmith/strategies tests/test_confidence_gain_strategy.py +git commit -m "feat: add confidence gain strategy" +``` + +--- + +### Task 5: Documentation And Verification + +**Files:** +- Modify: `docs/wiki/02_architecture.md` +- Modify: `docs/wiki/04_references_index.md` +- Modify: `README.md` +- Modify: `README.ko.md` +- Modify: `docs/specs/spec_confidence_gain_reranking.md` + +- [ ] **Step 1: Update architecture docs** + +In `docs/wiki/02_architecture.md`, update the Confidence section: + +```markdown +`ConfidenceGainStrategy`는 `ranksmith.confidence`의 answerability confidence scorer를 사용해 `Conf(Q+C)-Conf(Q)`를 계산하고 문서를 정렬하는 Strategy다. + +현재 범위: +- query-only answerability confidence +- query+context answerability confidence +- sync confidence gain reranking + +제외: +- CBDR retrieval skip +- async confidence gain reranking +- reranker fine-tuning +``` + +- [ ] **Step 2: Update README and Korean README with matching structure** + +Add a short usage section to both `README.md` and `README.ko.md`. + +English example: + +```python +from ranksmith.confidence import StructuralConfidenceEstimator +from ranksmith.strategies import ConfidenceGainStrategy + +base_estimator = StructuralConfidenceEstimator.from_artifact( + "query-answerability.joblib" +) +context_estimator = StructuralConfidenceEstimator.from_artifact( + "query-context-answerability.joblib" +) + +strategy = ConfidenceGainStrategy( + base_estimator=base_estimator, + context_estimator=context_estimator, + answer_generator=my_answer_generator, +) +``` + +Korean README must mirror the same section order, table shape, and no benchmark claims. + +- [ ] **Step 3: Mark spec checklist implementation items complete** + +In `docs/specs/spec_confidence_gain_reranking.md`, mark completed task checklist items as `[x]` only after the corresponding implementation and tests pass. + +- [ ] **Step 4: Run targeted checks** + +Run: + +```bash +uv run pytest tests/test_confidence_answerability_tasks.py tests/test_confidence_training_answerability.py tests/test_confidence_generation_answerability.py tests/test_confidence_gain_strategy.py -q +uv run ruff check src/ranksmith tests +uv run mypy src/ranksmith tests +``` + +Expected: +- pytest passes. +- ruff passes. +- mypy passes. + +- [ ] **Step 5: Run full verification** + +Run: + +```bash +./scripts/verify.sh +``` + +Expected: all tests, lint, format, type check, and build pass. + +- [ ] **Step 6: Commit docs and verification updates** + +```bash +git add docs README.md README.ko.md +git commit -m "docs: document confidence gain reranking" +``` + +--- + +## Self-Review + +### Spec Coverage +- Runtime query-only/contextual confidence tasks: Task 1. +- Training schema/task expansion: Task 2. +- Generation dataset expansion: Task 3. +- Confidence gain utility/Strategy: Task 4. +- Docs and verification: Task 5. +- CBDR retrieval skip: intentionally excluded by spec. +- Async strategy: intentionally excluded by spec. +- Benchmark claims: intentionally excluded by spec. + +### Placeholder Scan +- No placeholder markers or unspecified implementation steps remain. +- Each task has explicit files, tests, commands, and expected outcomes. + +### Type Consistency +- New task names are consistently: + - `query_answerability_confidence` + - `query_context_answerability_confidence` +- New runtime inputs are consistently: + - `QueryAnswerabilityConfidenceInput` + - `QueryContextAnswerabilityConfidenceInput` +- Strategy name is consistently `ConfidenceGainStrategy`. diff --git a/docs/superpowers/plans/2026-06-05-lmstudio-confidence-training-pipeline.md b/docs/superpowers/plans/2026-06-05-lmstudio-confidence-training-pipeline.md new file mode 100644 index 0000000..07304f3 --- /dev/null +++ b/docs/superpowers/plans/2026-06-05-lmstudio-confidence-training-pipeline.md @@ -0,0 +1,1313 @@ +# LM Studio Confidence Training Pipeline Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a local LM Studio path that can generate answerability confidence datasets, train CBDR-ready scorer artifacts, and run CBDR with LM Studio instead of Azure. + +**Architecture:** Add LM Studio as a `ModelProvider` under `ranksmith.integrations`, keep answer generation provider-agnostic, and expose thin scripts over the existing confidence generation/training APIs. CBDR remains unchanged as an algorithm; only its runtime answer generator assembly becomes provider-selectable. + +**Tech Stack:** Python 3.10+, OpenAI Python SDK, pytest, argparse, existing `ranksmith.confidence_generation`, existing `ranksmith.confidence_training`, existing `CBDRStrategy`. + +--- + +## File Map + +- Create `src/ranksmith/integrations/_lmstudio_provider.py` + LM Studio OpenAI-compatible `ModelProvider`. Converts ranksmith `json_object` requests to LM Studio `json_schema`. + +- Create `src/ranksmith/integrations/_answer_generator.py` + Provider-agnostic sync answer generator with `answer_query()` and `answer_with_context()`. + +- Modify `src/ranksmith/integrations/_azure_answer_generator.py` + Make `AzureAnswerGenerator` reuse `ProviderAnswerGenerator` instead of owning duplicate prompt/parse logic. + +- Modify `src/ranksmith/integrations/__init__.py` + Export `LMStudioModelProvider` and `ProviderAnswerGenerator` from submodule only. + +- Create `scripts/generate_confidence_dataset.py` + CLI for `query_answerability_confidence` and `query_context_answerability_confidence`. + +- Create `scripts/train_confidence_scorer.py` + CLI wrapper for `train_confidence_scorer()`. + +- Create `src/ranksmith/confidence_training/_dataset_report.py` + Dataset balance/source/group summary helper. + +- Create `scripts/report_confidence_dataset.py` + CLI wrapper for dataset report. + +- Modify `scripts/compare_reranking.py` + Add `--cbdr-answer-provider azure|lmstudio` and LM Studio flags for CBDR runtime. + +- Create fixtures: + - `tests/fixtures/confidence_query_answerability_raw.jsonl` + - `tests/fixtures/confidence_query_context_answerability_raw.jsonl` + +- Add tests: + - `tests/test_lmstudio_provider.py` + - `tests/test_provider_answer_generator.py` + - `tests/test_generate_confidence_dataset_script.py` + - `tests/test_train_confidence_scorer_script.py` + - `tests/test_confidence_training_dataset_report.py` + - extend `tests/test_compare_reranking.py` + +--- + +## Task 1: LM Studio Provider + +**Files:** +- Create: `src/ranksmith/integrations/_lmstudio_provider.py` +- Modify: `src/ranksmith/integrations/__init__.py` +- Test: `tests/test_lmstudio_provider.py` + +- [x] **Step 1: Write failing provider tests** + +Create `tests/test_lmstudio_provider.py`: + +```python +from __future__ import annotations + +import importlib + +import pytest + +from ranksmith.errors import RerankInputError, RerankProviderError +from ranksmith.model import ModelMessage, ModelRequest + + +class FakeChoiceMessage: + content = '{"answer":"Paris"}' + + +class FakeChoice: + message = FakeChoiceMessage() + + +class FakeUsage: + prompt_tokens = 3 + completion_tokens = 2 + total_tokens = 5 + + +class FakeResponse: + choices = [FakeChoice()] + usage = FakeUsage() + + +class FakeCompletions: + def __init__(self) -> None: + self.kwargs = None + + def create(self, **kwargs: object) -> FakeResponse: + self.kwargs = kwargs + return FakeResponse() + + +class FakeChat: + def __init__(self) -> None: + self.completions = FakeCompletions() + + +class FakeClient: + def __init__(self) -> None: + self.chat = FakeChat() + + +def test_lmstudio_provider_is_submodule_export_only() -> None: + integrations = importlib.import_module("ranksmith.integrations") + root = importlib.import_module("ranksmith") + + assert integrations.LMStudioModelProvider is not None + assert not hasattr(root, "LMStudioModelProvider") + + +def test_lmstudio_provider_converts_json_object_to_json_schema() -> None: + from ranksmith.integrations import LMStudioModelProvider + + client = FakeClient() + provider = LMStudioModelProvider(model="google/gemma-4-12b", client=client) + + response = provider.complete( + ModelRequest( + messages=[ModelMessage(role="user", content="Return JSON.")], + response_format="json_object", + temperature=0, + ) + ) + + kwargs = client.chat.completions.kwargs + assert response.content == '{"answer":"Paris"}' + assert kwargs["model"] == "google/gemma-4-12b" + assert kwargs["temperature"] == 0 + assert kwargs["max_tokens"] == 128 + assert kwargs["response_format"]["type"] == "json_schema" + assert kwargs["response_format"]["json_schema"]["name"] == "ranksmith_json_response" + + +def test_lmstudio_provider_uses_env_model(monkeypatch: pytest.MonkeyPatch) -> None: + from ranksmith.integrations import LMStudioModelProvider + + monkeypatch.setenv("LMSTUDIO_MODEL", "google/gemma-4-12b") + + provider = LMStudioModelProvider(client=FakeClient()) + + provider.complete(ModelRequest(messages=[ModelMessage(role="user", content="x")])) + + +def test_lmstudio_provider_requires_model(monkeypatch: pytest.MonkeyPatch) -> None: + from ranksmith.integrations import LMStudioModelProvider + + monkeypatch.delenv("LMSTUDIO_MODEL", raising=False) + + with pytest.raises(RerankInputError, match="LMSTUDIO_MODEL"): + LMStudioModelProvider(client=FakeClient()) + + +def test_lmstudio_provider_wraps_client_error() -> None: + from ranksmith.integrations import LMStudioModelProvider + + class FailingCompletions: + def create(self, **kwargs: object) -> object: + del kwargs + raise RuntimeError("server down") + + class FailingChat: + completions = FailingCompletions() + + class FailingClient: + chat = FailingChat() + + provider = LMStudioModelProvider(model="google/gemma-4-12b", client=FailingClient()) + + with pytest.raises(RerankProviderError, match="server down"): + provider.complete(ModelRequest(messages=[ModelMessage(role="user", content="x")])) +``` + +- [x] **Step 2: Run failing test** + +```bash +uv run pytest tests/test_lmstudio_provider.py -q +``` + +Expected: fails because `LMStudioModelProvider` does not exist. + +- [x] **Step 3: Implement provider** + +Create `src/ranksmith/integrations/_lmstudio_provider.py`: + +```python +from __future__ import annotations + +import os +from typing import Any, cast + +from openai import OpenAI + +from ranksmith.errors import RerankInputError, RerankProviderError +from ranksmith.model import ModelRequest, ModelResponse +from ranksmith.types import RerankUsage + + +class LMStudioModelProvider: + def __init__( + self, + *, + model: str | None = None, + base_url: str | None = None, + api_key: str | None = None, + timeout: float | None = None, + max_tokens: int = 128, + client: Any | None = None, + ) -> None: + resolved_model = model or _env_value("LMSTUDIO_MODEL") + if resolved_model is None or resolved_model == "": + raise RerankInputError("LMSTUDIO_MODEL is required") + if max_tokens < 1: + raise RerankInputError("max_tokens must be greater than 0") + self._model = resolved_model + self._max_tokens = max_tokens + self._client = client or OpenAI( + base_url=base_url or _env_value("LMSTUDIO_BASE_URL") or "http://localhost:1234/v1", + api_key=api_key or _env_value("LMSTUDIO_API_KEY") or "lm-studio", + timeout=timeout, + ) + + def complete(self, request: ModelRequest) -> ModelResponse: + try: + response = self._client.chat.completions.create( + model=self._model, + messages=_to_openai_messages(request), + response_format=_lmstudio_response_format(request), + temperature=request.temperature, + max_tokens=self._max_tokens, + ) + except Exception as exc: + raise RerankProviderError(str(exc)) from exc + + content = _extract_content(response) + if content is None or content == "": + raise RerankProviderError("LM Studio returned an empty response.") + return ModelResponse(content=content, usage=_extract_usage(response)) + + +def _lmstudio_response_format(request: ModelRequest) -> dict[str, object]: + if request.response_format != "json_object": + raise RerankProviderError("LM Studio provider only supports json_object requests.") + return { + "type": "json_schema", + "json_schema": { + "name": "ranksmith_json_response", + "schema": {"type": "object"}, + }, + } + + +def _to_openai_messages(request: ModelRequest) -> list[dict[str, str]]: + return [ + {"role": message.role, "content": message.content} + for message in request.messages + ] + + +def _extract_content(response: object) -> str | None: + choices = getattr(response, "choices", None) + if not choices: + raise RerankProviderError("LM Studio returned an invalid response.") + try: + return cast(str | None, choices[0].message.content) + except AttributeError as exc: + raise RerankProviderError("LM Studio returned an invalid response.") from exc + + +def _extract_usage(response: object) -> RerankUsage | None: + usage = getattr(response, "usage", None) + if usage is None: + return None + return RerankUsage( + prompt_tokens=int(getattr(usage, "prompt_tokens", 0) or 0), + completion_tokens=int(getattr(usage, "completion_tokens", 0) or 0), + total_tokens=int(getattr(usage, "total_tokens", 0) or 0), + ) + + +def _env_value(name: str) -> str | None: + value = os.environ.get(name) + if value is not None and value != "": + return value + return None +``` + +Modify `src/ranksmith/integrations/__init__.py`: + +```python +from ranksmith.integrations._azure_answer_generator import AzureAnswerGenerator +from ranksmith.integrations._lmstudio_provider import LMStudioModelProvider + +__all__ = ["AzureAnswerGenerator", "LMStudioModelProvider"] +``` + +- [x] **Step 4: Run provider tests** + +```bash +uv run pytest tests/test_lmstudio_provider.py -q +``` + +Expected: all tests pass. + +--- + +## Task 2: Provider-Agnostic Answer Generator + +**Files:** +- Create: `src/ranksmith/integrations/_answer_generator.py` +- Modify: `src/ranksmith/integrations/_azure_answer_generator.py` +- Modify: `src/ranksmith/integrations/__init__.py` +- Test: `tests/test_provider_answer_generator.py` +- Update: `tests/test_azure_answer_generator.py` + +- [x] **Step 1: Write failing tests** + +Create `tests/test_provider_answer_generator.py`: + +```python +from __future__ import annotations + +from dataclasses import dataclass + +import pytest + +from ranksmith.errors import RerankParseError, RerankProviderError +from ranksmith.model import ModelRequest, ModelResponse + + +@dataclass +class FakeProvider: + responses: list[str] + requests: list[ModelRequest] + + def complete(self, request: ModelRequest) -> ModelResponse: + self.requests.append(request) + return ModelResponse(content=self.responses.pop(0)) + + +def test_provider_answer_generator_parses_query_answer() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + provider = FakeProvider(responses=['{"answer":"Paris"}'], requests=[]) + generator = ProviderAnswerGenerator(provider=provider) + + assert generator.answer_query("capital of france?") == "Paris" + assert provider.requests[0].response_format == "json_object" + assert provider.requests[0].temperature == 0 + + +def test_provider_answer_generator_parses_context_answer() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + provider = FakeProvider(responses=['{"answer":"Nancy Travis"}'], requests=[]) + generator = ProviderAnswerGenerator(provider=provider) + + assert generator.answer_with_context("who?", "Nancy Travis played Karen.") == "Nancy Travis" + assert "Use only the context" in provider.requests[0].messages[1].content + + +@pytest.mark.parametrize("content", ["not json", "{}", '{"answer":""}', '{"answer":1}']) +def test_provider_answer_generator_rejects_invalid_json(content: str) -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + generator = ProviderAnswerGenerator( + provider=FakeProvider(responses=[content], requests=[]), + ) + + with pytest.raises(RerankParseError): + generator.answer_query("x") + + +def test_provider_answer_generator_preserves_provider_error() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + class FailingProvider: + def complete(self, request: ModelRequest) -> ModelResponse: + del request + raise RerankProviderError("provider failed") + + generator = ProviderAnswerGenerator(provider=FailingProvider()) + + with pytest.raises(RerankProviderError, match="provider failed"): + generator.answer_query("x") +``` + +- [x] **Step 2: Run failing test** + +```bash +uv run pytest tests/test_provider_answer_generator.py -q +``` + +Expected: fails because `ProviderAnswerGenerator` does not exist. + +- [x] **Step 3: Implement generator** + +Create `src/ranksmith/integrations/_answer_generator.py`: + +```python +from __future__ import annotations + +import json +from dataclasses import dataclass + +from ranksmith.errors import RerankParseError, RerankProviderError +from ranksmith.model import ModelMessage, ModelProvider, ModelRequest + + +@dataclass(frozen=True) +class ProviderAnswerGenerator: + provider: ModelProvider + no_answer_value: str = "__NO_ANSWER__" + + def __post_init__(self) -> None: + _validate_no_answer_value(self.no_answer_value) + + def answer_query(self, query: str) -> str: + return self._complete( + system=( + "You answer questions for confidence estimation. Return only JSON " + 'with an "answer" string.' + ), + user=( + f"Question:\n{query}\n\n" + "Return JSON exactly like this shape:\n" + '{"answer":"..."}\n\n' + "Answer from your parametric knowledge. If you do not know the " + f"answer, return {_answer_contract(self.no_answer_value)}." + ), + ) + + def answer_with_context(self, query: str, context: str) -> str: + return self._complete( + system=( + "You answer questions using the provided context for confidence " + 'estimation. Return only JSON with an "answer" string.' + ), + user=( + f"Question:\n{query}\n\n" + f"Context:\n{context}\n\n" + "Return JSON exactly like this shape:\n" + '{"answer":"..."}\n\n' + "Use only the context. If the context does not contain the answer, " + f"return {_answer_contract(self.no_answer_value)}." + ), + ) + + def _complete(self, *, system: str, user: str) -> str: + try: + response = self.provider.complete( + ModelRequest( + messages=[ + ModelMessage(role="system", content=system), + ModelMessage(role="user", content=user), + ], + response_format="json_object", + temperature=0, + ) + ) + except RerankProviderError: + raise + except Exception as exc: + raise RerankProviderError(str(exc)) from exc + return parse_answer(response.content) + + +def parse_answer(content: str) -> str: + try: + parsed = json.loads(content) + except json.JSONDecodeError as exc: + raise RerankParseError("answer response must be valid JSON") from exc + if not isinstance(parsed, dict): + raise RerankParseError("answer response must be a JSON object") + answer = parsed.get("answer") + if not isinstance(answer, str) or answer.strip() == "": + raise RerankParseError('answer response must contain a non-empty "answer"') + return answer + + +def _answer_contract(no_answer_value: str) -> str: + return json.dumps( + {"answer": no_answer_value}, + ensure_ascii=False, + separators=(",", ":"), + ) + + +def _validate_no_answer_value(value: object) -> None: + if not isinstance(value, str) or value.strip() == "": + raise ValueError("no_answer_value must be a non-empty string") +``` + +Modify `src/ranksmith/integrations/_azure_answer_generator.py` so `AzureAnswerGenerator` delegates: + +```python +from ranksmith.integrations._answer_generator import ProviderAnswerGenerator + +# keep env helpers and Azure provider assembly +# replace answer_query/answer_with_context/_complete/_parse_answer with: + + def answer_query(self, query: str) -> str: + return ProviderAnswerGenerator( + provider=self.provider, + no_answer_value=self.no_answer_value, + ).answer_query(query) + + def answer_with_context(self, query: str, context: str) -> str: + return ProviderAnswerGenerator( + provider=self.provider, + no_answer_value=self.no_answer_value, + ).answer_with_context(query, context) +``` + +Modify `src/ranksmith/integrations/__init__.py`: + +```python +from ranksmith.integrations._answer_generator import ProviderAnswerGenerator +from ranksmith.integrations._azure_answer_generator import AzureAnswerGenerator +from ranksmith.integrations._lmstudio_provider import LMStudioModelProvider + +__all__ = [ + "AzureAnswerGenerator", + "LMStudioModelProvider", + "ProviderAnswerGenerator", +] +``` + +- [x] **Step 4: Run tests** + +```bash +uv run pytest tests/test_provider_answer_generator.py tests/test_azure_answer_generator.py -q +``` + +Expected: all tests pass. + +--- + +## Task 3: Generation CLI + +**Files:** +- Create: `scripts/generate_confidence_dataset.py` +- Create: `tests/fixtures/confidence_query_answerability_raw.jsonl` +- Create: `tests/fixtures/confidence_query_context_answerability_raw.jsonl` +- Test: `tests/test_generate_confidence_dataset_script.py` + +- [x] **Step 1: Add raw fixtures** + +Create `tests/fixtures/confidence_query_answerability_raw.jsonl`: + +```jsonl +{"id":"qa-1","query":"What is the capital of France?","gold_answer":"Paris","source":"fixture-general","group_id":"q1"} +{"id":"qa-2","query":"Who played Karen in Married to the Mob?","gold_answer":"Nancy Travis","source":"fixture-movie","group_id":"q2"} +``` + +Create `tests/fixtures/confidence_query_context_answerability_raw.jsonl`: + +```jsonl +{"id":"qc-1","query":"What is the capital of France?","context":"Paris is the capital and largest city of France.","gold_answer":"Paris","source":"fixture-general","group_id":"q1"} +{"id":"qc-2","query":"Who played Karen in Married to the Mob?","context":"Nancy Travis played Karen Lutnick in Married to the Mob.","gold_answer":"Nancy Travis","source":"fixture-movie","group_id":"q2"} +``` + +- [x] **Step 2: Write failing CLI tests** + +Create `tests/test_generate_confidence_dataset_script.py`: + +```python +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path + + +SCRIPT = Path("scripts/generate_confidence_dataset.py") + + +def _load_script() -> object: + spec = importlib.util.spec_from_file_location("generate_confidence_dataset", SCRIPT) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_parse_args_accepts_query_answerability() -> None: + module = _load_script() + + args = module.parse_args( + [ + "--task", + "query_answerability_confidence", + "--provider", + "lmstudio", + "--lmstudio-model", + "google/gemma-4-12b", + "--input", + "in.jsonl", + "--output", + "out.jsonl", + ] + ) + + assert args.task == "query_answerability_confidence" + assert args.lmstudio_model == "google/gemma-4-12b" + + +def test_generation_cli_runs_with_fake_provider(tmp_path: Path, monkeypatch) -> None: + module = _load_script() + input_path = tmp_path / "raw.jsonl" + output_path = tmp_path / "canonical.jsonl" + input_path.write_text( + '{"id":"1","query":"What is the capital of France?","gold_answer":"Paris"}\n', + encoding="utf-8", + ) + + class FakeProvider: + def __init__(self, **kwargs: object) -> None: + del kwargs + + def complete(self, request): + del request + from ranksmith.model import ModelResponse + + return ModelResponse(content='{"answer":"Paris"}') + + monkeypatch.setattr(module, "LMStudioModelProvider", FakeProvider) + + result = module.run( + module.parse_args( + [ + "--task", + "query_answerability_confidence", + "--provider", + "lmstudio", + "--lmstudio-model", + "google/gemma-4-12b", + "--input", + str(input_path), + "--output", + str(output_path), + "--overwrite", + ] + ) + ) + + rows = [json.loads(line) for line in output_path.read_text(encoding="utf-8").splitlines()] + assert result.generated_count == 1 + assert rows[0]["task_type"] == "query_answerability_confidence" + assert rows[0]["label"] == 1 +``` + +- [x] **Step 3: Implement CLI** + +Create `scripts/generate_confidence_dataset.py`: + +```python +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from ranksmith.confidence_generation import ( + QueryAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationConfig, + generate_query_answerability_confidence_dataset, + generate_query_context_answerability_confidence_dataset, +) +from ranksmith.integrations import LMStudioModelProvider + + +def main() -> None: + result = run(parse_args()) + print(json.dumps(result.__dict__ | {"output_path": str(result.output_path)}, sort_keys=True)) + + +def run(args: argparse.Namespace): + provider = LMStudioModelProvider( + base_url=args.lmstudio_base_url, + model=args.lmstudio_model, + api_key=args.lmstudio_api_key, + timeout=args.timeout, + max_tokens=args.lmstudio_max_tokens, + ) + if args.task == "query_answerability_confidence": + return generate_query_answerability_confidence_dataset( + QueryAnswerabilityGenerationConfig( + input_path=args.input, + output_path=args.output, + provider=provider, + overwrite=args.overwrite, + resume=args.resume, + max_items=args.max_items, + source=args.source, + ) + ) + if args.task == "query_context_answerability_confidence": + return generate_query_context_answerability_confidence_dataset( + QueryContextAnswerabilityGenerationConfig( + input_path=args.input, + output_path=args.output, + provider=provider, + overwrite=args.overwrite, + resume=args.resume, + max_items=args.max_items, + max_context_chars=args.max_context_chars, + source=args.source, + ) + ) + raise SystemExit(f"unsupported task: {args.task}") + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--task", choices=("query_answerability_confidence", "query_context_answerability_confidence"), required=True) + parser.add_argument("--provider", choices=("lmstudio",), required=True) + parser.add_argument("--input", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--overwrite", action="store_true") + parser.add_argument("--resume", action="store_true") + parser.add_argument("--max-items", type=int) + parser.add_argument("--source") + parser.add_argument("--max-context-chars", type=int, default=8000) + parser.add_argument("--timeout", type=float) + parser.add_argument("--lmstudio-base-url") + parser.add_argument("--lmstudio-model") + parser.add_argument("--lmstudio-api-key") + parser.add_argument("--lmstudio-max-tokens", type=int, default=128) + return parser.parse_args(argv) + + +if __name__ == "__main__": + main() +``` + +- [x] **Step 4: Run CLI tests** + +```bash +uv run pytest tests/test_generate_confidence_dataset_script.py -q +``` + +Expected: all tests pass. + +--- + +## Task 4: Training CLI + +**Files:** +- Create: `scripts/train_confidence_scorer.py` +- Test: `tests/test_train_confidence_scorer_script.py` + +- [x] **Step 1: Write failing CLI test** + +Create `tests/test_train_confidence_scorer_script.py`: + +```python +from __future__ import annotations + +import importlib.util +from pathlib import Path + + +SCRIPT = Path("scripts/train_confidence_scorer.py") + + +def _load_script() -> object: + spec = importlib.util.spec_from_file_location("train_confidence_scorer_script", SCRIPT) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_training_cli_builds_config(monkeypatch, tmp_path: Path) -> None: + module = _load_script() + captured = {} + + def fake_train(config): + captured["config"] = config + from ranksmith.confidence_training import ConfidenceTrainingResult + + return ConfidenceTrainingResult( + output_dir=Path(config.output_dir), + export_path=Path(config.export_path), + report_path=Path(config.output_dir) / "report.json", + metadata_path=Path(config.output_dir) / "metadata.json", + ) + + monkeypatch.setattr(module, "train_confidence_scorer", fake_train) + + module.run( + module.parse_args( + [ + "--task", + "query_answerability_confidence", + "--dataset", + str(tmp_path / "data.jsonl"), + "--output-dir", + str(tmp_path / "out"), + "--export-path", + str(tmp_path / "artifact.joblib"), + "--encoder-name", + "bert-base-uncased", + "--max-length", + "256", + ] + ) + ) + + config = captured["config"] + assert config.task_type == "query_answerability_confidence" + assert config.encoder_name == "bert-base-uncased" + assert config.max_length == 256 +``` + +- [x] **Step 2: Implement CLI** + +Create `scripts/train_confidence_scorer.py`: + +```python +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from ranksmith.confidence_training import ( + ConfidenceTrainingConfig, + train_confidence_scorer, +) + + +def main() -> None: + result = run(parse_args()) + print( + json.dumps( + { + "output_dir": str(result.output_dir), + "export_path": str(result.export_path), + "report_path": str(result.report_path), + "metadata_path": str(result.metadata_path), + }, + sort_keys=True, + ) + ) + + +def run(args: argparse.Namespace): + return train_confidence_scorer( + ConfidenceTrainingConfig( + task_type=args.task, + dataset_path=args.dataset, + output_dir=args.output_dir, + export_path=args.export_path, + encoder_name=args.encoder_name, + encoder_revision=args.encoder_revision, + tokenizer_name=args.tokenizer_name, + tokenizer_revision=args.tokenizer_revision, + cache_dir=str(args.cache_dir) if args.cache_dir is not None else None, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + seed=args.seed, + train_ratio=args.train_ratio, + valid_ratio=args.valid_ratio, + test_ratio=args.test_ratio, + ) + ) + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--task", choices=("query_answerability_confidence", "query_context_answerability_confidence"), required=True) + parser.add_argument("--dataset", type=Path, required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--export-path", type=Path, required=True) + parser.add_argument("--encoder-name", default="bert-base-uncased") + parser.add_argument("--encoder-revision") + parser.add_argument("--tokenizer-name") + parser.add_argument("--tokenizer-revision") + parser.add_argument("--cache-dir", type=Path) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--max-length", type=int, default=256) + parser.add_argument("--allow-truncation", action="store_true") + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--train-ratio", type=float, default=0.8) + parser.add_argument("--valid-ratio", type=float, default=0.1) + parser.add_argument("--test-ratio", type=float, default=0.1) + return parser.parse_args(argv) + + +if __name__ == "__main__": + main() +``` + +- [x] **Step 3: Run test** + +```bash +uv run pytest tests/test_train_confidence_scorer_script.py -q +``` + +Expected: all tests pass. + +--- + +## Task 5: Dataset Balance Report + +**Files:** +- Create: `src/ranksmith/confidence_training/_dataset_report.py` +- Create: `scripts/report_confidence_dataset.py` +- Test: `tests/test_confidence_training_dataset_report.py` + +- [x] **Step 1: Write failing report tests** + +Create `tests/test_confidence_training_dataset_report.py`: + +```python +from __future__ import annotations + +import json +from pathlib import Path + +from ranksmith.confidence_training._dataset_report import build_dataset_report + + +def test_build_dataset_report_counts_sources_and_groups(tmp_path: Path) -> None: + path = tmp_path / "data.jsonl" + path.write_text( + "\n".join( + [ + json.dumps({"id":"1","task_type":"query_answerability_confidence","query":"q","answer":"a","label":1,"source":"s1","group_id":"g1"}), + json.dumps({"id":"2","task_type":"query_answerability_confidence","query":"q","answer":"x","label":0,"source":"s1","group_id":"g1"}), + json.dumps({"id":"3","task_type":"query_answerability_confidence","query":"q","answer":"a","label":1,"source":"s2"}), + ] + ) + + "\n", + encoding="utf-8", + ) + + report = build_dataset_report(path, task_type="query_answerability_confidence") + + assert report["sample_count"] == 3 + assert report["positive_count"] == 2 + assert report["negative_count"] == 1 + assert report["missing_group_id_count"] == 1 + assert report["sources"]["s1"]["sample_count"] == 2 + assert report["sources"]["s1"]["positive_count"] == 1 +``` + +- [x] **Step 2: Implement report helper** + +Create `src/ranksmith/confidence_training/_dataset_report.py`: + +```python +from __future__ import annotations + +from collections import Counter, defaultdict +from pathlib import Path +from typing import Any + +from ranksmith.confidence import TaskType +from ranksmith.confidence_training._dataset import load_canonical_dataset + + +def build_dataset_report(path: str | Path, *, task_type: TaskType) -> dict[str, Any]: + samples = load_canonical_dataset(path, task_type=task_type) + positive_count = sum(sample.label for sample in samples) + source_counts: dict[str, Counter[str]] = defaultdict(Counter) + missing_source_count = 0 + missing_group_id_count = 0 + group_ids: set[str] = set() + + for sample in samples: + source = sample.source + if source is None: + missing_source_count += 1 + source = "__MISSING__" + if sample.group_id is None: + missing_group_id_count += 1 + else: + group_ids.add(sample.group_id) + source_counts[source]["sample_count"] += 1 + source_counts[source]["positive_count"] += sample.label + source_counts[source]["negative_count"] += 1 - sample.label + + return { + "task_type": task_type, + "sample_count": len(samples), + "positive_count": positive_count, + "negative_count": len(samples) - positive_count, + "positive_rate": _rate(positive_count, len(samples)), + "source_count": len(source_counts), + "group_count": len(group_ids), + "missing_source_count": missing_source_count, + "missing_group_id_count": missing_group_id_count, + "sources": { + source: { + "sample_count": counts["sample_count"], + "positive_count": counts["positive_count"], + "negative_count": counts["negative_count"], + "positive_rate": _rate(counts["positive_count"], counts["sample_count"]), + } + for source, counts in sorted(source_counts.items()) + }, + } + + +def _rate(count: int, total: int) -> float: + if total == 0: + return 0.0 + return count / total +``` + +- [x] **Step 3: Add CLI** + +Create `scripts/report_confidence_dataset.py`: + +```python +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from ranksmith.confidence_training._dataset_report import build_dataset_report + + +def main() -> None: + report = run(parse_args()) + print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)) + + +def run(args: argparse.Namespace) -> dict[str, object]: + return build_dataset_report(args.dataset, task_type=args.task) + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--task", choices=("query_answerability_confidence", "query_context_answerability_confidence"), required=True) + parser.add_argument("--dataset", type=Path, required=True) + return parser.parse_args(argv) + + +if __name__ == "__main__": + main() +``` + +- [x] **Step 4: Run report tests** + +```bash +uv run pytest tests/test_confidence_training_dataset_report.py -q +``` + +Expected: all tests pass. + +--- + +## Task 6: CBDR Benchmark LM Studio Runtime + +**Files:** +- Modify: `scripts/compare_reranking.py` +- Test: extend `tests/test_compare_reranking.py` + +- [x] **Step 1: Add failing compare test** + +Add to `tests/test_compare_reranking.py`: + +```python +def test_compare_cbdr_can_use_lmstudio_answer_provider(monkeypatch, tmp_path): + import scripts.compare_reranking as compare_reranking + from ranksmith._benchmark import BenchmarkCase, BenchmarkDocument + + created = {} + + class FakeLMStudioProvider: + def __init__(self, **kwargs): + created["lmstudio"] = kwargs + + class FakeProviderAnswerGenerator: + def __init__(self, *, provider, no_answer_value="__NO_ANSWER__"): + created["generator"] = provider + self.no_answer_value = no_answer_value + + class FakeCBDRStrategy: + @classmethod + def from_artifacts(cls, **kwargs): + created["strategy"] = kwargs + return cls() + + def rerank(self, *, query, documents, top_k=None): + del query, top_k + return [ + type("Result", (), {"document": document}) + for document in documents + ] + + monkeypatch.setattr("ranksmith.integrations.LMStudioModelProvider", FakeLMStudioProvider) + monkeypatch.setattr("ranksmith.integrations.ProviderAnswerGenerator", FakeProviderAnswerGenerator) + monkeypatch.setattr("ranksmith.strategies.CBDRStrategy", FakeCBDRStrategy) + + case = BenchmarkCase( + fixture_id="fixture", + dataset="dataset", + source="source", + license="license", + query_id="q1", + query="query", + documents=(BenchmarkDocument(id="d1", text="doc", title=""),), + qrels={"d1": 1}, + ) + + ranking = compare_reranking._rank_case( + case=case, + algorithm="cbdr", + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + cbdr_base_artifact=tmp_path / "base.joblib", + cbdr_context_artifact=tmp_path / "context.joblib", + cbdr_answer_provider="lmstudio", + lmstudio_model="google/gemma-4-12b", + ) + + assert ranking == ("d1",) + assert created["lmstudio"]["model"] == "google/gemma-4-12b" + assert created["strategy"]["answer_generator"].no_answer_value == "__NO_ANSWER__" +``` + +- [x] **Step 2: Modify compare runner** + +In `scripts/compare_reranking.py`: + +Add parser flags: + +```python +parser.add_argument("--cbdr-answer-provider", choices=("azure", "lmstudio"), default="azure") +parser.add_argument("--lmstudio-base-url") +parser.add_argument("--lmstudio-model") +parser.add_argument("--lmstudio-api-key") +parser.add_argument("--lmstudio-max-tokens", type=int, default=128) +``` + +Add `_rank_case(...)` parameters: + +```python +cbdr_answer_provider: str = "azure", +lmstudio_base_url: str | None = None, +lmstudio_model: str | None = None, +lmstudio_api_key: str | None = None, +lmstudio_max_tokens: int = 128, +``` + +Replace hardcoded CBDR generator: + +```python +if cbdr_answer_provider == "azure": + answer_generator = AzureAnswerGenerator.from_env(timeout=timeout) +elif cbdr_answer_provider == "lmstudio": + from ranksmith.integrations import LMStudioModelProvider, ProviderAnswerGenerator + + answer_generator = ProviderAnswerGenerator( + provider=LMStudioModelProvider( + base_url=lmstudio_base_url, + model=lmstudio_model, + api_key=lmstudio_api_key, + timeout=timeout, + max_tokens=lmstudio_max_tokens, + ) + ) +else: + raise SystemExit("--cbdr-answer-provider must be azure or lmstudio.") +``` + +Pass new args from the caller to `_rank_case(...)`. + +- [x] **Step 3: Run compare tests** + +```bash +uv run pytest tests/test_compare_reranking.py -q +``` + +Expected: all tests pass. + +--- + +## Task 7: Docs And Spec Checklist + +**Files:** +- Modify: `docs/specs/spec_lmstudio_confidence_training_pipeline.md` +- Modify: `README.md` +- Modify: `README.ko.md` +- Modify: `docs/wiki/02_architecture.md` + +- [x] **Step 1: Update spec checklist** + +Mark implemented items as `[x]` only after the corresponding tests pass. + +- [x] **Step 2: Update architecture wiki** + +Add under `Integrations`: + +```markdown +- `LMStudioModelProvider`: LM Studio OpenAI-compatible local model provider for confidence dataset generation and CBDR answer generation. +- `ProviderAnswerGenerator`: provider-agnostic sync answer helper used by Azure and LM Studio runtime paths. +``` + +Add under `Confidence`: + +```markdown +Confidence generation/training can be operated through local CLI scripts for query-only and query+context answerability scorer artifacts. The scripts are utility entry points and do not add a new reranking algorithm. +``` + +- [x] **Step 3: Update README and README.ko** + +Add a minimal LM Studio confidence pipeline section with: + +```bash +lms server start + +uv run python scripts/generate_confidence_dataset.py \ + --task query_answerability_confidence \ + --provider lmstudio \ + --lmstudio-model google/gemma-4-12b \ + --input runs/confidence/local/raw/query_answerability.jsonl \ + --output runs/confidence/local/canonical/query_answerability_confidence.jsonl \ + --resume +``` + +Do not add benchmark quality numbers. + +--- + +## Task 8: Verification + +**Files:** all touched files. + +- [x] **Step 1: Run targeted tests** + +```bash +uv run pytest \ + tests/test_lmstudio_provider.py \ + tests/test_provider_answer_generator.py \ + tests/test_azure_answer_generator.py \ + tests/test_generate_confidence_dataset_script.py \ + tests/test_train_confidence_scorer_script.py \ + tests/test_confidence_training_dataset_report.py \ + tests/test_compare_reranking.py \ + -q +``` + +Expected: all selected tests pass. + +- [x] **Step 2: Run full verification** + +```bash +./scripts/verify.sh +``` + +Expected: ruff, format check, mypy, pytest, and build pass. + +- [x] **Step 3: Optional live LM Studio smoke** + +Requires LM Studio server running and `google/gemma-4-12b` loaded. + +```bash +lms server status + +uv run python scripts/generate_confidence_dataset.py \ + --task query_answerability_confidence \ + --provider lmstudio \ + --lmstudio-model google/gemma-4-12b \ + --input tests/fixtures/confidence_query_answerability_raw.jsonl \ + --output /tmp/ranksmith-lmstudio-query-answerability.jsonl \ + --overwrite \ + --max-items 2 +``` + +Expected: canonical JSONL is written with strict `answer` values and labels. + +--- + +## Self-Review + +- Spec coverage: + - LM Studio provider: Task 1. + - `json_object -> json_schema`: Task 1. + - provider-agnostic answer generation and CBDR LM Studio runtime: Tasks 2 and 6. + - generation CLI: Task 3. + - training CLI: Task 4. + - dataset balance report: Task 5. + - docs/spec/wiki/README: Task 7. + - verification/live smoke: Task 8. + +- Known intentional limits: + - No automatic external dataset download. + - No async generation. + - No model fine-tuning. + - No benchmark quality numbers without real summary artifacts. diff --git a/docs/wiki/02_architecture.md b/docs/wiki/02_architecture.md index e5d1b51..fd1ac5d 100644 --- a/docs/wiki/02_architecture.md +++ b/docs/wiki/02_architecture.md @@ -26,13 +26,21 @@ src/ranksmith/ setwise.py tourrank.py acurank.py + confidence_gain.py + cbdr.py providers/ __init__.py # public provider exports azure.py # Azure OpenAI implementation + integrations/ + __init__.py # public runtime helper exports + azure_answer_generator.py + answer_generator.py + lmstudio_provider.py + validation.py ``` -외부 사용자는 root import 또는 `ranksmith.strategies`, `ranksmith.providers`의 public export를 사용한다. -strategies/, providers/ 하위 개별 모듈은 내부 구현으로 취급한다. +외부 사용자는 root import 또는 `ranksmith.strategies`, `ranksmith.providers`, `ranksmith.integrations`의 public export를 사용한다. +strategies/, providers/, integrations/ 하위 개별 모듈은 내부 구현으로 취급한다. ## ModelProvider 실제 SDK 호출은 Azure OpenAI만 구현한다. @@ -41,6 +49,22 @@ strategies/, providers/ 하위 개별 모듈은 내부 구현으로 취급한다 Provider는 `ModelRequest`를 받아 `ModelResponse`를 반환한다. Provider는 ranking 도메인 prompt의 의미를 알지 않는다. +## Integrations +`ranksmith.integrations`는 closed model runtime helper layer다. +Strategy나 Algorithm을 추가하지 않고, 기존 Strategy가 필요로 하는 외부 hook을 공식 조립 경로로 제공한다. + +현재 범위: +- `AzureAnswerGenerator`: Azure OpenAI JSON answer generation helper +- `ProviderAnswerGenerator`: `ModelProvider` 기반 sync JSON answer generation helper +- `LMStudioModelProvider`: LM Studio OpenAI-compatible runtime helper +- confidence generation과 같은 no-answer sentinel prompt contract +- root import가 아닌 `ranksmith.integrations` submodule export + +제외: +- async answer generation +- non-Azure hosted provider implementation +- scorer training + ## ModelClient Listwise model client call은 1-based ranking permutation을 담은 JSON 문자열을 반환한다. @@ -61,6 +85,8 @@ v1 공개 strategy: - `AsyncTourRankStrategy` - `AcuRankStrategy` - `AsyncAcuRankStrategy` +- `ConfidenceGainStrategy` +- `CBDRStrategy` 공식 확장 지점: - 새 reranking 방법은 새 Strategy 클래스로 추가한다. @@ -79,9 +105,11 @@ v1 지원 algorithm: - `setwise_heapsort` - `tourrank_r` - `acurank` +- `confidence_gain` +- `cbdr` 향후 algorithm 후보: -- `confidence` +- `Pointwise` ## Confidence `ranksmith.confidence`는 reranking Strategy나 Algorithm이 아니라, closed model output confidence를 계산하는 utility layer다. @@ -91,6 +119,10 @@ v1 지원 algorithm: - `structural-v1` 70차원 feature extraction - 학습된 scorer artifact 기반 single-item sync confidence inference - bounded batch sync confidence inference +- `answer_confidence` +- `judgment_confidence` +- `query_answerability_confidence` +- `query_context_answerability_confidence` - root import가 아닌 `ranksmith.confidence` submodule export `score_batch(..., max_workers>1)`은 같은 encoder/scorer instance를 worker thread들이 공유하므로, concurrent call에 안전한 backend에서만 사용한다. 기본값은 안정성을 위해 `max_workers=1`이다. @@ -100,6 +132,13 @@ v1 지원 algorithm: - async inference - reranking Strategy +`ConfidenceGainStrategy`는 confidence utility layer 자체가 아니라, `ranksmith.confidence`의 query-only 및 query+context answerability scorer를 소비하는 별도 sync Strategy다. +`Conf(Q+C)-Conf(Q)`를 계산해 confidence gain 내림차순으로 문서를 정렬한다. + +`CBDRStrategy`는 `Conf(Q)`가 `skip_threshold` 이상이면 context reranking을 skip하고 original order를 보존한다. +`Conf(Q)`가 threshold보다 낮으면 `Conf(Q+C)-Conf(Q)` confidence gain으로 문서를 정렬한다. +true pre-retrieval skip, retriever integration, async CBDR은 구현하지 않는다. + `ranksmith.confidence_training`은 Phase 1 compatible scorer artifact를 만들기 위한 별도 training utility layer다. 현재 범위: @@ -109,11 +148,12 @@ v1 지원 algorithm: - LightGBM binary classifier training - validation split 기반 sigmoid calibration - Phase 1 `ScorerMetadata` compatible artifact export +- CBDR용 answerability task CLI wrapper +- source/group balance dataset report helper 제외: - 외부 benchmark/source adapter - label 생성 -- CLI - reranking Strategy 또는 Algorithm `ranksmith.confidence_generation`은 closed model output을 생성해 confidence training canonical JSONL로 저장하는 utility layer다. @@ -121,13 +161,15 @@ v1 지원 algorithm: 현재 범위: - answer-oriented raw JSONL -> `answer_confidence` canonical JSONL - relevance-oriented raw JSONL -> `judgment_confidence` canonical JSONL +- query-only answerability raw JSONL -> `query_answerability_confidence` canonical JSONL +- query+context answerability raw JSONL -> `query_context_answerability_confidence` canonical JSONL - sync closed model call - resume 가능한 JSONL output +- CBDR용 answerability task CLI wrapper 제외: - async generation - dataset adapter -- CLI - runtime reranking Strategy 또는 Algorithm ## LLM 응답 계약 diff --git a/docs/wiki/04_references_index.md b/docs/wiki/04_references_index.md index 368e4bd..9f9b6e7 100644 --- a/docs/wiki/04_references_index.md +++ b/docs/wiki/04_references_index.md @@ -7,6 +7,7 @@ - [AcuRank](references/acurank.md): Paper + Repo / Bayesian, Uncertainty-aware Adaptive Reranking / 요약 완료, 구현 완료 - [A Setwise Approach for Effective and Highly Efficient Zero-shot Ranking with Large Language Models](references/setwise_ranking_prompting.md): Paper / Setwise Reranking, Heapsort-based Selection Reranking / 요약 완료, 구현 완료 - [Trust in One Round: Confidence Estimation for Large Language Models via Structural Signals](references/structural_confidence.md): Paper / Black-box confidence, proxy hidden-state trajectory / 요약 완료, Phase 1 inference spec 작성 +- [Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking](references/parametric_post_retrieval_confidence.md): Paper / Confidence gain, post-retrieval reranking, CBDR / 요약 완료, spec 작성 ## 처리 대기 Reference - [Attention in Large Language Models Yields Efficient Zero-Shot Re-Rankers](references/Attention%20in%20Large%20Language%20Models%20Yields%20Efficient%20Zero-Shot%20Re-Rankers.pdf): Paper / Attention-based reranking, zero-shot reranking / 요약 대기 diff --git a/docs/wiki/references/Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking b/docs/wiki/references/Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking new file mode 100644 index 0000000..6f8fbec --- /dev/null +++ b/docs/wiki/references/Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking @@ -0,0 +1,8328 @@ +%PDF-1.7 +% +1 0 obj +<< /Lang (en) /Metadata 3 0 R /Names 4 0 R /OpenAction 5 0 R /Outlines 6 0 R /PageMode /UseOutlines /Pages 7 0 R /Type /Catalog /ViewerPreferences << /DisplayDocTitle true >> >> +endobj +2 0 obj +<< /Author (Haoxiang Jin; Ronghan Li; Zixiang Lu; Qiguang Miao) /CreationDate (D:20250910004624+00'00') /Creator (arXiv GenPDF \(tex2pdf:\)) /DOI (https://doi.org/10.48550/arXiv.2509.06472) /Keywords (Knowledge Boundary, Evaluation, Large Language Models, Retrieval-Augmented Generation, Reranker, Generator) /License (http://arxiv.org/licenses/nonexclusive-distrib/1.0/) /ModDate (D:20250910004624+00'00') /PTEX.Fullbanner (This is pdfTeX, Version 3.141592653-2.6-1.40.28 \(TeX Live 2025\) kpathsea version 6.4.1) /Producer (pikepdf 8.15.1) /Subject (- Do Not Use This Code -> Generate the Correct Terms for Your Paper.Generate the Correct Terms for Your Paper.Generate the Correct Terms for Your Paper.Generate the Correct Terms for Your Paper.) /Title (Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking) /Trapped /False /arXivID (https://arxiv.org/abs/2509.06472v2) >> +endobj +3 0 obj +<< /Subtype /XML /Type /Metadata /Length 18226 >> +stream + + + + + + + + Adobe PDF Schema + pdf + http://ns.adobe.com/pdf/1.3/ + + + + Trapped + Text + internal + Indication if the document has been modified to include trapping information + + + + + + XMP Media Management Schema + xmpMM + http://ns.adobe.com/xap/1.0/mm/ + + + + DocumentID + URI + internal + UUID based identifier for all versions and renditions of a document + + + InstanceID + URI + internal + UUID based identifier for specific incarnation of a document + + + VersionID + Text + internal + Document version identifier + + + RenditionClass + RenditionClass + internal + The manner in which a document is rendered + + + + + + IPTC Core Schema + Iptc4xmpCore + http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/ + + + + CreatorContactInfo + ContactInfo + external + Document creator's contact information + + + + + + + ContactInfo + http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/ + Iptc4xmpCore + Basic set of information to get in contact with a person + + + + CiAdrCity + Text + Contact information city + + + CiAdrCtry + Text + Contact information country + + + CiAdrExtadr + Text + Contact information address + + + CiAdrPcode + Text + Contact information local postal code + + + CiAdrRegion + Text + Contact information regional information such as state or province + + + CiEmailWork + Text + Contact information email address(es) + + + CiTelWork + Text + Contact information telephone number(s) + + + CiUrlWork + Text + Contact information Web URL(s) + + + + + + + + + PRISM Basic Metadata + prism + http://prismstandard.org/namespaces/basic/3.0/ + + + + complianceProfile + Text + internal + PRISM specification compliance profile to which this document adheres + + + publicationName + Text + external + Publication name + + + aggregationType + Text + external + Publication type + + + bookEdition + Text + external + Edition of the book in which the document was published + + + volume + Text + external + Publication volume number + + + number + Text + external + Publication issue number within a volume + + + pageRange + Text + external + Page range for the document within the print version of its publication + + + issn + Text + external + ISSN for the printed publication in which the document was published + + + eIssn + Text + external + ISSN for the electronic publication in which the document was published + + + isbn + Text + external + ISBN for the publication in which the document was published + + + doi + Text + external + Digital Object Identifier for the document + + + url + URL + external + URL at which the document can be found + + + byteCount + Integer + internal + Approximate file size in octets + + + pageCount + Integer + internal + Number of pages in the print version of the document + + + subtitle + Text + external + Document's subtitle + + + + + + + pikepdf 8.15.1 + Knowledge Boundary, Evaluation, Large Language Models, Retrieval-Augmented Generation, Reranker, Generator + 1.7 + application/pdf + + Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking + + + - Do Not Use This Code -> Generate the Correct Terms for Your Paper.Generate the Correct Terms for Your Paper.Generate the Correct Terms for Your Paper.Generate the Correct Terms for Your Paper. + + + arXiv + + + 2025-09-10T00:46:24Z + + + + + Text + + + + Haoxiang JinRonghan LiZixiang LuQiguang Miao + + cs.IR + Rethinking_LLM_Parametric_Knowledge_as_Post-retrieval_Confidence_for_Dynamic_Retrieval_and_Reranking.tex + + + en + + + https://arxiv.org/abs/2509.06472v2 + 2025-09-10T00:46:24Z + 2025-09-10T00:46:24Z + 2025-09-10T00:46:31.082990+00:00 + arXiv GenPDF (tex2pdf:) + uuid:efd1dc90-00a8-4d13-a467-f7f75f2b1333 + uuid:0541dcef-2000-45f2-a75f-7aea888262b1 + 1 + default + + Shaanxi + China + jinhx@stu.xidian.edu.cn + + three + Proceedings of Make sure to enter the correct conference title from your rights confirmation email (Conference acronym 'XX) + book + 1 + 1 + XXXXXXX.XXXXXXX + 9 + 9 + + http://arxiv.org/licenses/nonexclusive-distrib/1.0/ + + + + +endstream +endobj +4 0 obj +<< /Dests 8 0 R >> +endobj +5 0 obj +<< /D [ 9 0 R /Fit ] /S /GoTo >> +endobj +6 0 obj +<< /Count 8 /First 10 0 R /Last 11 0 R /Type /Outlines >> +endobj +7 0 obj +<< /Count 9 /Kids [ 12 0 R 13 0 R ] /Type /Pages >> +endobj +8 0 obj +<< /Kids [ 14 0 R 15 0 R 16 0 R ] /Limits [ (Doc-Start) (table.caption.9) ] >> +endobj +9 0 obj +<< /Annots [ 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R 35 0 R ] /Contents [ 36 0 R 37 0 R 38 0 R 39 0 R ] /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources 40 0 R /Type /Page >> +endobj +10 0 obj +<< /A 41 0 R /Next 42 0 R /Parent 6 0 R /Title 43 0 R >> +endobj +11 0 obj +<< /A 44 0 R /Parent 6 0 R /Prev 45 0 R /Title 46 0 R >> +endobj +12 0 obj +<< /Count 6 /Kids [ 9 0 R 47 0 R 48 0 R 49 0 R 50 0 R 51 0 R ] /Parent 7 0 R /Type /Pages >> +endobj +13 0 obj +<< /Count 3 /Kids [ 52 0 R 53 0 R 54 0 R ] /Parent 7 0 R /Type /Pages >> +endobj +14 0 obj +<< /Kids [ 55 0 R 56 0 R 57 0 R 58 0 R 59 0 R 60 0 R ] /Limits [ (Doc-Start) (cite.yao2024seakr) ] >> +endobj +15 0 obj +<< /Kids [ 61 0 R 62 0 R 63 0 R 64 0 R 65 0 R 66 0 R ] /Limits [ (cite.yin2023large) (section.6) ] >> +endobj +16 0 obj +<< /Kids [ 67 0 R 68 0 R 69 0 R 70 0 R 71 0 R ] /Limits [ (subsection.2.1) (table.caption.9) ] >> +endobj +17 0 obj +<< /A << /S /URI /Type /Action /URI (https://doi.org/XXXXXXX.XXXXXXX) >> /Border [ 0 0 0 ] /C [ 0 1 1 ] /H /I /Rect [ 52.802 81.265 163.733 90.44 ] /Subtype /Link /Type /Annot >> +endobj +18 0 obj +<< /A << /D (page.9) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 405.379 383.556 411.07 392.905 ] /Subtype /Link /Type /Annot >> +endobj +19 0 obj +<< /A << /S /URI /Type /Action /URI (https://doi.org/XXXXXXX.XXXXXXX) >> /Border [ 0 0 0 ] /C [ 0 1 1 ] /H /I /Rect [ 433.21 383.556 559.455 392.905 ] /Subtype /Link /Type /Annot >> +endobj +20 0 obj +<< /A << /D (cite.zhao2023survey) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 412.821 324.73 418.983 332.277 ] /Subtype /Link /Type /Annot >> +endobj +21 0 obj +<< /A << /D (cite.hui2024qwen2) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 422.003 324.649 428.165 332.277 ] /Subtype /Link /Type /Annot >> +endobj +22 0 obj +<< /A << /D (cite.ji2023towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 340.119 291.853 346.281 299.4 ] /Subtype /Link /Type /Annot >> +endobj +23 0 obj +<< /A << /D (cite.martino2023knowledge) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 348.498 291.772 354.66 299.4 ] /Subtype /Link /Type /Annot >> +endobj +24 0 obj +<< /A << /D (cite.izacard2020leveraging) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 505.697 280.813 511.859 288.441 ] /Subtype /Link /Type /Annot >> +endobj +25 0 obj +<< /A << /D (cite.lewis2020retrieval) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 514.063 280.813 520.225 288.441 ] /Subtype /Link /Type /Annot >> +endobj +26 0 obj +<< /A << /D (cite.yoran2023making) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 373.082 226.019 379.244 233.647 ] /Subtype /Link /Type /Annot >> +endobj +27 0 obj +<< /A << /D (cite.cuconasu2024power) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 382.136 226.019 392.467 233.647 ] /Subtype /Link /Type /Annot >> +endobj +28 0 obj +<< /A << /D (cite.ji2023survey) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 466.017 149.387 476.348 156.934 ] /Subtype /Link /Type /Annot >> +endobj +29 0 obj +<< /A << /D (cite.yin2024benchmarking) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 479.2 149.307 489.532 156.934 ] /Subtype /Link /Type /Annot >> +endobj +30 0 obj +<< /A << /D (cite.wei2022chain) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 362.476 127.469 372.807 135.017 ] /Subtype /Link /Type /Annot >> +endobj +31 0 obj +<< /A << /D (cite.ren2023investigating) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 481.341 127.389 491.672 135.017 ] /Subtype /Link /Type /Annot >> +endobj +32 0 obj +<< /A << /D (cite.yin2024benchmarking) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 405.548 94.512 415.879 102.14 ] /Subtype /Link /Type /Annot >> +endobj +33 0 obj +<< /A << /D (cite.li2024knowledge) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 418.092 94.512 428.424 102.14 ] /Subtype /Link /Type /Annot >> +endobj +34 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 431.3 94.512 441.632 102.14 ] /Subtype /Link /Type /Annot >> +endobj +35 0 obj +<< /A << /S /URI /URI (https://arxiv.org/abs/2509.06472v2) >> /BS << /W 0 >> /NM (fitz-L0) /Rect [ 12 230.46002 32 561.54 ] /Subtype /Link >> +endobj +36 0 obj +<< /Length 10 /Filter /FlateDecode >> +stream +x+| +endstream +endobj +37 0 obj +<< /Filter /FlateDecode /Length 5218 >> +stream +x;iƕ+zŜ`b/ږ-'[eeaA9tm}UXBW|_~@vnXeH8ܼmz/PVe}ͷ}dkʜ_WM=mf̮n$(`/nXil:Ui g>rWTlrW7Vcܩ/ 5]* jbfʹ +]e%C85tc)p(pVPGTyjDR-w>å^D#燪>Ǜ0nyM I¹x*hڲ{YT,!˪l?:Qa::|zVŮWy B$B\u?LYHYJ2Ï5p7*R ӏ+'87JSU$_Fx?Bܩ'ñC5In7q2#}?  +TEANUl u9ޟ? "h_~Jܚ QMY$gۮn4H7>jKTd: ೂ%cˣ?_3껮xp_T:Y +8޵b{Gڢ% +.<V!;JXN5|c.2Pv/ZZxԤ$gd~w(@=(|B +- ÀyM7+n`sd:ޘߟs⼯m"/iW|Qv`.Z"_ +{p$W:]g|@wW&t)(OhM7>{iB/E ggCF'T@zp( +t jR|ײ||{;CoW9wLXFS9ak=q#tY'*1)I̅X,i۟{^@<"+c4ꁯpz ? nQ~r&EFr/Q%)lWw(w;0v= +w8 ->0cs\ TmI.Hp&()^%C9µ6#;(evpCЁ}:tCj4J H$|#B<#7?̵ǎB"I~fB@8㑹dL=Y<61?l0uvs F|:[&ʷ"l)&嶐~?3'. +ej~ovxE7KQ~9c˜v}El`iX'VpcuQʎEܔ5_e9iܦ +(땗7x#"ٱF .DZ%=>i+AŌoABvkzZHD l?pC-`R-i̖z/%oy) )G`3f. tv9ID5`ͭ[PR/W T),`0Lo {DeԆؠ'ٍ6AȒgD0A%l4%ګ,S1#9wʼnD.Y؁:M>"L^8p?YNX,^! 1f'9?X~m1x'0EvpmqFhHIS^t"cAXИHb£K+, ]3an]*C,RJ\ +>r1clED•_9siy+O'GƱE|]Ǐ-;cR]`zѷkNՑUpųc2X9 HB>$~ٍ635''wh:X9.hD7Qlc O.|'K5Hp}ov0e`f6Iy9 sԂJMBo@*C:i$j2)9&u W#[& m]ĻF$ \d7$d:m:s:܁(Oo[])` g(e)YށȝC,i \l؃Y]k[Wӝw +c +bv&pbP'svtR G  -y&#t2Cz.tfI oD\b1$ɪ_{D|Xp2;t4cN)h,ͦBlU=TSQF0V:\DžVȀɕ Pz_cMC]գgC=+eZ9]7hǐ~ct3ሐϗCoy -DO͹=&$C 8[WiM˝u3_:$؃v1t3::9ԅ178KdAU>u2*2,b2@($GƧfI^F(D̘x:d rQ6Ig,zuwH`< x֧D@d1GCq0y +/ ~GˇЛMU;l;oPvy)rQW_XzkCl@SS?r;aiuӧ7|)] MCdOԝ1™x+flvhj5XNֿ)!Pv3O'uEX(j- \ڇD v4I/sdJ[_{k/C &6h6Y:l/d4Q!z\fIvZ2ͩHӄ1{A/4yAag#j{]C/=+NI2/NK4gKJ;j|PiN`CO-XI2?l +VP| )}lS`~>0BbdQXʧTf]R? $9"VxKo,j-JxlSr X֎\R|D[_ڷB[v% Rp4p2h@!!F/JTdL`Ry9l2_lw6$62˹dAʩm0bĜ֞BNJ1e]Q3u +I YZA[d,NZ1G>B_(%H+Ă+dwG(8cQ4 t.:JP.R!g0p5I6Jt2K/Oxbg|/R@l kSʼng6&J&n78 :h7)JOLb%w f8"7^9xc֏9|x#z-9oBӊ'[V*&u|RZH{qPqQ/꙰( ~B JR}޾b/Mj#9o @WcU+:"5P ْ6*Q#UOn0xQi$f; +N1 ^bu4W0fU^g)TÄ(n]pkM幎#=_ϸz>_>9qYN$ӹ[n< +aWlL +E)^d0CS“hQ"˩cJ +Y2K"AZ_hP0ϻt+_4^f.wKō]Eu7͝EZ߈X+y?q0#eONYbHj~دC U g)ﶘԅ]zxy`ϖh{uyxGWݖM9)r1tLnĐxq~xqM} +@]y6Ԋ]{5ſ x;] 4M=qDalLl +0na~]>0iW\ ~GbTT| JbxhX&mY[ạLJCٹ!3T`ા\B(u:rq:$J@M.B`&|r#o6R@|N.@fdʍ/&"ksqUXyz6ɟ;[ +z."b%9S +!5B:㔤zŽSH?@1+/_?'ԉv +endstream +endobj +38 0 obj +<< /Length 11 /Filter /FlateDecode >> +stream +x +f +endstream +endobj +39 0 obj +<< /Filter /FlateDecode /Length 141 >> +stream +xE +1 D@4iR AKoa]PEUA27CXe > /ProcSet [ /PDF /Text ] >> +endobj +41 0 obj +<< /D (section*.1) /S /GoTo >> +endobj +42 0 obj +<< /A 79 0 R /Next 80 0 R /Parent 6 0 R /Prev 10 0 R /Title 81 0 R >> +endobj +43 0 obj + +endobj +44 0 obj +<< /D (section*.12) /S /GoTo >> +endobj +45 0 obj +<< /A 82 0 R /Next 11 0 R /Parent 6 0 R /Prev 83 0 R /Title 84 0 R >> +endobj +46 0 obj + +endobj +47 0 obj +<< /Annots [ 85 0 R 86 0 R 87 0 R 88 0 R 89 0 R 90 0 R 91 0 R 92 0 R 93 0 R 94 0 R 95 0 R 96 0 R 97 0 R 98 0 R 99 0 R 100 0 R 101 0 R ] /Contents 102 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources 103 0 R /Type /Page >> +endobj +48 0 obj +<< /Annots [ 104 0 R 105 0 R 106 0 R 107 0 R 108 0 R 109 0 R 110 0 R 111 0 R 112 0 R 113 0 R 114 0 R 115 0 R 116 0 R 117 0 R 118 0 R 119 0 R 120 0 R 121 0 R 122 0 R ] /Contents 123 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources 124 0 R /Type /Page >> +endobj +49 0 obj +<< /Annots [ 125 0 R 126 0 R 127 0 R 128 0 R 129 0 R ] /Contents 130 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources 131 0 R /Type /Page >> +endobj +50 0 obj +<< /Annots [ 132 0 R 133 0 R 134 0 R 135 0 R 136 0 R ] /Contents 137 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources 138 0 R /Type /Page >> +endobj +51 0 obj +<< /Annots [ 139 0 R 140 0 R 141 0 R 142 0 R 143 0 R 144 0 R ] /Contents 145 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources 146 0 R /Type /Page >> +endobj +52 0 obj +<< /Annots [ 147 0 R 148 0 R 149 0 R ] /Contents 150 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources 151 0 R /Type /Page >> +endobj +53 0 obj +<< /Annots [ 152 0 R 153 0 R ] /Contents 154 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources 155 0 R /Type /Page >> +endobj +54 0 obj +<< /Contents 156 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources 157 0 R /Type /Page >> +endobj +55 0 obj +<< /Limits [ (Doc-Start) (cite.chen2024inside) ] /Names [ (Doc-Start) 158 0 R (Hfootnote.1) 159 0 R (cite.47761) 160 0 R (cite.azaria2023internal) 161 0 R (cite.brown2024large) 162 0 R (cite.chen2024inside) 163 0 R ] >> +endobj +56 0 obj +<< /Limits [ (cite.cong2024query) (cite.fang2024enhancing) ] /Names [ (cite.cong2024query) 164 0 R (cite.cuconasu2024power) 165 0 R (cite.dong2023statistical) 166 0 R (cite.dong2025understand) 167 0 R (cite.dubey2024llama) 168 0 R (cite.fang2024enhancing) 169 0 R ] >> +endobj +57 0 obj +<< /Limits [ (cite.feng2025improving) (cite.jia2024bridging) ] /Names [ (cite.feng2025improving) 170 0 R (cite.hui2024qwen2) 171 0 R (cite.izacard2020leveraging) 172 0 R (cite.ji2023survey) 173 0 R (cite.ji2023towards) 174 0 R (cite.jia2024bridging) 175 0 R ] >> +endobj +58 0 obj +<< /Limits [ (cite.lewis2020retrieval) (cite.ni2024large) ] /Names [ (cite.lewis2020retrieval) 176 0 R (cite.li2024knowledge) 177 0 R (cite.longjohn2025statistical) 178 0 R (cite.marina2025llm) 179 0 R (cite.martino2023knowledge) 180 0 R (cite.ni2024large) 181 0 R ] >> +endobj +59 0 obj +<< /Limits [ (cite.ni2024llms) (cite.skean2025layer) ] /Names [ (cite.ni2024llms) 182 0 R (cite.ni2025towards) 183 0 R (cite.qwen2.5) 184 0 R (cite.ren2023investigating) 185 0 R (cite.shi2023replug) 186 0 R (cite.skean2025layer) 187 0 R ] >> +endobj +60 0 obj +<< /Limits [ (cite.su2024unsupervised) (cite.yao2024seakr) ] /Names [ (cite.su2024unsupervised) 188 0 R (cite.sun2025divide) 189 0 R (cite.touvron2023llama) 190 0 R (cite.wei2022chain) 191 0 R (cite.yang2018hotpotqa) 192 0 R (cite.yao2024seakr) 193 0 R ] >> +endobj +61 0 obj +<< /Limits [ (cite.yin2023large) (cite.zheng2025enhancing) ] /Names [ (cite.yin2023large) 194 0 R (cite.yin2024benchmarking) 195 0 R (cite.yoran2023making) 196 0 R (cite.zhang2025reasoning) 197 0 R (cite.zhao2023survey) 198 0 R (cite.zheng2025enhancing) 199 0 R ] >> +endobj +62 0 obj +<< /Limits [ (equation.1) (equation.6) ] /Names [ (equation.1) 200 0 R (equation.2) 201 0 R (equation.3) 202 0 R (equation.4) 203 0 R (equation.5) 204 0 R (equation.6) 205 0 R ] >> +endobj +63 0 obj +<< /Limits [ (figure.caption.5) (page.3) ] /Names [ (figure.caption.5) 206 0 R (figure.caption.6) 207 0 R (figure.caption.7) 208 0 R (page.1) 209 0 R (page.2) 210 0 R (page.3) 211 0 R ] >> +endobj +64 0 obj +<< /Limits [ (page.4) (page.9) ] /Names [ (page.4) 212 0 R (page.5) 213 0 R (page.6) 214 0 R (page.7) 215 0 R (page.8) 216 0 R (page.9) 217 0 R ] >> +endobj +65 0 obj +<< /Limits [ (section*.1) (section*.4) ] /Names [ (section*.1) 218 0 R (section*.11) 219 0 R (section*.12) 220 0 R (section*.2) 221 0 R (section*.3) 222 0 R (section*.4) 223 0 R ] >> +endobj +66 0 obj +<< /Limits [ (section.1) (section.6) ] /Names [ (section.1) 224 0 R (section.2) 225 0 R (section.3) 226 0 R (section.4) 227 0 R (section.5) 228 0 R (section.6) 229 0 R ] >> +endobj +67 0 obj +<< /Limits [ (subsection.2.1) (subsection.3.3) ] /Names [ (subsection.2.1) 230 0 R (subsection.2.2) 231 0 R (subsection.2.3) 232 0 R (subsection.3.1) 233 0 R (subsection.3.2) 234 0 R (subsection.3.3) 235 0 R ] >> +endobj +68 0 obj +<< /Limits [ (subsection.3.4) (subsection.5.3) ] /Names [ (subsection.3.4) 236 0 R (subsection.4.1) 237 0 R (subsection.4.2) 238 0 R (subsection.5.1) 239 0 R (subsection.5.2) 240 0 R (subsection.5.3) 241 0 R ] >> +endobj +69 0 obj +<< /Limits [ (subsection.5.4) (subsubsection.4.1.2) ] /Names [ (subsection.5.4) 242 0 R (subsubsection.3.1.1) 243 0 R (subsubsection.3.2.1) 244 0 R (subsubsection.3.2.2) 245 0 R (subsubsection.4.1.1) 246 0 R (subsubsection.4.1.2) 247 0 R ] >> +endobj +70 0 obj +<< /Limits [ (subsubsection.4.1.3) (subsubsection.4.2.3) ] /Names [ (subsubsection.4.1.3) 248 0 R (subsubsection.4.1.4) 249 0 R (subsubsection.4.1.5) 250 0 R (subsubsection.4.2.1) 251 0 R (subsubsection.4.2.2) 252 0 R (subsubsection.4.2.3) 253 0 R ] >> +endobj +71 0 obj +<< /Limits [ (table.caption.10) (table.caption.9) ] /Names [ (table.caption.10) 254 0 R (table.caption.8) 255 0 R (table.caption.9) 256 0 R ] >> +endobj +72 0 obj +<< /BaseFont /XMMIKB+LinBiolinumTB /Encoding 257 0 R /FirstChar 45 /FontDescriptor 258 0 R /LastChar 121 /Subtype /Type1 /ToUnicode 259 0 R /Type /Font /Widths 260 0 R >> +endobj +73 0 obj +<< /BaseFont /IHNHAB+LinLibertineT /Encoding 261 0 R /FirstChar 21 /FontDescriptor 262 0 R /LastChar 252 /Subtype /Type1 /ToUnicode 263 0 R /Type /Font /Widths 264 0 R >> +endobj +74 0 obj +<< /BaseFont /IHNHAB+LinLibertineT /Encoding 265 0 R /FirstChar 42 /FontDescriptor 262 0 R /LastChar 169 /Subtype /Type1 /ToUnicode 266 0 R /Type /Font /Widths 267 0 R >> +endobj +75 0 obj +<< /BaseFont /BPBAOH+LinLibertineTB /Encoding 268 0 R /FirstChar 27 /FontDescriptor 269 0 R /LastChar 122 /Subtype /Type1 /ToUnicode 270 0 R /Type /Font /Widths 271 0 R >> +endobj +76 0 obj +<< /BaseFont /ZWLALW+txsys /FirstChar 0 /FontDescriptor 272 0 R /LastChar 188 /Subtype /Type1 /ToUnicode 273 0 R /Type /Font /Widths 274 0 R >> +endobj +77 0 obj +<< /BaseFont /VZMBFR+LinLibertineTI /Encoding 275 0 R /FirstChar 27 /FontDescriptor 276 0 R /LastChar 121 /Subtype /Type1 /ToUnicode 277 0 R /Type /Font /Widths 278 0 R >> +endobj +78 0 obj +<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> +endobj +79 0 obj +<< /D (section.1) /S /GoTo >> +endobj +80 0 obj +<< /A 279 0 R /Count -3 /First 280 0 R /Last 281 0 R /Next 282 0 R /Parent 6 0 R /Prev 42 0 R /Title 283 0 R >> +endobj +81 0 obj + +endobj +82 0 obj +<< /D (section.6) /S /GoTo >> +endobj +83 0 obj +<< /A 284 0 R /Count -4 /First 285 0 R /Last 286 0 R /Next 45 0 R /Parent 6 0 R /Prev 287 0 R /Title 288 0 R >> +endobj +84 0 obj + +endobj +85 0 obj +<< /A << /D (figure.caption.5) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 236.66 509.515 247.817 521.313 ] /Subtype /Link /Type /Annot >> +endobj +86 0 obj +<< /A << /D (figure.caption.5) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 259.873 476.639 271.514 487.165 ] /Subtype /Link /Type /Annot >> +endobj +87 0 obj +<< /A << /D (cite.sun2025divide) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 280.874 417.598 291.206 425.226 ] /Subtype /Link /Type /Annot >> +endobj +88 0 obj +<< /A << /D (cite.yin2023large) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 55.949 351.845 66.281 359.473 ] /Subtype /Link /Type /Annot >> +endobj +89 0 obj +<< /A << /D (cite.ni2024large) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 69.133 351.926 79.464 359.473 ] /Subtype /Link /Type /Annot >> +endobj +90 0 obj +<< /A << /D (cite.brown2024large) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 90.429 329.927 100.761 337.555 ] /Subtype /Link /Type /Annot >> +endobj +91 0 obj +<< /A << /D (cite.longjohn2025statistical) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 103.105 330.008 113.437 337.555 ] /Subtype /Link /Type /Annot >> +endobj +92 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 200.496 308.009 210.827 315.637 ] /Subtype /Link /Type /Annot >> +endobj +93 0 obj +<< /A << /D (cite.su2024unsupervised) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 213.36 308.009 223.691 315.637 ] /Subtype /Link /Type /Annot >> +endobj +94 0 obj +<< /A << /D (cite.chen2024inside) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 226.224 308.009 236.555 315.637 ] /Subtype /Link /Type /Annot >> +endobj +95 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 147.639 242.256 157.97 249.884 ] /Subtype /Link /Type /Annot >> +endobj +96 0 obj +<< /A << /D (cite.su2024unsupervised) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 160.192 242.256 170.523 249.884 ] /Subtype /Link /Type /Annot >> +endobj +97 0 obj +<< /A << /D (cite.chen2024inside) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 172.745 242.256 183.076 249.884 ] /Subtype /Link /Type /Annot >> +endobj +98 0 obj +<< /A << /D (figure.caption.5) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 87.471 141.613 97.623 151.881 ] /Subtype /Link /Type /Annot >> +endobj +99 0 obj +<< /A << /D (figure.caption.5) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 234.054 86.818 244.525 97.087 ] /Subtype /Link /Type /Annot >> +endobj +100 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 396.022 406.64 406.353 414.267 ] /Subtype /Link /Type /Annot >> +endobj +101 0 obj +<< /A << /D (cite.yin2024benchmarking) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 408.202 105.471 418.533 113.099 ] /Subtype /Link /Type /Annot >> +endobj +102 0 obj +<< /Filter /FlateDecode /Length 4342 >> +stream +xڝ[[۶~K'<K5ӦMqH9"iw t<@\|qmw~e,,ؼ;l8b$xn1x=Pu:jCg#4'7{(`x~?IW'X ysSG^޿ОM&y"qf m7ۥ/v菧K:/%8z^F:K$aTK:,rucS8ەi7JPGzWˇyJA!(4)Bf׽*IddVtU: +3_uj{#Yňs`A <Pg#J + +zե>]_0]>6ٺΣ0+{O8N=KU\n_3.O/UVw|ڹyشL +a(ҫǍ$]k~ f*mo;ThDMp ՙ3I:x:]xYHM'DԴoƩw2!jx8 @8Rӷ=Pw H ٣{"{ڏt"ëtM B9lς=Xঅ$ >݉:/_haGmݾPt2vz>~ȠLdAWM' @v6"=MaGD##{'>wyΞ"izD 8˓a㚀Gj>v H`niᔸ~`AOML\1#/<+$٬"}?EqԋxQr)iD0EceYqT t0fxq@?25±zi uy 5Y8$-+VQ+ t2j[ͱ_F4+6Y7B ms<ҏ|&`&n:+ )h##TGX({3N 0- +oىTy`#PRZB׈K\Y!a0 Zut P.w4ߚ a}t6.uճp%--7,nY޳{ף~5[6[âUNaCtY|L_?zҥ e G*k0:*nCQr(M/PəSuy(eG=ɂe5#]2%kNOУœ7@\B|vFCپrOqf|=_HE +c]a>H'7TGàJ}FG |p0X|(Y-OCtdZtJ42Sb;Fi4&cZLá x6gٻ.!P:L"NCl0 Wp9hzM &c.j:nLP^P&~]DXA&p8H Quaؓ]|e1$DgDe+|R ݧ6je:U/TYNƁ7Nb fژmZvG'JK!/%'76c*mZ#Q_D,ͮ"qjDRVNHsy'pKr7PVf9%H_PAQ֚+&TuԎ LirTK6Z) 2qS9DO|@ eifpp׈Gj@.E_qљ4y10g qI S.jz OĮ$Tr+ë<"fi#˫ .n>'uޝKXUYf ݱ9 6 +KaMߚ +AN҉ +OYF֘LX|YHT-O OB>UHF jBJ#=G#Ɠ=!"'7o#NV1{6ڜLq +:}*G4ruÜݒC"Ⱓ:Ѩv'<8o 3$K~ܴ3iR4IIS*,v; `MXj@;Hs[&=u5%&%( +8vO)vб,dvԡZO]$e.Ynry.`Ȏ|o0Bj +|+ĝ`z!gKƳtt bqH#{k0xoz>Il yǵ~n$Ug"ARM*xo9!T3%X_Fj\m4 }%}kr2TI'qCa.ጜKaZb  f n95 +66%UJ)kvRmx5̟s-;ZĢdXy. UWƨKHq` >B' +݉HTWT4:^.twR7ׇ@lLʓ&a΄J +qp ѯZ~]j4aXc0Sa*I(eA eI&`n!Lʼ~d"8G>[cf*ɃrJ:wxKI" 3 v[86׸9Bԁ蘭6BL`sD.9p#' f_ 9y WX ۷v7C~9`R *$CddI!%ٕwu`\k^&gu,*,Ql؂>zc j>vrpy /BznjoT˨4oa#|]fw_/#/\ 76aMXS ȺzشFg*B hk{ӼO|xV܂SȢk/˕F^`{KiO9֩a|gcjylO/ CǃI*)a'Jh1/; 8PD +mQ;nMx +endstream +endobj +103 0 obj +<< /Font << /F247 73 0 R /F259 75 0 R /F359 289 0 R /F362 290 0 R >> /ProcSet [ /PDF /Text ] /XObject << /Im1 291 0 R /Im2 292 0 R >> >> +endobj +104 0 obj +<< /A << /D (cite.ren2023investigating) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 113.148 595.934 123.479 603.562 ] /Subtype /Link /Type /Annot >> +endobj +105 0 obj +<< /A << /D (cite.feng2025improving) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 265.863 563.058 276.194 570.685 ] /Subtype /Link /Type /Annot >> +endobj +106 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 96.829 519.222 107.16 526.85 ] /Subtype /Link /Type /Annot >> +endobj +107 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 200.147 505.576 210.478 513.204 ] /Subtype /Link /Type /Annot >> +endobj +108 0 obj +<< /A << /D (cite.yoran2023making) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 238.741 393.195 244.903 400.822 ] /Subtype /Link /Type /Annot >> +endobj +109 0 obj +<< /A << /D (cite.cuconasu2024power) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 247.726 393.195 258.057 400.822 ] /Subtype /Link /Type /Annot >> +endobj +110 0 obj +<< /A << /D (cite.marina2025llm) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 264.837 338.4 275.169 346.028 ] /Subtype /Link /Type /Annot >> +endobj +111 0 obj +<< /A << /D (cite.yao2024seakr) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 55.93 327.441 66.261 335.069 ] /Subtype /Link /Type /Annot >> +endobj +112 0 obj +<< /A << /D (cite.sun2025divide) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 240.494 305.523 250.825 313.151 ] /Subtype /Link /Type /Annot >> +endobj +113 0 obj +<< /A << /D (cite.shi2023replug) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 79.494 160.266 89.825 167.893 ] /Subtype /Link /Type /Annot >> +endobj +114 0 obj +<< /A << /D (cite.cong2024query) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 56.058 138.348 66.389 145.976 ] /Subtype /Link /Type /Annot >> +endobj +115 0 obj +<< /A << /D (cite.dong2025understand) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 163.954 127.389 174.285 135.017 ] /Subtype /Link /Type /Annot >> +endobj +116 0 obj +<< /A << /D (cite.jia2024bridging) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 131.908 105.471 142.24 113.099 ] /Subtype /Link /Type /Annot >> +endobj +117 0 obj +<< /A << /D (cite.yao2024seakr) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 428.313 697.252 438.644 704.88 ] /Subtype /Link /Type /Annot >> +endobj +118 0 obj +<< /A << /D (cite.yao2024seakr) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 397.101 587.663 407.432 595.291 ] /Subtype /Link /Type /Annot >> +endobj +119 0 obj +<< /A << /D (cite.skean2025layer) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 435.934 274.866 446.265 282.494 ] /Subtype /Link /Type /Annot >> +endobj +120 0 obj +<< /A << /D (cite.zhang2025reasoning) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 489.405 274.866 499.736 282.494 ] /Subtype /Link /Type /Annot >> +endobj +121 0 obj +<< /A << /D (cite.azaria2023internal) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 383.342 241.989 393.674 249.617 ] /Subtype /Link /Type /Annot >> +endobj +122 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 545.633 220.072 555.964 227.7 ] /Subtype /Link /Type /Annot >> +endobj +123 0 obj +<< /Filter /FlateDecode /Length 5900 >> +stream +xڭ\YHr~_!bPܳ;{uh:hDz PRϯwfeVʲ/ P@77͏_|拯~P)T͛nJ¨7vw\oWN~mNͱNݖ~á]of8mNjw0}s+Ck-0F9W^;w0ZԵ}jkl? :ӹ9= 2ǺWٍ=n^͟ș_ uySeuQhF8^u)#n.YUlU{^MOMjǗbxvίvǧi< 'D] N3:^BUfOv:<=?詹?0Oz>68ywhG၎?mYfͮ˯qlGN(.L+E>^JעAhzuwN'$`D{ԖƟO0s:5SصDBya2]j}Zծo^0N>5;@EVl|ʚզ"n܂P$p*3}{ͺ~nn`vvqtyv"ijm3Ohm7#a%`ߊdF'Ẁݡ_j]TV^X&}:6H{͢''JY X/~=汅%Қ/A[d&ݤd^y ey+cJY&r +C&:9VwDdhSYXp>.6j:֚t|j h{G+%R)07*4ۆ0u-㾨E&G5y3iqS[ 0΂ `poN]ظBg)FVp{h+n?E=<'z{ UFY_~jOCM`Vv4 -uqcxg4:A+}𖎕ZfRt7Hأ}>4'VZ:2:\ԡyvVvuv=<ս<}d,D +EK#NģûG)><O%fQd)) VHs]EV8JX w l[tb'xH/Z/M23]x zu N_we' eNPMV:'΢PZ5} m]w=Û1y#ubAEkaF +)H+Ao5؉߾Z+yuW,oXDbuoD014a#@pÁ۵[j}{BkUeB] D%P6[>݂ehF0_-*ѽ'?n18_I2*XOy/ʄ>YfdO -҂$@9|,ؔ.d遄c֦" P:EII&CĢw.xh9n8v93 ];M& mĘan +"N j%a O{pYVec]C~EUo>WpZݢshxK[m&A\]0:bɈ5[ +6Kq]q=SLŌpr" 2L i]5= fwtSf1ppH%+<Rxo,a\h8B VY@h<"\*r)2S"EVS7"SPKJrYBr++qq"N0cR &*~0?յM7 ,ۅqV Yt^Pr١B#rNax8t\F1CR.:Ϫ+xü|^$8Ւ6:tkb>^cPd 7leօ\q.,ji"qVWʹ1Ȫ.s8"T +gw^ Z^sM!p=6(Bƙ \t" YJBG%H%Yjp9ЇK4w8Y?#a>-c5oqV(I|dDԠD? a*fqz<[`}Uwޗ4͵򟀈B?R%.F `4DSU~Ī!vG=#M5̐{_1E:6=*N0I6`T{mmt>j: +KSLnBɿVǯiqu!6:EdV,d}QjVf< I4 7? kQV~&jdD%"$\p&kݯkSTj]cÕ^sֻ}'JYܮupida)\VodUr#8X]g= `˲^!/GnF1}w?NcQJ74J_# BͺB:jSRK <r" .?%T61^#ElybEt]`$Ulq(^ s \Qp[ XFbXs(06&nI!<iV~_v,e;' *B'fle3 բ QۜF*:ʰ5>SRkfx"̡X V%s,V@*#\ kpFAC!K'jgF?;3x; +7; : O׽F=s0w;+(a CvQV% r>h˹CpEzB( k{%+@q<$G?!$K[u#X ƻ^T̪.WWdo~JƅDE(I cu:"v] ْ(y7pG Cf6eͮ(v8ps{jKfɪ$ݳUk<\h\ )}츂ή iuVKuúCc_6Z vwl |,zCTV;ubd\̄ sׁR.gRhPI?ZиltA# d4IjPCϦk0VrAɾbm\EJ5G*%c$g,3]c},wR0L3wmI4@Gj| .5qhC#sVaebvhHK&!qFqXt@)+_N|-.^z"}6 B5X#N&Hjz"u*0JҴ +0 +0`hWC;v%t PSZ67sv\8!Di}F@0W a`+&B XS^AfE +p^}w(]DgJK5QuJMx<:J&ڎNW.Jغ%iJ'\d56"*p}88lzxc}_?)|bf\a@`9Aɺmjs~\d?|SqR \3TW\% +6aљkN~ກ_Jclmk}cU YQVAN¢s(DE4.-{<灳23¼LNj O34" 8>3~761{x'lxzi6&*\@.TH:ˮ,Zzv)1Zqxe5>o &:GUi% +@ SW#UlSBak]uvuL.7 ٘y/e=\syw& >6\` G +0K=&Ad!ʝNp Q#f Rf]j[  *eQ~._gړïosH}FeK"xZ*ٞ=)Hrɹ'lǗP)ԏԨ͠{jNa57mhq._|sg˥ݮLH,s͗6de).6MVd-MV&韗x.%U0ʽUɔ2 ь^(&^.MwW:Ekoou&9 +endstream +endobj +124 0 obj +<< /Font << /F247 73 0 R /F259 75 0 R /F282 76 0 R /F359 289 0 R /F403 293 0 R /F406 294 0 R /F413 295 0 R /F419 296 0 R >> /ProcSet [ /PDF /Text ] >> +endobj +125 0 obj +<< /A << /D (figure.caption.6) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 191.592 215.186 197.828 225.454 ] /Subtype /Link /Type /Annot >> +endobj +126 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 281.566 173.363 291.897 180.991 ] /Subtype /Link /Type /Annot >> +endobj +127 0 obj +<< /A << /D (cite.47761) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 196.389 118.569 206.721 126.197 ] /Subtype /Link /Type /Annot >> +endobj +128 0 obj +<< /A << /D (cite.ni2025towards) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 379.848 184.322 390.179 191.95 ] /Subtype /Link /Type /Annot >> +endobj +129 0 obj +<< /A << /D (cite.47761) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 361.012 173.363 371.343 180.991 ] /Subtype /Link /Type /Annot >> +endobj +130 0 obj +<< /Filter /FlateDecode /Length 2605 >> +stream +xڭk۶ +s ҝ~ci<7uZۓ$J""뻋]>%mfľ_mfrՓMfHg5%PDnWwwU8e6_h+t̞WW73S "L '$D6w¨(Oqjw_dMF{o՚"ߔ9 +͖?y#x_=vrI!W S%a0@Ily]MZg5* + D :q~E{h`@mp 1+ +|ZHu"#7 x(W(AXpe^h<AnyMOVN$,!!`v3t^ш٢Q4v<<6J0,El@HuT9< f9!!BcDZcj%?!o_,n4ЪD}^= IP?6" Ln, gJ2yYMEy/é=sVH׃%68NY\qX +a"EI_\]Xa>Ћ.-A9a$ryا$WxeUz]?|;7ap2t޹^f%8w2+/Qu9(t㨯ZAw(W |z+yQ =E,)ٺ!H2Ecu&0D/hl->/>$AHޑMY]!2&j QgX4Nj밲/J>y4΍ Vz%aU4#nh)G]^z +# HY;pV<}?f +b̜>as:i ըP@GcƞjXC2&I;0vʁmo1liC3_"[zx`՞8tx'uӚH46tM7udi O[;,T?M>7 +8ceQ͖UQ>,+9*ZY?߼bz[m.5Hk( QCdH : (/hX M͈6!q%R}Mhl%ߔW {6A>4e"UFS5I0j)ۗ>CjVO{h1Nry օWJށB>i)m#!uզ +m8 EbFm<\W6梾>fGzޅoj u_`nh%le4]O*D7-_+ꮁdP +8^2`5%V"};yGvRAFZߔp*)dи)YZB A7Ǫ-XU1p!1z:;ﲊ\ Jod_okkH%vmun'Vud8;| !7%j|m9pja;"yGl)Awh`=5.KIzw:A^ӼjӓWTWـ _Pj#CPA'ƗC\D'şS$ 0FA(vt0%MhZT$m3eNH7=֓n!ݝ#}\~+ +\@0"L].̆,}Y6n{`]_/.M7,{MOJkw6/ Tc L4F&X9?/VӾeCiSSDѤO']lIUQm"wjv(Q7W +&f1>rnDɠ8X8 Ƿq=':&vnX޴lehUbT ++TTH29-X`n@}:ƛS(GO6w Lgb#yʰ?w۔UjRVӺBNa7Jh]DgF32/|e/6:a +/.W.֠[ a/ѳ?^7Ցk*gu{($Byt,ؿ^#q\L)  &㣠 ^ xݟ"03L|G߄}Q;4[z<@gbETL |$x6ho/ ][+:pA&/H[K,R۴\vpj +q$q]P +endstream +endobj +131 0 obj +<< /Font << /F247 73 0 R /F259 75 0 R /F359 289 0 R /F362 290 0 R /F403 293 0 R /F406 294 0 R /F413 295 0 R /F419 296 0 R /F421 297 0 R >> /ProcSet [ /PDF /Text ] /XObject << /Im3 298 0 R /Im4 299 0 R >> >> +endobj +132 0 obj +<< /A << /D (figure.caption.6) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 149.519 548.145 155.706 558.414 ] /Subtype /Link /Type /Annot >> +endobj +133 0 obj +<< /A << /D (equation.2) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 98.138 471.433 104.359 481.867 ] /Subtype /Link /Type /Annot >> +endobj +134 0 obj +<< /A << /D (cite.47761) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 272.03 423.29 282.361 430.918 ] /Subtype /Link /Type /Annot >> +endobj +135 0 obj +<< /A << /D (figure.caption.6) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 125.71 92.499 131.934 102.768 ] /Subtype /Link /Type /Annot >> +endobj +136 0 obj +<< /A << /D (figure.caption.7) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 401.579 158.253 407.824 168.687 ] /Subtype /Link /Type /Annot >> +endobj +137 0 obj +<< /Filter /FlateDecode /Length 6687 >> +stream +x=kqWĭڡ&qN'lI/۩cjȹC~{ pșteIhlv;c/|G_Hmg&,g/3-–Bh9{^ڻz} Ͳ.Wv_/U٬E]QkwM;_~_Wzl]vXt՜q[݇!^}\mѾP*nu:x>5]0'?W%v87gٴw-쿯{'̣/*fenQ_3، -s=`~ ng(j_uP/zKȧݖ~U[5n;G g,{h›ȥM6t(nYW#uVqվ+YE??V Η)z0 &WR-_z@ áK`CTBعvO7٢zhֱ_W8SL+}.il3jhJ#ׄ| +=9,-|) G\sCy86fK ms{ jCO մ֡jkeQ2׺ p-NP0I7 H7uh.UoZ:bP,9vwcCZ"Kry_/׍Ĥ\cz[,]uO0%O0{rG ;6Qr<7zZ^|z%UiW9Ɉ2ZhfÐO4}-J}-D ;Ѣ $d93Υ4@oB_I}06 9`p6\`?O+}jB:&cWȸCr| LiW>| eJD[Y9ԲA N_MSf +@2sϠMdBK1@NG@~^ "Z5]VĤy \,< K+sUXMoIV44<'pr@L,Wɫl Q%߸Wq?楝i@{f -?3&O6:q-n!Ě8DֱoR D(̀\6窀K;cROk WT'd-eVo6'U{a4/NfC_I=.W(&~ֻƉ.E! )B+ ,#Zf]ڃvy(&ed#B|X@` +-D=ch/-W:fxր"& 6LUUKhA‡)e2̯(@G10hqNj-t* (P>(S(egu> @kJKpޡI5,lCϐ5)e>]oa `G}=hOR!+"W›Cn)-9q&eڅWC$څE{իy)6wʰxC&~Z*>!A$ʌ99)~0TΜt~gh#"B\% + r`$(v{fUY"e6FHβVhɹA3HLFA.S4]b<ѴloOд,tLzJLtp=CURZE*,0gsaγEM06_*i%:y^S[foGxWDMʢ[0Qq]]Vɠ޴!pESjn߀u +eD@ +#H ׵;0mx|V/@}ggカ`Cl.L`o 3o+˽ɩ gHŪBT|yJ +$*&X>96rog^.W>*ѴU&T-~S/uX^zV_ ҄%/?}XMe<ʓq67m iL: nmF{t0V$(|Z{ ʽ4t) ^n̥Vː:9y {4- J q=S$I= `&/`u3b:F1A&k(5,~F5Z)s._<3t$s%D' EwFrym.\L>@G@.9.r T:bB%tsrw1 %@D̍48WAL:`uxisB4vk,{WBȕ|}?5=Clȇ_8\ .wm91J< ЉXbr sI`RyөV3yLďaL .ٚقr(0A Kjlo3+$@^=k6mH d{3d>lr-~mH)aEng}U»yH*y?YjsԫH:piiK2,@q0C{&碬V.t',u`7Meۄc4<vR yd)A%#/c,4{rW=wW*egqv ƼZ?(9K$C ,Bġ6t%0djq28.TFD5:ƴw'@@Q IdL C՚gf*b,u Ei$cEa~Vha..`GyؼdR(Jd_a;o/MGYxMa(A`,{iݲ瘕"/^z:6ԜWR)ĤB'菚JN~br6Z87OMܤպ9f"hY#TG(e*֧/eh)/&S )>Vps:!c\[2hU6yWwT>~Ӈ.l#<&j**dݠٞe,Ivq23.]fy2_yIr'r9O=R%YV,+ViE/'hlt}"saSK0VI f';5DPZXx~KI%i6gl{dr6TM@LU&sdw#Ͳ1jb,)9-B>~Gkqi\NF2pB ^\E [j~$P/!+.;0,X#"q&Ouy,xW\EwrD`$ȼ]UO0SfJ~ pJ`ch\E34"sj3XFexP Q=,EKq!"M.dky@iu$bR8&r +g1g ]}巿3IAbTha;rf@M4j0 +%Ƣ"D a3gO'E)aSC +w ?-B>KHz8xv]n=Tg;|U xҺ5_pRc6rO@.I뺉RRV@k-LIڈN_rULf7uT3Kjjg!sg-ݧ4˧n5ϋY![MЎl xz[wT Mv}(xkW]qC*0 [bxWz?)~$}ug!muR~G0 suu"@?C1d"ڈ5Um(ٽRMcmԲCxKnT%ap.(V&a[ZI)Gfqثy\0AJ̠oϻ_5yhݷϞ75vW<.޽HȚ9RBoHPqV' ]ӄ.cs__۹YOyv0rcwd @݆__l62g&sAM'}TŤZg p-L˹~Lw:;`{Nh)㟗 7M Tces&VW2 hv{Ty 3AUvOq?u +P+zt@PBJEq\#5hZɼT΄>U#YZ*'v:$6KwUفQh㒏+{('Gs,3qrl!MN^;Rj<),$:nC@g]IOѱB@0R)7gubyQblY&+U6iٗNw}/ ,|`ڬ2&z/: O'QVH ž6WZD)^vqAa4%E~T Á E~| +&q$PA'(A- O_57 <,9>q=o%&>.^ӛ5EϷ>>  /:::$td.̝ q{.UX~TAjAQ% jdLU:{i]DljTAuV꬝n3 +惑Mn61L'cNJk[<G:lFNf8ɈtIoZ*]d\A lHH ' +䲴hL/=9O rtj .hJPDKr  ŪҙFzE|V|OvX~}!tFxB(b Dx-9NvW>y`,e*KN5$"fP]EQ/'*HuQʟ~05g p= cU,[nXe+P[X|GAqG.S{qx:/W#}BU]EؔIۻx⹈ov%c*,=9] *W[1q?Bgkq8e$ q 6aBH0oWdK>ǡ.Zӯ?ZEGé (Tl~uAguaKa~ [/1'fJ)z 9m/SVO)2Ix;ExO eU`_܁N;u@C~\mO#d&M o|·+O>,$Nٿ|\~iԫnY^_OXWD4XC3 (Q~e(>?#9 +#zKeݟA.st*(qBq y wkWѥt2~rG꫋_>qR,8)v{Tn+$z. Mtv +[O/iOU gUV!/NjDhqbq V ^݅.C:sY'02(ѧ]'d uQJo&.BiZ.G<"6fs | +endstream +endobj +138 0 obj +<< /Font << /F247 73 0 R /F259 75 0 R /F282 76 0 R /F359 289 0 R /F403 293 0 R /F404 300 0 R /F406 294 0 R /F413 295 0 R /F419 296 0 R /F421 297 0 R /F443 301 0 R /F449 302 0 R >> /ProcSet [ /PDF /Text ] >> +endobj +139 0 obj +<< /A << /D (subsubsection.4.2.3) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 251.655 651.403 270.43 661.672 ] /Subtype /Link /Type /Annot >> +endobj +140 0 obj +<< /A << /D (cite.47761) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 281.476 358.604 291.808 366.232 ] /Subtype /Link /Type /Annot >> +endobj +141 0 obj +<< /A << /D (cite.yang2018hotpotqa) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 159.928 347.645 170.259 355.273 ] /Subtype /Link /Type /Annot >> +endobj +142 0 obj +<< /A << /D (Hfootnote.1) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 141.76 154.8 146.492 164.844 ] /Subtype /Link /Type /Annot >> +endobj +143 0 obj +<< /A << /D (cite.dubey2024llama) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 500.807 686.293 511.138 693.921 ] /Subtype /Link /Type /Annot >> +endobj +144 0 obj +<< /A << /D (cite.qwen2.5) /S /GoTo >> /Border [ 0 0 0 ] /C [ 0 1 0 ] /H /I /Rect [ 362.665 675.334 372.996 682.962 ] /Subtype /Link /Type /Annot >> +endobj +145 0 obj +<< /Filter /FlateDecode /Length 5817 >> +stream +xڵ\[6~Pֈ&nxkrq&ή].q-QHٙRH|h:[/ŷOyeEV*7w Ң*infx|U4ݪZJ% +Î??odM;d&J̒ղ?~#_Wj#"{~G]О˅IuQ] +^4Di-(qվο"E_mN+t+47LB̼t?7h32E*|'o.eZib.rT7ǭ~8CsD;nǍƖ]vֺ?xSmi@m+:}.\$K(&7/TUȺݢDj2OkdJn>Pq+Uky7vkLpw4H=~Cte)PUVZ;$`J›gDfR'5.~߮&! M7?c:Z{YMY\h-;pl6v#o_RJ7:XZ4b*ICف\,`ÛX&BezktZl<vkX~755ܑ8H0;db l wH~Ɖ74K>!Ti)}Ewp"j` +tY*F=]wrmB,SǴWj\K蜷4U1!l$būXw֚lAB[Z&"|eeK' zΡX}782LPAa~|֗ҽ{nYTdU" L?aiEnO7NY\S>cUkz(W-,19yXNpɊTd|Y/nJ&iBi ڻ!0&hOL\?3tb u5Gn8gy[=Eү6 ^|#Zº9'K +a eT1JϾk~?mt h$~4;p;l᚟hlNlvz(OMNܺѠp<ըI}քՀ_益svJ3?lkՉM-UNbN[h֚h`4r^:z2UdB6=KL@7uO85Paej{Z;QFfg准pR{N xa +tRRHjjB +<\lvs;٘}hpT/zP1ۯE KYe:@Mן\S]a XNihWcjHC:z"hM~@+)dYH6qL43JUg#O!YH? |jn̅~ W\ͩ73ɪC[466)Ty暑WgN0)R OXh4/(Xi,Vm#lCj;[xYn4z Q˘fЛL_\)d ˥%L[BV Y}!`c1*MLSR6K{(mQ1Ce98&N/h'x_=nvh8U&z'QOTr9B`+=@e)AfJ独hПW`\u>Ͽ!ڔtqK95BdXИ;BC/O&H6f=y6[ [(.~zJ(PWCu,R>jHx"< á3L~ű5QbnGǗiB5\wuܦ>`KRD6&ř'=s"f˱ߥ&ͥ&IjbSg.*+#!H<zWe;6CtRUg!)8 0u25cr5W* (BQ)cm= h|Bcަ4k*A#v뀟gwoaA(˥3p M19Ihkki`ڦcpX +`Ǯ#d+iMwdk0p_# 0:JY' +9~އ'0Ůa"|4`TI:˜*2qZEV'1-m޷t1'? KQw+wE}g7RC+2f?qC oʿ)w>{H4e0y!< +UT,bַ5<+!UnvWHRjvۮ=Xء 2v$rhxlѦ!=Fuom}Q(w@RҸc^gw]#K8RS(ia(t%>n1F$sI)$"K}&VA-`kGiP4ذ,`BrW1{]Gѹ=,f`x2C]ɴUKt"C!_\7 p^)Y~4bA9غn -Ȟ` 2?Ԡ\6[;=+BT":N ;ɧYEr@TcQeب5 ++WK)+[4p]ٌY +/Yt8Lɗ rNsIQ] VΒ=>rTiUqŦ5j6pT%V\4zkYYEp c741 $|3$rv%ʾDS\yoj5㠠8 r@.K2 )[F5c20[xIe œUba*t*Euj3AM2|Dꫠ' V.PL8YR̵d? 3">2sӽx@q3]1I^&siV 4:ieRL<Χy'NBON _%gW/XB +v_ck0 !d<=c{[% `~JZ] zn8 57,DߴaUZx] nUIve|6Ε.A=ǫX[wVЊF=jNd st:rh;UA4>R4- ΡCƟ!5wVz@SQk75؂C]Ue!(O.\`Y]n/Wt)> /ProcSet [ /PDF /Text ] >> +endobj +147 0 obj +<< /A << /D (table.caption.8) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 234.22 395.313 240.324 405.581 ] /Subtype /Link /Type /Annot >> +endobj +148 0 obj +<< /A << /D (table.caption.9) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 229.411 126.366 235.656 136.635 ] /Subtype /Link /Type /Annot >> +endobj +149 0 obj +<< /A << /D (table.caption.10) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 513.833 347.828 520.003 357.177 ] /Subtype /Link /Type /Annot >> +endobj +150 0 obj +<< /Filter /FlateDecode /Length 4658 >> +stream +xڵ[]㶱}_ʋC 8/:o]OZ8geRDɓ~h\} H448azTڮԖʬnWZ5RivzϏMS=7Rg |W}}>5[:eMjjz}ϛ$xG Zovu;':z#乫~n/*2ֺnjyO7t>1_d& 6YrwsvnOsۯ_doڢߥIKQ4une{ħ~We>?թi͏7r~g}=uu盿襾}! [URÏPzݿ fR%"(SUxݿ|R'oa#s8nW߿xțhhe^FH%WXOLXo +# Z<p܈mùktQ,qsXuk5wYAoJ0nƅ賐l +"oiHk%qE)+`1`erkl Cج-Y3hf>OlFrwa_ 0*řGhg.X$CIې<D讖KfVLa2g yˁ "' +kHB +Hj|!+ $2Qz:[(C9qE܋<rOG*\!Q[taMMl ˟&zVnSSrɈM.9bԘj0v)^1PCSxGO]c+?& i9T`*.LyODBʼX)L-2E:uIo}hɑT$[kIkgO s\\TY+Ъ! B Vը⇨6ݞ;A69UMp/W mH?"d%(>}W+,;X9Gyp;]{^-w"+=*$rz*68\`xI(.2|VsB~xuge4+HUog "iCEz<4JL&]it: @^V3lӍ^,Ы^ك4ѐ:kO{CI4YpQǂC0Ve޼r^* lלCHD`>=/A)eHC{I[@" 4nH ![P/{f >} O!#1tHj&g2D4v6q.#a,]Mg#l3WĴ4=qLcCy0:q^K siҠF|JjZ[~ls%$VA-Ә[7Rt ,H*fE9J'O%Xʓ(|ܟ=y-Gn/j&1mĴKNKNH$e6!RTd 0LNY:N=D+,97N^d&p?}B%qL\Lb#U%_]}Qʺh}~Ņho'Fҿ66M#Syev9yPfDLC-xP.E,OJ(AsDX_{m΄~g.rîڜKʻ6żʪ"Z"p2i.0VjYk'tZ@RgkF,X.`YKł*׀hbtray 螱C e'=5PL:KNYs9RʼnĻ,DPDc]FYz.5]r O͑95,4=?fG2:YNnLJ7@Z r}QEjK6 dH9Iĝ‚}=$X_ܑ h2 POtHyl]Xb y< +9m0;Lnf[HuG[!H/d#s[mL.9UeJΈOb>O=CO\R!s֟rQᑺaq ?ѸXHr*j=j8#:װ+f]* +endstream +endobj +151 0 obj +<< /Font << /F247 73 0 R /F259 75 0 R /F282 76 0 R /F359 289 0 R /F413 295 0 R >> /ProcSet [ /PDF /Text ] >> +endobj +152 0 obj +<< /A << /D (subsubsection.3.2.1) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 122.354 186.608 140.8 196.738 ] /Subtype /Link /Type /Annot >> +endobj +153 0 obj +<< /A << /D (table.caption.9) /S /GoTo >> /Border [ 0 0 0 ] /C [ 1 0 0 ] /H /I /Rect [ 469.754 293.674 475.908 303.943 ] /Subtype /Link /Type /Annot >> +endobj +154 0 obj +<< /Filter /FlateDecode /Length 3585 >> +stream +xڭZێF}WهP a_̾'e;Ɓ8'{IQ8o멪rL}ruo&6R&7w4sT5YL~Vwnꢢә4Iϩ7r}$M 2.4;uIRCG?FFiQ2QV_oVNL*=LI('úpZ;/#b?eÛ#t_.#Ղ;e eX&vx֓$78n2"":,Ioq3Y߮/l*d4wGwAϦJGrc;eML0&W73Kn|ܖUޔ۪@#5qtDj&͓ߟXLg𢌥d$NU:y 7-8d~`)uړH*B!byKDO +Ir/Tmsャy5T03FW$ d[i'3acMVڳ*Nu}Eh͓!؃lgNR18⬄Ƥ`2/o+pioΗŌPV˷)h6 QJ4*dN]B梓j@ -]X95d$cD8 lh Œ#6 '%7qԱJ^ у7}5[8+Pp%cqĢ +Ud-$2?If^Un?eZӢIfD`Sa'iۏr{Xٔ6snk V Vm]3^{KJcΥ3awl1{F# [{D_# v0F'}XDjwzI`"w5dE0ʶd厭hD06|$BeZlmO[FUuQ7F$D#d8h"#iYpdPEOz7>{4<3:Cp$(,<{{]e73v"/`v"S`XJOpnse8ՙX~8@S6eoGI#7}#k>;/O!C=cZ?@٩d">ndD֎6д0cD"XՖa&}PJ~Z :[ y('2EOfE E2.R_kT=z^~MQf +b +g +ǬZP?#P \<^|Sʫٕjw:m.odQ|AC`u.iHJ +KbhMGytqRٖ:X*kBoÎ ur3V!\_e۟ +L>v! yI9L6H|Ym@[pOOHt5D'};9LfTzбnmR} y)H +!-5F~\:ErҘ8лtPOg\F@E3kmD>$~ +൞MwmwFHF[m&2C%,gJۿaIueGсY"1 ߖu| Oh.ݐy^5GmwNerjYEBCIq@F"'8GV_f5p=n5+jыnΑ:[@7B7hp}E _@:fYņ/[اQ_ D`(B%4 }|Eހ;? +""4Ctv륍guYyRL+R^,#}„!$u(8}rD?FFU?{0Ӽ•U~,֋o5k +a@MۺmmaQ EiKb:!3HX̚@ |, ѳD=p[}]tj瘖%3ܰ$;auk8ecL"m0$Ȁz}u?hmыm})$<PR>ܑ;TD\hE%A*E9(gLrh#e@ѻ;fB]Ԩk;\fb+O,$( < kAMWCQEa^H>z3#>\>T"(A +)/y!,Cc[wjζCt)mIU0":"42S_d6dfH)ϙ!FT/>䶯񢼪=툷AXHv?gEKV';|]31)gCɥ}bEkXf 5z7l#UAE_H(C5 @-cXWnNCGKctL6_SQܥ9y$: &Oyê}+$ +,D۪XNJĶ>mfH[8l67hͅ19LJz\$iYZoܨ9e_Hў/P /QW-ΫT xF[dX ۪G1JB;Ț'ӭ8yٮǰ`cd%ˢ TmKl7Ho;H8QLJO†]ͳ:!:ĐyEA]2VauXgX&ub{a-: + XQ~I/LQg!W?Z#zT1?|ѫG~"dq* u)7Yha=:GQrsF?8 24+<*=ɧ&uU#sBjy,uțDZ:EJۃ7s?ڒG[450e\q/"+ +kN3}n?(8GP6%wK=5G18ʼCcg.Ȇ+cTooy`pɇ`KDl\"$ +af@Q> /ProcSet [ /PDF /Text ] >> +endobj +156 0 obj +<< /Filter /FlateDecode /Length 8347 >> +stream +xڵ=GwFw + "aE4)[MH_UuU7`Z{tFtnW?_/0݋47{oi{E\vᅎuzzFsQuoj?"[WM {d^TsT7r㦪)qpk΢\Z,7MըX\c*֔g:Jn:r '?'UԟeӪ׿O~rx҃u;uR7[x;m+t\]#C K4ڎAWg +=8Oh %nrXwՔ͚V\3eե;r Nihf?'%K:'I~6/QQl]Kgղw T[iVuʵpY~`MNy>zjdjg~HS,`ﻖiPelUӃ60r WjY uWײԬxɬC׎fMTyl13cR]VFh6qo2/D^8v4\YU[YG +\<][-Uc5+dpCcWSFx͹aX؜-/0<~_syc2 pՎ@+ [BW]Ǧ<;EJ@qͶ)n'㔏,ƃBjV"@=^.x6+P +mmRNlK|5]co#3t1,ڜ^/R8r)ZT0 ntU­PI\ˮn^oH, +*-l06cT{#UUEB([(`Co2$ {495bIb5KkhD ؞P5S'Ͼ ;vz(J`/ `~A$9=c)H>H3~/)| p>C$ϚO~jutC!8G0| >'Nh0a +'I48?>nZv Amx3Q:6ogz pL3y{섓5@k|VDRG>{⺞9:ͭKCX@2.۪4LB%"[ŕ ʃԕk YK`Y +&wC2ZCq%25dUUPêeϨBwnk}kWw&H2Vꪪ1ۙ`o'b"'~cRYajb-{Nue.*=*EុE+5dg.GO44BS/IןAtztwu'5ꓺ  +t, -M`CŃ3#KI\O3no0bTĮ#/ +{D*J |KZٚ:\>0eGy65 + 5%3Z0Avzֆ8 M'p7(s4R>frC=KaS^mӭ*JirfhEˮe@yAfU6/Q@J"@e^Mb(TeFMVP:1-?\M9GK< +E5O 8w47ĘIy +xdZ/U뻮U: 䃆狳w=ymƌ!5 n߇Ms'+|wx95` C~JQYQDZB_:%nj]`iv,IEtOX}=c?}?bxW)Of #0*{3l +R8 %cTD A.5I#/W1V0Mna0Nm )Hv?aі_XQ[2;*OE +/:=E"^K !P|-jOn▃1 |O/cu5PVM%]vRs*8:>ef4wn^ $k ]\`)P|ߡn? &WN+D4jJSNZCcGF>\:&]<.CWH׆cf<~/.zߣ|t_Bl]0TbWsjh(ge@ImXE ,Eߞ;2 +D6LtIb{V7 OQThERߌkT0Xhcɹc{`/P*HOo O7lρhx))qA0}TPկR)Y) "X~BAPA&H,{LL'^n~hѥXD..*(Y7F.|rv$k+c7%1xڜqn;lS{F#b)yI. v|fh\I8nCs0$vwu<3= c wonv0 +St=fƿlO:.NHrM ]P#7& |9m0A]p%CblB7vx NYp p \a\#"=bpx:Pi7? Mnd>/52Ҵa"^ù6#jFk>8Sg/+C9JeܔO9fs4cVgo#~Nby+2cR\BTl~@S)K53^P:q lEi=FS^6H`CWJI~-$OZRώ]*]]-Ϩ*E^r4.h}%N4#5yEaر;?}i;0"LydDc luJ`u=Xcn]+D)CǂKY :v ر/վh)f@:p+A]qcpTXEhu!)g+@ATYD!t9$߷^~H4E*_T=HY +X;wpa6n8bISl⛰Pt[0 +i⮹VNFÓZ;>hQK[((o' +@#*oQ}cS{|dQϻP;h}hOHv/ƓgZR|B7sdG {)0GEĞ[t&gH-؇Ţ댺NmhC7zl5~n[0n #rI;0| !^ތyc|G9JurܮM3 Oč^A'3@:C$3tW#E?7 bbux8D2dža.cw&MـjO7dӧO!8Ȗ+Q͆3םf AC'~ dJ@>j:F#]I2 + ыD@]Lx%з~/ƚ7BnmS ڟO+GI^}3ʅ,3E C`+>0PIQ,+{`|ZokнrM6 (餺2-s%4fLʃbΪfuPf%U!\H"+F]:H!aB(s+éPݐ0o'p mEax;(G|%917͉A %TAJHP~|*_~,= v*˵¯Њ>gDȺ4ܸ@DOF{s`K8ؚ%B|-ȜFڟk7-ytðȣ5waGa1|_(EŖ +>sLOēXWdG,!lR +ӨRib54yks4Ph:1W϶N%ob0Z+2%vk0Y  .S(I@nW^-$vFaIBG( 0 +6ٵNUk;C'y-jMU"J4:p Ggs-sP?*z=2G?+>:&/369Puҍ81B0/HBpsy ,pdJ/ϴG8pĘҎ p1[q=ݶQ\=Ar*)b/~C +DAF2L @Њ8;6T|fvٙi :% )G\OhOG6"O)(8]DY5FA|7BS2.R,-U(/(,G @ȍk(;^v<9]n|—'9EQ$PߧHUȗK'y>򙨈o^w? xZ%'g>WIna\(W}Mŝxd KV1+O1y: )yI§]Awv0biu +_uH8plIeF!y7nD]n8z fp.Z +QBچfG]i?7X Dv;N!`(5Vb؅ PuchfY1VAh=t~:a0!; !+0x%Et xzi<>EX)s&[4p*&*rZf~q/9qFF_泄?ax`ҖO[ wX0Bdv|]ѦȍpxVrͧsQTka)=UE:a2+ܠ$+TaRh\uƓf^bf r#Y֒oU(%c]f7E!Y> /ProcSet [ /PDF /Text ] >> +endobj +158 0 obj +<< /D [ 9 0 R /XYZ 53.798 520.172 null ] >> +endobj +159 0 obj +<< /D [ 51 0 R /XYZ 56.165 92.623 null ] >> +endobj +160 0 obj +<< /D [ 54 0 R /XYZ 317.955 244.055 null ] >> +endobj +161 0 obj +<< /D [ 54 0 R /XYZ 317.955 259.995 null ] >> +endobj +162 0 obj +<< /D [ 54 0 R /XYZ 317.955 594.74 null ] >> +endobj +163 0 obj +<< /D [ 54 0 R /XYZ 317.955 523.009 null ] >> +endobj +164 0 obj +<< /D [ 54 0 R /XYZ 317.955 395.487 null ] >> +endobj +165 0 obj +<< /D [ 54 0 R /XYZ 53.798 323.756 null ] >> +endobj +166 0 obj +<< /D [ 54 0 R /XYZ 53.798 259.995 null ] >> +endobj +167 0 obj +<< /D [ 54 0 R /XYZ 317.955 371.577 null ] >> +endobj +168 0 obj +<< /D [ 54 0 R /XYZ 317.955 172.324 null ] >> +endobj +169 0 obj +<< /D [ 54 0 R /XYZ 57.041 347.666 null ] >> +endobj +170 0 obj +<< /D [ 54 0 R /XYZ 317.955 499.098 null ] >> +endobj +171 0 obj +<< /D [ 54 0 R /XYZ 57.041 499.098 null ] >> +endobj +172 0 obj +<< /D [ 54 0 R /XYZ 57.041 427.367 null ] >> +endobj +173 0 obj +<< /D [ 54 0 R /XYZ 53.798 283.905 null ] >> +endobj +174 0 obj +<< /D [ 54 0 R /XYZ 57.041 475.188 null ] >> +endobj +175 0 obj +<< /D [ 54 0 R /XYZ 317.955 339.696 null ] >> +endobj +176 0 obj +<< /D [ 54 0 R /XYZ 57.041 403.457 null ] >> +endobj +177 0 obj +<< /D [ 54 0 R /XYZ 53.798 148.413 null ] >> +endobj +178 0 obj +<< /D [ 54 0 R /XYZ 317.955 570.829 null ] >> +endobj +179 0 obj +<< /D [ 54 0 R /XYZ 317.955 475.188 null ] >> +endobj +180 0 obj +<< /D [ 54 0 R /XYZ 57.041 451.278 null ] >> +endobj +181 0 obj +<< /D [ 54 0 R /XYZ 317.955 618.65 null ] >> +endobj +182 0 obj +<< /D [ 54 0 R /XYZ 317.955 642.56 null ] >> +endobj +183 0 obj +<< /D [ 54 0 R /XYZ 53.798 100.593 null ] >> +endobj +184 0 obj +<< /D [ 54 0 R /XYZ 317.955 148.413 null ] >> +endobj +185 0 obj +<< /D [ 54 0 R /XYZ 53.798 180.294 null ] >> +endobj +186 0 obj +<< /D [ 54 0 R /XYZ 317.955 419.397 null ] >> +endobj +187 0 obj +<< /D [ 54 0 R /XYZ 317.955 307.816 null ] >> +endobj +188 0 obj +<< /D [ 54 0 R /XYZ 317.955 546.919 null ] >> +endobj +189 0 obj +<< /D [ 54 0 R /XYZ 317.955 698.351 null ] >> +endobj +190 0 obj +<< /D [ 54 0 R /XYZ 57.041 530.979 null ] >> +endobj +191 0 obj +<< /D [ 54 0 R /XYZ 53.798 212.174 null ] >> +endobj +192 0 obj +<< /D [ 54 0 R /XYZ 317.955 196.234 null ] >> +endobj +193 0 obj +<< /D [ 54 0 R /XYZ 317.955 443.308 null ] >> +endobj +194 0 obj +<< /D [ 54 0 R /XYZ 317.955 666.471 null ] >> +endobj +195 0 obj +<< /D [ 54 0 R /XYZ 53.798 236.085 null ] >> +endobj +196 0 obj +<< /D [ 54 0 R /XYZ 57.041 371.577 null ] >> +endobj +197 0 obj +<< /D [ 54 0 R /XYZ 317.955 283.905 null ] >> +endobj +198 0 obj +<< /D [ 54 0 R /XYZ 57.041 554.889 null ] >> +endobj +199 0 obj +<< /D [ 54 0 R /XYZ 53.798 124.503 null ] >> +endobj +200 0 obj +<< /D [ 48 0 R /XYZ 549.495 102.137 null ] >> +endobj +201 0 obj +<< /D [ 50 0 R /XYZ 102.591 593.662 null ] >> +endobj +202 0 obj +<< /D [ 50 0 R /XYZ 285.338 202.602 null ] >> +endobj +203 0 obj +<< /D [ 50 0 R /XYZ 549.495 575.416 null ] >> +endobj +204 0 obj +<< /D [ 50 0 R /XYZ 549.495 555.216 null ] >> +endobj +205 0 obj +<< /D [ 51 0 R /XYZ 391.21 365.481 null ] >> +endobj +206 0 obj +<< /D [ 47 0 R /XYZ 53.798 713.793 null ] >> +endobj +207 0 obj +<< /D [ 49 0 R /XYZ 53.798 713.793 null ] >> +endobj +208 0 obj +<< /D [ 49 0 R /XYZ 53.798 408.173 null ] >> +endobj +209 0 obj +<< /D [ 9 0 R /XYZ 52.798 736.213 null ] >> +endobj +210 0 obj +<< /D [ 47 0 R /XYZ 52.798 736.213 null ] >> +endobj +211 0 obj +<< /D [ 48 0 R /XYZ 52.798 736.213 null ] >> +endobj +212 0 obj +<< /D [ 49 0 R /XYZ 52.798 736.213 null ] >> +endobj +213 0 obj +<< /D [ 50 0 R /XYZ 52.798 736.213 null ] >> +endobj +214 0 obj +<< /D [ 51 0 R /XYZ 52.798 736.213 null ] >> +endobj +215 0 obj +<< /D [ 52 0 R /XYZ 52.798 736.213 null ] >> +endobj +216 0 obj +<< /D [ 53 0 R /XYZ 52.798 736.213 null ] >> +endobj +217 0 obj +<< /D [ 54 0 R /XYZ 52.798 736.213 null ] >> +endobj +218 0 obj +<< /D [ 9 0 R /XYZ 53.798 520.172 null ] >> +endobj +219 0 obj +<< /D [ 54 0 R /XYZ 53.798 570.58 null ] >> +endobj +220 0 obj +<< /D [ 54 0 R /XYZ 53.798 554.736 null ] >> +endobj +221 0 obj +<< /D [ 9 0 R /XYZ 53.798 523.161 null ] >> +endobj +222 0 obj +<< /D [ 9 0 R /XYZ 53.798 246.886 null ] >> +endobj +223 0 obj +<< /D [ 9 0 R /XYZ 317.955 489.877 null ] >> +endobj +224 0 obj +<< /D [ 9 0 R /XYZ 317.955 372.435 null ] >> +endobj +225 0 obj +<< /D [ 47 0 R /XYZ 317.955 148.808 null ] >> +endobj +226 0 obj +<< /D [ 48 0 R /XYZ 317.955 522.651 null ] >> +endobj +227 0 obj +<< /D [ 51 0 R /XYZ 53.798 630.274 null ] >> +endobj +228 0 obj +<< /D [ 52 0 R /XYZ 317.955 215.644 null ] >> +endobj +229 0 obj +<< /D [ 54 0 R /XYZ 53.798 708.314 null ] >> +endobj +230 0 obj +<< /D [ 47 0 R /XYZ 317.955 133.068 null ] >> +endobj +231 0 obj +<< /D [ 48 0 R /XYZ 53.798 475.635 null ] >> +endobj +232 0 obj +<< /D [ 48 0 R /XYZ 53.798 229.784 null ] >> +endobj +233 0 obj +<< /D [ 48 0 R /XYZ 317.955 399.13 null ] >> +endobj +234 0 obj +<< /D [ 49 0 R /XYZ 317.955 142.063 null ] >> +endobj +235 0 obj +<< /D [ 50 0 R /XYZ 317.955 708.314 null ] >> +endobj +236 0 obj +<< /D [ 50 0 R /XYZ 317.955 418.423 null ] >> +endobj +237 0 obj +<< /D [ 51 0 R /XYZ 53.798 395.196 null ] >> +endobj +238 0 obj +<< /D [ 52 0 R /XYZ 53.798 542.634 null ] >> +endobj +239 0 obj +<< /D [ 52 0 R /XYZ 317.955 167.987 null ] >> +endobj +240 0 obj +<< /D [ 53 0 R /XYZ 53.798 177.237 null ] >> +endobj +241 0 obj +<< /D [ 53 0 R /XYZ 317.955 347.244 null ] >> +endobj +242 0 obj +<< /D [ 53 0 R /XYZ 317.955 187.891 null ] >> +endobj +243 0 obj +<< /D [ 48 0 R /XYZ 317.955 190.677 null ] >> +endobj +244 0 obj +<< /D [ 49 0 R /XYZ 317.955 128.335 null ] >> +endobj +245 0 obj +<< /D [ 50 0 R /XYZ 53.798 433.232 null ] >> +endobj +246 0 obj +<< /D [ 51 0 R /XYZ 53.798 379.079 null ] >> +endobj +247 0 obj +<< /D [ 51 0 R /XYZ 317.955 708.314 null ] >> +endobj +248 0 obj +<< /D [ 51 0 R /XYZ 317.955 635.531 null ] >> +endobj +249 0 obj +<< /D [ 51 0 R /XYZ 317.955 386.193 null ] >> +endobj +250 0 obj +<< /D [ 51 0 R /XYZ 317.955 183.18 null ] >> +endobj +251 0 obj +<< /D [ 52 0 R /XYZ 53.798 529.779 null ] >> +endobj +252 0 obj +<< /D [ 52 0 R /XYZ 53.798 247.91 null ] >> +endobj +253 0 obj +<< /D [ 52 0 R /XYZ 317.955 415.671 null ] >> +endobj +254 0 obj +<< /D [ 53 0 R /XYZ 53.798 436.691 null ] >> +endobj +255 0 obj +<< /D [ 52 0 R /XYZ 53.798 713.793 null ] >> +endobj +256 0 obj +<< /D [ 53 0 R /XYZ 53.798 713.793 null ] >> +endobj +257 0 obj +<< /Differences [ 21 /endash 27 /f_f 39 /quoteright 42 /asterisk 44 /comma /hyphen /period 48 /zero /one /two /three /four /five 56 /eight 65 /A 67 /C /D /E 71 /G /H /I /J /K /L /M /N 80 /P /Q /R /S 87 /W /X /Y /Z 97 /a 99 /c /d /e /f /g /h /i 107 /k /l /m /n /o /p 114 /r /s /t /u /v /w /x /y ] /Type /Encoding >> +endobj +258 0 obj +<< /Ascent 693 /CapHeight 662 /CharSet (/C/D/K/L/M/P/R/a/c/d/e/f/g/h/hyphen/i/k/l/m/n/o/r/s/t/v/w/y) /Descent -235 /Flags 4 /FontBBox [ -1082 -267 6171 1023 ] /FontFile 303 0 R /FontName /XMMIKB+LinBiolinumTB /ItalicAngle 0 /StemV 132 /Type /FontDescriptor /XHeight 429 >> +endobj +259 0 obj +<< /Filter /FlateDecode /Length 882 >> +stream +xڕUM6W6Ťd}R$.[lɕr{EiH~~:87ns%zxhvOω: hXg{YΔzYgJ 9[x3˙/N'9t?L~7͑ix 9QoӴrI#Ss Ws?T.iKHk +endstream +endobj +260 0 obj +[ 344 246 342 514 514 514 514 514 514 514 514 514 514 253 253 512 551 512 464 1068 701 675 706 761 594 554 753 764 330 375 712 588 921 748 799 598 799 687 542 602 719 672 1043 672 653 645 409 313 409 518 486 268 516 586 472 591 508 366 561 606 312 350 582 304 892 600 566 585 595 421 419 354 591 550 811 534 539 ] +endobj +261 0 obj +<< /Differences [ 21 /endash /emdash 27 /f_i /f_f_i /f_f /f_l 34 /quotedbl 37 /percent 39 /quoteright /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash /zero /one /two /three /four /five /six /seven /eight /nine /colon /semicolon /less /equal /greater /question /at /A /B /C /D /E /F /G /H /I /J /K /L /M /N /O /P /Q /R /S /T /U /V /W /X /Y /Z /bracketleft 93 /bracketright 95 /underscore /quoteleft /a /b /c /d /e /f /g /h /i /j /k /l /m /n /o /p /q /r /s /t /u /v /w /x /y /z 228 /adieresis 233 /eacute 252 /udieresis ] /Type /Encoding >> +endobj +262 0 obj +<< /Ascent 701 /CapHeight 653 /CharSet (/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/a/adieresis/asterisk/asteriskmath/at/b/bracketleft/bracketright/bullet/c/colon/comma/copyright/d/dagger/e/eacute/eight/emdash/endash/equal/f/f_f/f_f_i/f_i/f_l/five/four/g/greater/h/hyphen/i/j/k/l/less/m/n/nine/o/one/p/parenleft/parenright/percent/period/plus/q/question/quotedbl/quoteleft/quoteright/r/s/semicolon/seven/six/slash/t/three/two/u/udieresis/underscore/v/w/x/y/z/zero) /Descent -233 /Flags 4 /FontBBox [ -1082 -257 6171 1125 ] /FontFile 304 0 R /FontName /IHNHAB+LinLibertineT /ItalicAngle 0 /StemV 79 /Type /FontDescriptor /XHeight 429 >> +endobj +263 0 obj +<< /Filter /FlateDecode /Length 877 >> +stream +xڕUM0WJ!N/TUrFZ Į7&M4=of Z=O̼yq7Bۍ;ej]X}ܞzl/G7u>=W}p껡z.N*S?RG>o~4{ >Mgv=KN$(_yʓտ5s?yUv!M2aDYVH~x>x7`R/yiԹI) O`VNѩy|r*9ڱsӶuvxrJZ5:pC3snnQeQQ +]jq9cCCh +lhPE4p 1u"Nb.\GU +a^ǢN8^-pƹpq[i]r_T/k5M̹Pf/c`fk۬^/֛5"-#zCT3 a 11cϗ7䯡1cO+>G|V|~+>C1 V|R|FR|/g)g1{)>_|&~Χa90K)cR>,߉TG^ |h#0nS1.LF^.xB趻^X}j +endstream +endobj +264 0 obj +[ 548 742 0 271 271 272 560 829 582 540 815 250 288 336 465 465 637 705 268 298 298 369 550 220 338 220 323 465 465 465 465 465 465 465 465 465 465 236 236 550 550 550 435 895 695 588 646 701 557 485 685 730 297 322 637 528 839 699 702 541 702 587 485 597 661 652 951 660 575 604 356 287 356 518 486 268 457 493 428 506 447 310 500 538 271 272 512 264 790 542 504 519 503 372 390 316 531 497 747 490 515 424 277 205 277 449 338 695 695 646 646 701 557 557 685 528 528 528 699 699 725 702 587 587 485 485 485 597 597 661 661 575 604 604 604 598 297 506 473 457 457 428 428 528 447 447 500 264 290 264 542 542 528 504 372 372 390 390 390 374 316 531 531 515 424 424 424 542 288 435 465 695 695 695 695 695 695 865 646 557 557 557 557 297 297 297 297 701 699 702 702 702 702 702 869 702 661 661 661 667 575 527 0 457 457 457 457 457 457 687 428 447 447 447 447 271 271 271 271 486 542 504 504 504 504 504 778 504 531 531 531 531 ] +endobj +265 0 obj +<< /Differences [ 42 /asteriskmath 132 /dagger 136 /bullet 169 /copyright ] /Type /Encoding >> +endobj +266 0 obj +<< /Filter /FlateDecode /Length 689 >> +stream +xڕTn0C@cLB"Rn[5jo+N)1C~fH]=<ߛy3`_}{^inJbU_~/UVã1/xjmq\e+ 7T $m^NAq1?>4[ 5|[o˖m{|,f;ׄ±'ق0]ߴ^RJmlHMƮ箱u7&h jWn`+k\L^f?tgƛ> +endobj +269 0 obj +<< /Ascent 714 /CapHeight 628 /CharSet (/A/B/C/D/E/F/G/H/I/K/L/M/N/O/P/Q/R/S/T/U/W/Y/a/b/c/colon/comma/d/e/eight/f/f_f/f_f_i/f_i/f_l/five/four/g/h/hyphen/i/k/l/m/n/nine/o/one/p/parenleft/parenright/period/q/quoteright/r/s/seven/six/t/three/two/u/underscore/v/w/x/y/z/zero) /Descent -231 /Flags 4 /FontBBox [ -1082 -328 6171 1014 ] /FontFile 305 0 R /FontName /BPBAOH+LinLibertineTB /ItalicAngle 0 /StemV 130 /Type /FontDescriptor /XHeight 433 >> +endobj +270 0 obj +<< /Filter /FlateDecode /Length 874 >> +stream +xڕUMo:WA5iE_a, @EoDz%W쮓ڃjvwv(7o<Ķw>Ni՟=E77^~?y^}궺~~C{tOJAuCa&w۳yg5E=;`мE1q + +>1Ѣb O1էYM}/Y}K֟_~zOJ֟BO3ef/YN|֟u\X΄rYgB>[bghX|&^V|ƻgg33qgng3tZ[Yogt3:|'>gq3Ϙ݉_OXN]X߄:?85JC#9u#28~qem@w8rMGns6 +endstream +endobj +271 0 obj +[ 641 947 716 680 631 0 266 376 514 514 637 729 253 315 315 433 537 244 358 244 316 514 514 514 514 514 514 514 514 514 514 256 256 512 551 512 430 988 740 654 706 734 609 545 732 817 367 373 736 577 899 740 730 614 730 716 504 652 732 700 1028 718 624 624 397 307 397 518 486 253 506 542 456 561 489 391 521 619 322 312 613 325 905 616 551 581 573 428 427 358 598 529 777 561 558 452 ] +endobj +272 0 obj +<< /Ascent 707 /CapHeight 707 /CharSet (/arrowdown/arrowright/arrowup/bar/braceleft/braceright/bracketleft/bracketright/element/minus/parenleft/parenright/plus/slash) /Descent -152 /Flags 4 /FontBBox [ -47 -944 2835 838 ] /FontFile 306 0 R /FontName /ZWLALW+txsys /ItalicAngle 0 /StemV 52 /Type /FontDescriptor /XHeight 400 >> +endobj +273 0 obj +<< /Filter /FlateDecode /Length 1012 >> +stream +xmVn:+t҅k>$R* @-֑T@, 4_9r,lqpWJcWctsS o/OO_)-請>绾y9S៻~qAZMN{~%M/qwXL!?xK,? !Pm9)Z_]|+TnTH`8 8@f%s +,Gds8HdS"&G +9 0C@̠9C +&e =L,8YCJ( U2(B2(!iFI %$\/!9R nHZLqP%/n_Y +q9~GD)RBR EvE6ӑd'VK0 aw,qX_TlGchJCZP +͕dyb9$4Rlc+7U)঩c3sѬN ڔmCZiMS:ܯO +vAv l楐3^G'eOyJy)37|ve< ׵o> +endobj +276 0 obj +<< /Ascent 696 /CapHeight 660 /CharSet (/A/C/D/E/F/G/I/L/M/N/P/R/S/T/W/X/Y/a/b/c/colon/comma/d/e/eight/f/f_i/five/four/g/h/hyphen/i/k/l/m/n/nine/o/one/p/parenleft/parenright/period/quoteright/r/s/seven/six/t/three/two/u/v/y/zero) /Descent -234 /Flags 4 /FontBBox [ -634 -312 6171 893 ] /FontFile 307 0 R /FontName /VZMBFR+LinLibertineTI /ItalicAngle -12 /StemV 76 /Type /FontDescriptor /XHeight 429 >> +endobj +277 0 obj +<< /Filter /FlateDecode /Length 876 >> +stream +xڕUMHW&+CKr!R{V-Yf3%`T~UޓܺclqV_eNw';~|P_}vw7֏I7 +'G>{?|<ij~̟SQI5t2֡zZuV?N^-~-槗WK6;9Z?g^s~3 7&p _KhelT05 +) +PX`8SDž"20r30J0QcK3V  4`lȄ [: hq +<#`ؿ,wn8 ֆp zYg !X)ι7.N{:%N +1$܋a a2Ag&kxf|Wnoj˜5?ӌ;, +Ԅ6id]E3`Ԑ"cEΘ8{B^oEQ1sZԌK0פc %Od)%gd)?^֟{s3u渗%Lu&: 8g /VrG+93q$g첒3?+9c#9ûJi%gx3r3͗JYI[IW3 =L3W3%g|3^*^*a%LoXH4+EN09NxżuCt$> +endobj +280 0 obj +<< /A 308 0 R /Next 309 0 R /Parent 80 0 R /Title 310 0 R >> +endobj +281 0 obj +<< /A 311 0 R /Parent 80 0 R /Prev 309 0 R /Title 312 0 R >> +endobj +282 0 obj +<< /A 313 0 R /Count -4 /First 314 0 R /Last 315 0 R /Next 287 0 R /Parent 6 0 R /Prev 80 0 R /Title 316 0 R >> +endobj +283 0 obj + +endobj +284 0 obj +<< /D (section.5) /S /GoTo >> +endobj +285 0 obj +<< /A 317 0 R /Next 318 0 R /Parent 83 0 R /Title 319 0 R >> +endobj +286 0 obj +<< /A 320 0 R /Parent 83 0 R /Prev 321 0 R /Title 322 0 R >> +endobj +287 0 obj +<< /A 323 0 R /Count -2 /First 324 0 R /Last 325 0 R /Next 83 0 R /Parent 6 0 R /Prev 282 0 R /Title 326 0 R >> +endobj +288 0 obj + +endobj +289 0 obj +<< /BaseFont /NIHBMK+LinBiolinumT /Encoding 257 0 R /FirstChar 21 /FontDescriptor 327 0 R /LastChar 121 /Subtype /Type1 /ToUnicode 328 0 R /Type /Font /Widths 329 0 R >> +endobj +290 0 obj +<< /BaseFont /NIHBMK+LinBiolinumT /Encoding 330 0 R /FirstChar 132 /FontDescriptor 327 0 R /LastChar 132 /Subtype /Type1 /ToUnicode 331 0 R /Type /Font /Widths 332 0 R >> +endobj +291 0 obj +<< /BBox [ 26.2553 120.42 885.179 761.67 ] /Filter /FlateDecode /FormType 1 /PTEX.FileName (./Introduction_1.pdf) /PTEX.InfoDict 333 0 R /PTEX.PageNumber 1 /Resources << /ExtGState << /G3 334 0 R >> /Font << /F13 335 0 R /F28 336 0 R >> /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << /X11 337 0 R /X14 338 0 R /X16 339 0 R /X18 340 0 R /X20 341 0 R /X22 342 0 R /X24 343 0 R /X26 344 0 R /X29 345 0 R /X31 346 0 R /X33 347 0 R /X35 348 0 R /X37 349 0 R /X39 350 0 R /X4 351 0 R /X41 352 0 R /X43 353 0 R /X45 354 0 R /X47 355 0 R /X49 356 0 R /X51 357 0 R /X53 358 0 R /X55 359 0 R /X57 360 0 R /X59 361 0 R /X61 362 0 R /X63 363 0 R /X65 364 0 R /X67 365 0 R /X69 366 0 R /X7 367 0 R /X71 368 0 R /X73 369 0 R /X75 370 0 R /X77 371 0 R /X79 372 0 R /X81 373 0 R /X83 374 0 R /X85 375 0 R /X87 376 0 R /X89 377 0 R /X9 378 0 R /X91 379 0 R /X93 380 0 R >> >> /Subtype /Form /Type /XObject /Length 1683 >> +stream +xZۊF}WstL v}YHޝ;-iI[:szJC@z;0>?-4;AR֒ E(?~R/ߖz~`<&cO*uyߍq8>Fnj#Rl/D z$(_HmIQ&r +VYvZۙ` L",aȗf5I|cE<1`tm{Ӣ1XTOjyȨ/ χ +)yX=>-oX0뱟_Kp (P71{kILHz)ލ8(r@%b|&(*F#<_f5X19gx>|rg@µm4t0];BH "UQN!Ӫ0R_:x4BNxWi% 9S+o=Ukm- 8]4:Zp =2UN`A{kp4C0\z]O1t+9Nɸ^1T^pR3f 10 KʑCi)u '.(E6[hȧt~d݇H +_ +Mb+g(2*YrKb SRmHLL!|)ܷP.obx2.6LZokA< +EMwozlJ!|: 8)/AQ0pџeOs0( C-]HC 8ʩxf$O)0^ӂ*~gr2(_(QlP,*`my塦<~ +j$xC iWyTFTMfu32˜RlK\IfNvisĕQh,.YIeLuViQZYe׺v c0 $6nzkq,X!(^[\֬2aDKu&\153CSg^{PRh8&q\iO9fq#Ԩpw#4={(X܌u,k}7(4>f&5sRu;iM8[f M*c02)2$|)ȋ5`cRbXh9z`wfE=,^/2}* ɁU mUƶNV,2C&3e퓸aXSfMAWpSJ <+^9:NzY_+)QXּlRF@R2^."M1|G8=nvfv,DFMnfjy(OoL-w#nfzTɄ4ė` S iCPKӺ81Ul5~YpmUu#& @(1 RvĆW[\|ylǡMѳ˻kRK"x-A2w+AJ+[B7Ra YJΏҿGʹ~j3vL ^&1FzJ(7nA1lׁ557<^`clC +^zLTؼZT'X6LcKn+")[N7hc/iQ/O- +endstream +endobj +292 0 obj +<< /BBox [ 48.791 412.672 541.955 687.286 ] /Filter /FlateDecode /FormType 1 /PTEX.FileName (./Introduction_2.pdf) /PTEX.InfoDict 381 0 R /PTEX.PageNumber 1 /Resources << /ExtGState << /GS13 382 0 R >> /Font << /FT14 383 0 R /FT8 384 0 R >> >> /Subtype /Form /Type /XObject /Length 3905 >> +stream +x۪$=pa iRA0xo@`a~$UJ3sz}JITGDwoO_ -6YR㥃[ToOQϿDg^>1#O>'`=џ?}~~;V>&t%3u?Vt33;}XY.P׿×$ @`X +âńτ*i f_om[]6FWM#E^Tj e ]Y_KBLc}X$!!=Tu|7f'&f3/u!Zj\\Y CZCFĨ "D;2bX/@bd*|#[bdEլ1UZpHs@:+2!&sa?Oş E`l|Jܵ֨:i,kT:%~뿑'ȪH C- Q}:orYC!RWb&@)"KI +&dl kJkfFʛ.zyM)T~x +b5h4 '|CUppIɯ Y x>uRƮ`+Mɪ8 '3@~ ^'|,Y>Q +@)Yas!!|Fe(=}cJ/h$% +n5ݧVv0 ^֚U84YMa?O}aTMs!GDXȲK#<ߑ AXl[.7p'9+㨪޶Cj&YO Q>.13Я9?SnPoNefD䨉P3Fd6OBa +.Av s;Qϳ-xRtm:U6&mmX1y钻čzkXc-\Emi" 0Dqw  8 C:R޳a5E}E1=hgXQ!,PmI3Ĥ@g_.iqDY6pbM @H|c"lmC 7TiDh><C£1+JM6d%2<(:gIi Xy ੸*~C!o{6}%x/{բ +TȩBN{!&<2>p:`/$Q4\6lNI>ixV*t-fR V0+K0z`x33N[a'* hX#n(8n=2mbrxnb.HAYDSps9=XNTx:3DVQ8we(I\ +g`Ts-:Tarȹeͅ \cfSOt - +> +endobj +294 0 obj +<< /BaseFont /WTHKGI+LibertineMathMI7 /FirstChar 24 /FontDescriptor 388 0 R /LastChar 149 /Subtype /Type1 /ToUnicode 389 0 R /Type /Font /Widths 390 0 R >> +endobj +295 0 obj +<< /BaseFont /MYJMNU+LinBiolinumTI /Encoding 257 0 R /FirstChar 27 /FontDescriptor 391 0 R /LastChar 121 /Subtype /Type1 /ToUnicode 392 0 R /Type /Font /Widths 393 0 R >> +endobj +296 0 obj +<< /BaseFont /OGYQFN+txmiaX /FirstChar 61 /FontDescriptor 394 0 R /LastChar 61 /Subtype /Type1 /ToUnicode 395 0 R /Type /Font /Widths 396 0 R >> +endobj +297 0 obj +<< /BaseFont /IHNHAB+LinLibertineT /Encoding 397 0 R /FirstChar 48 /FontDescriptor 262 0 R /LastChar 64 /Subtype /Type1 /ToUnicode 398 0 R /Type /Font /Widths 399 0 R >> +endobj +298 0 obj +<< /BBox [ 42.004 127.365 1343.38 765.401 ] /Filter /FlateDecode /FormType 1 /PTEX.FileName (./Methods_1.pdf) /PTEX.InfoDict 400 0 R /PTEX.PageNumber 1 /Resources << /ExtGState << /G3 401 0 R >> /Font << /F17 402 0 R /F30 403 0 R /F67 404 0 R /F68 405 0 R /F69 406 0 R >> /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << /X100 407 0 R /X102 408 0 R /X104 409 0 R /X106 410 0 R /X108 411 0 R /X11 412 0 R /X110 413 0 R /X112 414 0 R /X114 415 0 R /X116 416 0 R /X118 417 0 R /X120 418 0 R /X122 419 0 R /X124 420 0 R /X126 421 0 R /X128 422 0 R /X13 423 0 R /X130 424 0 R /X132 425 0 R /X134 426 0 R /X136 427 0 R /X138 428 0 R /X140 429 0 R /X142 430 0 R /X144 431 0 R /X146 432 0 R /X148 433 0 R /X15 434 0 R /X150 435 0 R /X152 436 0 R /X154 437 0 R /X156 438 0 R /X158 439 0 R /X160 440 0 R /X162 441 0 R /X164 442 0 R /X166 443 0 R /X168 444 0 R /X170 445 0 R /X172 446 0 R /X174 447 0 R /X176 448 0 R /X178 449 0 R /X18 450 0 R /X180 451 0 R /X20 452 0 R /X22 453 0 R /X24 454 0 R /X26 455 0 R /X28 456 0 R /X31 457 0 R /X33 458 0 R /X35 459 0 R /X37 460 0 R /X39 461 0 R /X4 462 0 R /X41 463 0 R /X43 464 0 R /X45 465 0 R /X47 466 0 R /X49 467 0 R /X51 468 0 R /X53 469 0 R /X55 470 0 R /X57 471 0 R /X59 472 0 R /X61 473 0 R /X63 474 0 R /X65 475 0 R /X7 476 0 R /X70 477 0 R /X72 478 0 R /X74 479 0 R /X76 480 0 R /X78 481 0 R /X80 482 0 R /X82 483 0 R /X84 484 0 R /X86 485 0 R /X88 486 0 R /X9 487 0 R /X90 488 0 R /X92 489 0 R /X94 490 0 R /X96 491 0 R /X98 492 0 R >> >> /Subtype /Form /Type /XObject /Length 3789 >> +stream +xˎ>_XU|i)'8؉ll4gcN Zmk*E/oTOcX ^<E]ʡoOS}BI@;TVkLp?|/~KZ?N_J,E ^u$a>]_aPA+/`.^ *lQДq,HNp@)Ƃhv^?L\h>T :&-OBA%"1Mn@Y[F~:p U\|Flbs"ðHf!T?.dhZVq̺9`Ȕ߈DX&[C}`i/7+:?޾_/zTڡ].?ӚN& ^>eyZD-'tMH_Xr@rZ^6E[F]a.j~C[~ahB=_mMrټ.nm2?ӎY?XU#NN]d 3lXENe> 慩ﴶ6;B`bOs f2Ab$Ȥ3m5BN~7戼a3K]+vq&qƥJp!  WaX0[6RRX@a4}HH 7rIDFTU EJ&aL&k0 PlE$A]!.s֬YK`pm&ܨɄ{u]! jM'!ን<]GM!ۓlpǭ̊gVײ$RmoeIVBѪШS$1mR\ 3eS@(6# +]my9@~g :iR?ƍ]X+}kl r~$0v]& +g9 $N^+9 h޽˻O//ݨ#)FοI* +$ehTg+'?Cp)\?V \\ J=DύbuٕZBErq`9z-iR,hQxg4Zy2H P +\~%BS\Ti +z$5 +>@_u6g`Tc-]b&n :L#}e57aXLBdžLgg8Iq'aœ9Pf$2t,Q0TڗqB5A4 ˃2iSi! 0N*Ps(H+ɘAbp5(b?\WD1L;i$ӛ ;V@m%HrؤP6fRύ5ɾLX9V~e[M +S rp&&OfC)ZIِ%)8_.$&$ 'b^;nևIa+kC鬵BǀIWys_[1 ;XYi4ץYy#_R<&@5Bi~I/kghg"Im8ͱAkrLně-͚V@xQu~{*tͩW,lE-`,3FqJuk)a7lZ`:p"B; x(Q + 3< Ikؑ]a<e4VV<# Z7EQQ"#P7ҥed*;1hcu䍺"_X&ׅ+bp"QVz"Ό`@6!ߖgFzB3(-m!n3@0=0elRƳlD*gdj72%PjʚTg4G BAvq4>׵ Te\f{uI&Y(`FGN 9_4 qō\P̳%k庠Y$۾+w) zR^Z+70!|))ugXrnC FYQ8#}$wL.2xJ/;uodN^.Wp9<외Y}YQp3*BMFSAܸ/#+ $joHv儑AvĿ)05tKUPH0=#H9m@2 +endstream +endobj +299 0 obj +<< /BBox [ 21.75 486.259 951 765.408 ] /Filter /FlateDecode /FormType 1 /PTEX.FileName (./Methods_2.pdf) /PTEX.InfoDict 493 0 R /PTEX.PageNumber 1 /Resources << /ExtGState << /G3 494 0 R >> /Font << /F9 495 0 R >> /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << /X10 496 0 R /X12 497 0 R /X14 498 0 R /X16 499 0 R /X18 500 0 R /X20 501 0 R /X22 502 0 R /X24 503 0 R /X26 504 0 R /X28 505 0 R /X30 506 0 R /X32 507 0 R /X34 508 0 R /X36 509 0 R /X38 510 0 R /X4 511 0 R /X40 512 0 R /X42 513 0 R /X7 514 0 R >> >> /Subtype /Form /Type /XObject /Length 1046 >> +stream +xX[k7~_1s%Pc%1bC44{ZIi4q,]u{"!cEbNeg 6iO~g}^6 +*<1 ڷ`c 6:LiAC#.Z]z+|6 +e]G9?=ii=ld1++xDM>!"8=l/qun <(m c Rt$<ĝ)sgt>‘MʈJ>#{Dͻ/veT·T Q} +.q u0S{cj}zXN+ ; |ﯫADL0! Ny=ɤBw|ѯs5!0g}łQ'8&Gw:aULGͳ ME0:mcՄ>A`bx;gAZxjW`]hĜK.^č};" + > ƺ28?LYG嬪 ǒi8N!+9jcy=Фe*+xy0IIM(V{yV_hb Z}=XV;bѮS`K,Y8;?.Gs;&qYGմ/" DSS4a*:^kb+_YEPM9c׆ւ`41? LrP]MZ +A{z֘Kr @_6, +#p*'u>ofle #Gcd/9E;RYh e jeȹP;?^G{M9?XW!՛ߋXem_,{zURMj"0arrhG)Gj \}$R_ y](Eʎ+Yqh)]Eù(8Pex]tpre8/C(9㖉;7Jq1]v1;+QYMO.1|?3 ºΑÏ +endstream +endobj +300 0 obj +<< /BaseFont /ZMKHJD+NewTXMI /FirstChar 159 /FontDescriptor 515 0 R /LastChar 161 /Subtype /Type1 /ToUnicode 516 0 R /Type /Font /Widths 517 0 R >> +endobj +301 0 obj +<< /BaseFont /XKEAZI+LibertineMathMI5 /FirstChar 56 /FontDescriptor 518 0 R /LastChar 56 /Subtype /Type1 /ToUnicode 519 0 R /Type /Font /Widths 520 0 R >> +endobj +302 0 obj +<< /BaseFont /THUYEG+txexs /FirstChar 205 /FontDescriptor 521 0 R /LastChar 213 /Subtype /Type1 /ToUnicode 522 0 R /Type /Font /Widths 523 0 R >> +endobj +303 0 obj +<< /Filter /FlateDecode /Length1 903 /Length2 44937 /Length3 0 /Length 45333 >> +stream +xlpf-vضձmmVǶ:N:m;${ιw[j^k1Zc֮צ QRe1s01pwe`adE@{7;5Q ##+ h +011+W%//-+J?)fɑp/͝]k͍mCTpptg1&ή@{s@ +h ttH1lmp73w;۹,< gss?r..pJr=jIs{sgc[-z4?*xbijۛ%Eu'>\CWPl+WWG^&YkłUNe+33o7r ,ܬVN.'   +9;ۻ1g35c)ڲ)_uFvk=vsA+3(,FsSIoJ4/i]nG6v.JQ{]`7񴑟P+XxD{.w 3H5dfC0w1bg$-LRAR #K6g7iO{ YCbI(=8F«Q_ " 867I1iS X`Y*2Sa^GfRgf"W^e:|:Sj<q~jգWπ%zqO.TH"N-z;_C0J/ +m&FXP1.Щ-Z&{WZU}78KOr@IlO h4$~=mNwBF8v2}:6ZnF*3&݉ i;Ϳ`ƻ:1"^;ֱɴ3ìvf@Z矏nކܟd7s#v2-ǫťdWNbB!1[_&Tf6*a9>r`XɫrOCQ]G֥+ INPEN909!.AvL1;^[`}W/n_* 1U\NqiBbNmX G3m],'8/JG)af4 YMUfoQ9{c5P\<8XK_;B 6txGdY1CR #[aj?xp䷭Vm39ԛcKjHE3;^VgygoP؈RT=EATXsuO% !0Gqw<,1ȀsCd +5|RFiv3l3ɸUQд {uCBE&঵?dVU>h1KR>{ͥ'P_9dT=$Z:f*ULT}#OGnO̰*61U%~QG 9v, CHIz(.CC缻\:}4cyOCh +^;3Kq}?Y9WRkBN%IV>W-2!{%)uOCo %(]"2dIJW(1ghۍ돛C㊷}!m1d,EP5^lK! sx +ͣuj> +G.4>_8t@^BY7 8Yj5c-V )'!-$Ά>Ggd&ԠJ l7PWrM cMe$7{Pj8N !#i¨^գS-*vcH|02bU1uMw\EBx){F۴MBǜ:uq5eoh٧u4tO5rJ.yp,QlW +8kX[cµ,~ciXZ:?Nq5QZ@UdƐ#GyB}CbhIh,ƽ% md~2tNB Pjw;=<ko?쾇Mr D6}ٕkacoڮXr<+o,*}of(P@iB-7Vp +*M;k\_\+\_;yOݘ瓚ҷy$ fq 9iab՟ʉ $H|G@/)ՠ]ƴk ETS%쪡 ~Sc2^ V< ~e`{/eߙ@w-'\5=ЌwMep"*Ymii vؗ&#bңo9&~nexAG?'HX߀6i{g8-yy"WBBtyÞOɽ1.v7BTV_Ie$B5.mBWT> q".T0%5;i4v)eu=Mk -]R<6~IH2p8r+1A[UEcQsvm"o9A˯ 0w6 2ZmmaTz)qFx=_[\5cϡ]|fc)m;IQ(:U4\Ed쮟{z/g7`Rcac5.CZ G4CvGH5E#hdB$N\T@yp&Hk09O`sx)AX0Y3;/+nAcG2 a;k +!9G>f݋pp}pjfb 몥4r6uy;bldKDdy U)f%3n*Fa" ˼BŒmԝ%g40gWSFeb0*^ZZlt +!ʏ:/(]sCQ@9[#V҃P}7pQ,4> eUCe'iA-]!{0q~ V-׃4=b 34Moꕴ">jcJYCtI"Ы/=ex, I xnm(6P^Y^\d}0`8_blibZ]j|Ќ^$!)oYvn/ʴgϢXP + +KL _(C=Sg?z$1 ֻt'kj=ISR+MS#`ߘg:&_#7;,_7sb""7܍nW;ar.2*gqdѱoAm6N3RޙdڕBhW;I#oARTv#\ь6 H(ӯ +-#iκ7/ TJ=g#Sd7*Z=iR|nMl[cv@Qh.A}л<:n}TEMJ#a+_WաGa$F n) H<3-6ˑdtuoŹńc*E[cel31Z1>8i0e.jg9=!8"]NhqoH=F6:t3 K HZ.u+ɘcWd ZGC=wmڵ0pO]eI +K5Huᐘ"]"&& R^ܓ92ʿۉ14" gIh,+dI\:E`ći39'I N|&١l'oABkrA-=a2b2^r)GlQž %z>1 +|?WxpPꃞH +I):o͊IwʾGgCq7kGPPVI+ߌ`ڏb-}QZcqëJ$QeI*cTjAL$^-h3fX&!<f@>h%3"a[ЄvjZG 8HvvIzfu4O.\fq` ͻ<0ÿc4N8b"l_D]xs>ס _K&JL]٭{fSa'{kCR]҇TPyOoMeufJHv Yv prz:BSlyc0U6H+vupw!1],1|iԴ7,H[ -)klm~j]%l" YNdqDT:Е> PK#S}od!g횸$%;t?ELFWhX͕OdcBYH?pԢjV?Cuah]|yq3/}Cƚ/n9]+.7Ȝ[W7HϛۼR w`Y6ePr;՛AgP ߛArUO~uw%:S?MU&a$:nF&n}ٔn-){U4cMZd^9`u;-{v9eFd ȎF`U8F/41EX#M݇ڸI3Hw4s'O!Wa~ԭ^(fTxuu +Km#騰R w%ylmVbg$k2fPBAqgeIʼnT@$l>%RC-TLqkvN0>LcWLGh6xUh0c mT/}VQ`eɄ?~3-qRqa{Z*e-6}(u\gE*$j֔`X%Th[.WB}HV'x>Q\U]u| S lj)][ x}(ą +ek`kݵfc\ګ';[Beo i99Nn:P䭀zB;s?P6";E9\aTZKMٮ>@ʟpldl68X 4uz*ov @ +gE)'3@C\G&0@S+Mj"T\57 R2Q4MfHN jMHl7bǣp{wU|xː访{B#Ԭ@쪆[kjٯg +dyt~W{24!M޺VZW#"x,gXқGi?6N"Xr?sFU/hP yvD Y4>e*eo ɉ?XRc-\8e92;BjBei3}DFVB G`"U =LOڣOkcrwpXzⳉJo8hVj% hmQHb TM: Y,o 5?ɯ$vrY7To?j]U(U`7'L%/2LW9w6g0ln!o +aਮc;δnda:3g#e1ЙLb]AQFK,_@ +Yx + +'7rY\@*H;8 A>:anTy1r0ߦe=+ 9ζ7BD_`HEWA(( +(S=۱BT#nYz%Fd4B P=/X Ѩ+3iJϞrGrzc.frC%3ЀO|UeT-ͫPE_sewkb.R;D"`~V3zIZgݶ>`.]%i Poeo7d3CoЊm^bԇsh" ݕҐ Q In",&-H3sʖ&bg?t/J%Dy31+!.nL 55e +wXiLL3 AӝX"dQnþ[vnr 121\|6Uw*Z e~$?j~bJP!y.K8ZMn'v#PyI}IJH=Ol^Д;`3ЋZ~zY@mINN7y9 =Y+t3[bOzFI?&˲*8mWQ@=]$7K;S#w _ +uf[K~{yh*MyDiNha 17 0I״`w~`[`' TN/Jke7ͬ XYg N]{n+?lBe3ܝ@zB\.X2C˲uN"$5+j2(Ei02[ZS}3)ПA֯UIS9lDd *4i3ĄbBg.Cbǭ'] +2Wߢ"H>fT2l6U}68O. Zxo҄0l<6*Rnca@#׭[Vܰc]*y~ t[f5>(_}nSIQBxNŢs4%Z:F'wCF=S{Tqx|ĭM|҆ӗYasj3wlЌ.!'MARQMot ŤN.Q +~ՁûnTwtR^Zl%Ҟ+֪ŠunBViboJ=M0+b c#2P'Тه}PMiq-b?8L<̗%yk05,1n\<q)=Lߣ܇PAR4b&8`лdi +@?)19+@c=q )jiVh&zd>Ť-[ 5vG-_a4uS׬ybʱHOĩ?R2.ݚk"KI c߻?i-f\wӿC[f.eׯ8k[ٯsVl2 6A/m$ jxU9H¬ԦuֿG'u7.>R2O#3n| %RԒ\kְ ꀃE6<.ɛ[] TD:vx 󺿐cWy+ч+\DS"3`h]&zхk'H( +o؁-a,d^4q=vՀTb%NZ>afHі_aw|iltR3n +_.A("Sw ]\C+|3h + Bʅy!8qDvg?/-+GXܑ2`9¶EB3w*=yMmW7[qZ} oKY#e9QHiH}[-W!4axUKاVC ]@ɈeϚ)HGݪ0YiFѓK=r7ĩ%űY.ZHLjT +[&|,1&݃te gE/+0WƟ2,U<Gz'w$؋l5)ii˜lһ[v!rdV3l@1rGR*Q<B.1"Վn?/]!kV3\]ޚ$SLqWbmE˭PB_:k#Sy&"syEM{aN}&ypq7m޸Cڿ;:kPtg!)3q fO:1-6`1'r;Y +/>bכBrnQjv][&1O[`]Oc+qEy6S?]LۮYǙЋ\zUSJrn~KSP0Y9zıMwXǻD(l%|\ڧI B'>D$AxLUZيo]x/ZѩA侥:tg]TIQ8Nڂ%M6;_{JyX"ϪJZn=5F8*k_)Ar1Q{\`swzv-aF ռ;R@"*+z3 y:Mן7+Uƅ "j<(5ᖶyL]rҥ4o[s2 y=1w|.vI+L~85$ejc}B-1tژ%ƤO}b'؃w0an@zeH!͸rN%^-ҧvf ǰhޒ+z] Fc$|?^ι9_;p~${J_-%龇m@N +p6hܮh5s>m4Pܭ?pnfU1 D1{el0f)ߖ[2} y?D7l?xe#ju2CeI"B&\>q|alxJ|fHC6{'Gp<+l]8 Cُrqh,}%>dOiq.;eǽiA qR4W cv5RD.v`-hj;:|?ra[ B~Ư+y +Ge!GO34 [H ]u߉\dpUR("q ;9|B,4vT7g% hoB +E}] ׼ZKmoN߂+N S({L?0~NSt÷WnKf&O_Ms4)P?`Dv3]#ѳQDfbji8fK6T1"sg8,n3GKC#b[8=L )?S,IҤƽ +"?͛եt#ͰcԲ$i_\?<aȂe^6ys²5o78B̫zFy,W:2^Q"U@B9ݜIh"/ +5p\j@}B:P.2s +'UR+'*m9,È}6?vf,:j"1:tćҏnae 2U@ҠX!(;iҘ69Nij?)#]!zЛ [#KhYhD:Bx`73A8~Hv=n)IrNUr0qe Fª^V:2+աFUF:מ[0=-|mG쩏d;bbkfvÒy)DƎgmXJ'@^ Cd=xUS$"y6l/13ncF!ibtJ]t *|S ;)usl2hhXV\; +D!j4`U=B`=))Tt (/i6;Ghk%#0H3Y$baATq\<6Яė1LU+s`  oշq,bZwG +a!xFJ## 7~x\> laGsbKūLl5qS PU?"fQ1q. sMsٵ< ?g͖>b=D4!X\UƠC 7oɂ9t2eF~;վk/s_tV~90?$e, nz}@!]2mߘ0pZYo2J8]"L ػ\3i,Dj$~NzxNY\wnCO)-f?5Mu"5iA40K%_ZiI,m0C\'rstTkp"aZ:I}2݋K +.OެӅ #ifS <Η+Mp䎮{&ڴ>0' Am*j&c,| і?%B%/O432pF\zt $t\YZZZp9Ql<'ڍӏ 1Suۊx`wRTh*R%=5es$+w=|))ZSws|S+qcZ,gK +bKX@vǜ+WTbSc9x8#<{XhMY@\Tz ( rV1Ơ\tO& јqxn{*|ē1#@L3",V=)j‹gbװ6߀9ӇJL%*٥$_@[hl/ͻ ZKÄ4Q"]F*T<eDu*8dXY{f:%2u1ڠ.k[ɹ#Wv}e M l0?[N_5I /\hbqܽyL}U gYs+5ZMI%vh `-jn x蘅YQpD)UHbWoy ~±fZ҈r37$cvM]Hؾ;.9NvP}$M?‡wꆑAg09 +\ Nӹ3Y.<"c\~%Jc-@Y8<"wQyt( +n{ucv)តlaH9-g+H-ty Q\{R2!3[G0(ѓ&356" +Ϙ&Hre`]7a!RSԿ9t,vlwC1*R<32O)qğd=NAQS2ɬծm(ۇRD ߁*+ ~Ǥ([ yn$܌׮% ;x +.L>D|Um/hdJ?=g'CwEs+˴4*`+p~d|WmGl9dut[ CןQWl6fTҠ͟xyʳ/5@xA,f_(U;8 3ڹ~S<4&յk"E29dk0D+^!/w5_n^BL%u fn;==kECXEt/$'}o8ٮՇ!_4}k+kYϫ9l +~ HJt|b +?5ftdK,_bNd_z) +aXt> !4%)d@^vt'_@%Z)M°}V>bt=UǷ&ݱ̅6v)anx,nf..tt} +f@(艌njNgOFSQK]ix`-tEHG +Jl5#aT$mQL:f67'{a -L}0/ΏHuc +Yfz?.iKEY-۶m۶m۶m۶m۶m{Tp-[Dd@ H%g~E-H-].jԁj"L]ALﺚm"N_SG&mNCZzz.N>KNr jfi#ow;cۇy#SSDe+{&~;D.\)NbrB[K0$.s+Zre/A0|j9IC@2MxYWr6c6&3p>zQv-eݖ|--TvM,}$z){F%3i@$^ik ,3s{٭Zb^) kܫ`<5HǓuuMfr=C͆rU{gìP妖69il-";Őɷ5 ^luAS#JPJ0HnݴI +( WYՊn->@v7rJs6! +j+(ۯH\,$}āoz}~NާYlԆ^*tmv(Aiu6]ܱ6樹DbKxgl{i=?0w1ޤ$Wܱ磙u+?uB?HU6ntG7?x}ih\>>Ӛfۑ{_C(l&|KL-E2Mw:\ j*e@fTev5xӍ܀?yY@'WWSL=BU+EN" d%chߓp(>%%P5ځx&g (ڛ2}s6#<2tʡ.QVА]'#Geٯ _ˣKչiR!n鏻}_tXc:a?o1L`9,|\hlP&׎"30,h5KXw'J`@"Y v._%jeHԡ.H*hx0Ũ=Mņ0^^ֹ ǭ'9. 11DK&4||+e_5?m]@_ierC4eݔ]¾3&"6%aHBZUy;X8~ (Y<W@{& +6.#\ +Le56p =ݱl|49}';Z]X] fW9k# Nwqה:({Or1WITfOuJ FӾb,kéJy 4ZOmocx1[~Ez04"wo(5.zB`h|$Es֌(^\$_ *8kYu!*m-h[ +PAxpòC: +sʈksZ ӟ|WasHC0fX+ϺAs~%űa @< +d4DI-fm IFMw + HKSŀ Q\Ug)܀nxPҹ k6gNkm F<ߊ!=`[A?\YiiOE8Yv Trjb7J>0tBox_j(pyY{J>pxZbiʦdƺt xa.a H(G!=n|1Pc1&~ԖK0!-B`|u +)΋ϕխ5q#RNfu0-iqJtklfr"vH9WC^B2d7}=H,?od3Hg n +GAkYXFW?zDdnlR߹hĜ4O\){o x5>#(lpF?N`4 <2"&RHBxp71T<ؠhj P)8ӭX7lo!Dzgu'3QZ':4=*@"NݶGFEټhH3u*мPf ߎXq2ŃXq_ɚx\V`X 1=D~6?JA? zcrSE:YfZ T4w;'ɇ%"#GO:ӡߴRԯ}k 3)NaJΎ [952`>(M1/DZs1p=[ +#uBx.AES6wIyS1{p@sμlTK9`0x`JMQTD=:g'WcnE lAx֋7׽GGH )W|InEkVڞ,ַ.˙&sp >[&}-ZΏy?mlWaf@N%7{'R/TfѲ +wr S'(hʝi`ɪO9;Ix#AтM`_)"=ቾP:4,pL]dO.Iy`4ESn/jA;6ha*?ŏopIdZh}IpN ^< (e݅7e,m^HvRpH^ diYow쉄 +pq< 0mEXN+g$}=thZTNCT;|2"ߗop:_dA߭sM;kiMo&575Nt ^bI>Ҙ,d A[ioqIrf*t]{…Jl/sTF"÷ +`ܑ vhΒJ{ ݁L5M}ut2MMX+vM{DՌ?Ӝ!R= vb%U;f&lB>ơ8`wB4=㔷W-v`p&m1 [!`%^X{QVC wmLe&5N[ڞ';?Q +>pwVY@Ibt47L>^J֓2}եzɆV&c-ixK(8)v v &W'6;!o0+0=@o_F@PY+;_TGDS|Py3^i_xV*8֮`ڡ 10~d?#сv/Q&-VH DL4%ۻvH!: $lŦtRTO xr&R0&@ѡ&sҡz_6 )OwwŸ&i=_oj<!9(e _> _-;ofWEf.n;iEu~{ߛObP-njI)AR4$H*)nogdl ɱ߆JK z]]wQwX|WZ'*4ʝ$d \?q,IZ\pX6Afzڶ &]5ڮ-ɏUWKRH.lR$k$(l& 'ľAP(: +ɿ>EIkN(n&w:(I52ǫ;6u_VyDnɋB,qϺ~S?4:ٝavvmSv0[VHr +6%Lsy74 |v\;կ+!6"WHt l8"Q(>x }nq0tI#PVO^-#w䣈ԲɾFtDx) 0KҐx|14EypF_~^3p`)e@/r8G=GĄ\)SmXdr_~qâ}oJUP?>%"zL])D%А) Pk2mģ?$D5o@Y'c3B8nrP*]:YE-h d6uRbA8.X#xJ.DV! ] N +a --%#/?%IN5WP-V>o@Yd˯`NĭIC7%fjA#c6zBFO,\ 7{od@ޱO/#s7uJIHRtK|k[7T +L!NjAz]X9["*J(9F^V,=m`>y3O(qktC&{ښͻ_N*@>6ȇwhb3ӫ;#zNxz%y᝿uB~Cǔ[ [e}Fu2a@uRĪY=|k}qMQ+/X!Bjb 'y\yP%gO\@bHøZ_&Aqq[3Aaz$&#uB̓XoeTi./ME%NzV2;jqBCp Wc }X'V] ׬4>lPٵ>inAbbxBdնM3 +y\Ai*tN1^$kx@ +@9*Y@%$- yXwaBR'%y:T;&gF3۱G{ݦȽDmhv[MǘZ4&u +Op=@:P{qr$v%/(` (#Dn.3&oev iiO^oؐ |̬E%;mxU2\^B4|r {k̵ ` g>/@]$S(o,TU&kG6T$٘㉚"4UI+ +L,CaXjO"Lމ21ɣ#4x*|bA"<'vqŧt +rcwInz~86叐۝iĔXgQvXx YV$^Q,^бEp5\9fU=g[9 ܣ{: jzm¦f"LҡtCe +8 WKj΂-V7~10 ^-.-^"b=i_onS(?bzCxH})cEze=_KBc(-=₄9]&Q4OQgA`̞4߲ 6%UQwQsxR& p0̢GiS˱{-g:_O @gy½!a)ZKW<em[[D{o̦+2 qƂBJ{ʌ͐}6o1/j2ІjO2]ַ~`eBbjk8Q=ׂkMV+cej7BpR]!F]'/J2S#A~[,Cz0XNx-BК68&Ez3}F}3U/ˆ#%Ys#8ZaTȈ-gEpz%$cS@YAZ!1/LS-H4NcQ}Cƀ>hK:2垹a- Iq~ز ~%M8ĺ2f]n0|EC7j.5Er)b-㱳gfDMƺ_)/4~<C'imEA8u u07^]yKq޻C}>g%p}y!JYTD:rKN׼ȡl!(y8Jyr(U8YÇgJɦـ^oKmqEw {@@^RHb!oO&t A )d mjJ7iX%k  tL@!/f'wuFgkЅt>9+*z8{:9~k˃\ERJ(aG-$1VX)BO\X NY'xSaГo0fiWmE'g=@Jb_8M:UË́ șTH丧;ȞSz0{+f6;-,Pfɯ +ϣ+ s=tv>FT +]VI{=U$JnC䇘{VUyAM=LaH t7Nr)@n)NgRu#y1,Q=9Lc^^E=x,λX퉓km/kwHCKvVZ7꟔ wduy؄4UhuYT4pG 5K޶"sv'A dhgԤ C;zA@E gvNOA mOQv{? +d[-4+; f>okqrz4y~kUMu}bRDIא㻏Ył"E꽮.Ϳa-a @~nny'Œ}!1vh3#- X A QVtV!˸S(Bio[FيoAƥh_+Y(PŹVʊg2I1)o皟}l8 jБCX'D8@cB\/r.W(~:GSyxH2ÕnrhlI~WJI#,)R͡uIE&ghة{ yO; q]L%CJӈwMi=0is,k|K<%n-`wvd/O^3 :hŭ$gw?Yh/L  1^e!ٮ  b~l44-UQ]k݉پRFΟ E@Bf4{uy^ +'Y_>QMr_p{I!9&m2Qe"#z-d/Q8*ZoU "uhX8b,< I{z# 0M^`Q+GJ1#2D>9kCpiS F~9"T~.֎g:n?-,N&@Cꋵ,+-TzQx ] Yկ VH@Ehm),2$CՈ۲G$Rs9Lb:fՔ) !w^%'ҒpI /Wr!],^ +.R^JNf},ɻ@{y٪=%Ae|qOh"2Z4 +3;IB]*HiDM@̿~4iqs@hVcy*:R;{rfW)Śo9k8fZ\3(9jXfֲ11!>]LtW@!{4⪾HgL]&dEl68 {`c.k%un:$+JV(6q#Xs@0[+\/C^ЃnC:iwIfݍAi2 !1>Sbr6Әg4(~_ہuKJ`~JOʒЅS+^14.c&EMVD J_>eީ +vZj>ͺk0f0E'V4"E[靅P=\l?ۣj)I]>>W'c%Y0Kt /GuJƟ8\Gij´=n1 XwCg10cOoz@[VN:Rt~L'(LU.b;m;Y6ґ߀(^"V qY\kVEz}FmqM`DW^,1:pկ9S؟ņDb, lLl--2*x/Wf):5;q;>9 +)q{l^NjH|Pω:h[tyj9;j**qahɆ,n0&bMr!O FK!OaB|_Adjh^޶ØN +[Ǜ4*ɋ~Amv62!LMJ(]lU߯}:Rc % wۙ ES7LG&PVzz JT ԉoW]mEݲp#KK^zU |$ȩ73ݸ$^p Ln3qs-N: sOQ",ȕ11Ub+OW75w/("=o&TJE"YktB4#BQt@j)cs)Jn뱌noi@z1lN?%Թx{6 XC孎sG!=]D;V: Nu)߉9x '_BT8oR~)i*KdO6 ;C-t2fVbv Z3y܉Wӊ|(d ë@gGjDQsjgW< YR0eRL\š4=2Ye8W@&Y:,IoV мNPl +J9n{,1p(6wpNyí̷̀pjR~yٺ$Ҧ,z^}evSL}҄%<]L""ɋcJF>rd~q?UPש|"HC3Gy +|ζx^NOշ-b& ceX[v ٣(H ^lX!{*H58@*+>X[ŁM-9P]]1;,A]3+̞w5訔ՄA٢! KТ~xd>w6 ~U[7TҀD.Gޛ/?8F2$,mЬL&zV-6*ۊv6K  +c[MᨑT'7'jÒ*{ikwqݻv"̑)&f^H7ȢyQĆ8]LP>=FxUzVPt)Ů;D/qWE]p@96n.i+SԙVpv)5EʔHʂpkI$P)c:Jt& +PXT4؛t"\wq@5_N9zB̄M([RxmEg)5@U @y-dU^Ӯ?77h*^"yb AMؼima5PjTWk)~".+"Z8w֩G%FN|SābT +S AuM`moQHS-eBэg&L;+Oqx|^tVoIawC$:}W;5dmqXBYp䋟A%{M3MKx,ebϝȾu؇ ,(^Pr2?F+E{ OEaF#zBHR7)9fb=s<70YMM T݊^f{Mw +5 qoMzUqբJ.ᐕmW$7AӉd8B$2 ԊiQxJ.km@i)/||/$M 4[QTv'uŻp +T8(B|4݊8bDʬ<& +II\+`OL>[C?sB:u 'sӖblo1 w+6lJս} ku+ґ~9wPeCSpx9!چ- !5UY>6ZV:7-v)$/\@ո46##7,u6"; )) Io)7IgT5/ ,[T%}#S#p>T #x*xK=U!" %z$a&XyH0:Z%[Ƕb';m۶m۶mm6o>A=ƨ) Oke8 6q%";]"`@=QA/ck[0}زzIZp!iVuإ|+G!Jm'lv} +]ѰsI\D$nb:@pNs³b sڝT)HۃTk 3ʢ64q1^RAܼ0>/ň;A$i xmጾda`~O#}ઠI$5rOv #G*0C؟dK}oe 6AОC]R,fDtXMB U"-hvִhǁ8▤)Dihp^mf3f0؆ݡ`1`\ d@n"i"M +a‚+;bE{^тrhiӼ?եc1\}8QajnKfS6| `bD&lοi-AAQ]Χ =5S-hrC^jԗ x O9In +@;:Kl$ȋb6OqpJGLB,VԃӒP=z~4W dns_b ΂x +^W;C+P pk+`p  d3Ks8)g%)uw`-&̒|[ <+[P!<$ɎGwde42SP/Mx?>Äq+?0mB+yl=F*c/M%_ełڟ,82# +jq0ʯ8 ܎[ I+m( hp:.6_ţS)9h G!Lۂ9-ZO{yHm\5Qcl;_n +8 ls01-'4tI(2#\*`4w'T]{*٢:gFhjMC  7V|4h.џ<56ZWi^w|k#D&c6VGIpYsIgvnѝVylB>$ DrsL?Vp|:h].۵*T+9 (*je #{-ƐZhHl."!#b.~Fdѽ[uEbCp&E3CO2g&!r#ES"~K,%$ŋ+ 3l"'D ]?sn[ͤ#%[!DbI۔}):{q [%0dpE [ȭi/*?Bjm5غ ujܦu&$xV߸@KMm'V%sɛOʩY썣͉{[Ynhv$Ą9ZzRleisѿeʟ@ b?mUl&C!c=hYYM2<{ E-jRKEw~ g~8?N F8`7jp)6'fN:td0㪭%.ͫy5aY=s.1҂#jg9hivQodcsB7.BZ/sKR2: 8fNGj<'$Y-L@I H,c [!J'χ7+<)1=;u$yċpcH'Ydģ\W%J61I%l@`U~Sq L-|ӟwSid- c)ˎ+B{OnTKFuΐPN1WE5%߷ȨM^<e"g+gB'3ċ+xsW3htk&H+J RoP;0U~g BA%Ư~Y={ٛVﯽPlr_5".)oٶoL$\w82I0X%-uIH;HG#z=P =Ypm=q6kI +yRky8&y^UjגzN 4Qu}ҶtpyK +ghQwaELy>+79@O]9A'/PS9Y|ejCeLN@ y6y&p%9ط8M|{ԕ+YRW 1fՌ gOI^OX߹Z"d';:H6i+[)8k6~ߊ<3GRjՁK&OyMIPJ}(6Yvd!X~o=TSW)sdQ]=UsQp\ZB B"kp| +bti^( X6x7=ٟp%~T0 Gg )FN6xg,#6TDKR[ V/,Z߽H3(>v-a9ȳ?9I93HJ]+x#q؍s;KaA9d6&h02) nPQyoսW(Q?[# E !fEjs8WuZe͎iQat^V>8[Vߑ,s5,R !M`ūr8[oIPnl]&iS'NWȸ޴Ƿ;^M 2\ n#kMjMwb*5㉮ᝤ(_Σǀ @s o>hft`v 2s7'QQku9j<L2#Ց:y6\~"sPojGcQ9ɉH/'[!}b㶦'bP~[]͸ivvf# TA ;_{^l Xk%#}_ {J]; E2+/0; <),6IZaԲ:/aWn;dGᕎ;u)9pWsxs:QcUu'HlK[V8ڼ EAJ:=~|6Ĩ/:+mTA5\&={ 2nRxA_ͫWJ+DAB]EhօU}kSWd%09ꮧY~l<"pa eBL̝{p@͝p}sQ bِe5i|U'0_4~6k] !iHؔJ룷:pk%#ꔧ̱UGݩ{8_sևU+ZfWYm^JL!ez+*UK+~79 O +anYX3'gjov^դXtɪKvKp\_]H^3z"L&.=LP_o+xؘARc&d=rQS>lMRo_GìO$)vkil5,7>  +/Ζ0.1-´AAP\ۨQQHC̽_*$'ǀ/ie*ͳ{-usC~pHuN66b2+gjLf)@Gv'Q0XܼW'|ﳪ2"3.DC{+YF3WБ+1 "d.Bi@ݒ&c؈ Z>9Ϋ^՚̇H{#ƾփл ld*aU䐤TgN +G{+WRz&l $c;Pe 9E2y,A<jMEJB -o:r ;Lc'[.UKu@C9 +0MLjt~?Yi/0AHQ&st&x/HJin^j=Qv@ c֍(g$Jѣd).L+(t7l[>ߟu\4]\* y^h +~,q!I?]P&V jgN".!v_PKV +7VYzI*`R#js& \r-# *`x'ovԟ j-|SD[6\wƒL }E_=#1xZ,@ZbeziIfejJ~vK{:SװLx{~ӕRԮ5O5=wr=o~?*Qr Vȹ فF19LF t2dEL׋9W܂H<[[A'! %pub!ݓæm>Z<ddb禙HE.MEx&h]:#~GOU"Q~=*ӷPޭ^dZ_gY3 +cp2 ${`,Uխskw&oq;f@}@1"%xN}IԲCcByR+Dy.tzu# l)9(a#]sdD ˢKHy3ʷ_uqqG=+ih']lILWs-GA溄q2Gh鳿ψJ.:A+nlEa`(I$m#65l`mA#\mc\A)`tyxyo-Z"+1V0ff,dr!"x_f1[u aecx)۱`ȺYPt( N`a˞zoS|(e!y<%}A˓) ,$)x-NEEeBv&4iSJt|KoR{-+y3 \{\!mj/phkn)̀c36^llghjK.ڪ,H%:Dp[2_I< np:)ϒB䮼M V՘.bWrq7IUҼ p(.]#'59(Nj6:pB`A]>(j#K[;^,?bnsӧN{0@ %s/NNa2E0LI]=V-ef6B)6Tj m<Bt.3(9a7-%}q[IA~ ߃|.9x!ysF#@͌PRw| Ian\ k Sg33'>͜"k!:x2E8qTe9@k˫u-K`QlusFd4>h[T_!#n‰1A 2Yy+SRP$/K_2ɕQdrx{:q dSp +}{Rы)ހ&˚V GZ,>.psrNpU:ދ]VW0u,ΩL8ςxAbP`_A %5)8զ3P61BL8p&h^HѪYs @|`;`.6. +X]PWȡw|G[nN1o }"i5{zt d6*K4ҽ^W lt}xcTADZR[*ǵһ;Xb1ύ +f A(.2~bq,j' i~D!8N +:kp`k+Λ($[zm6^v1eѸʳwd4 + HRqPيCQ#--c>fRIQkZl/(ι_5 /yM} Ж9q1lx]f 1I$hK?** IIWb4V0uX[ɖȇSS71<.i!/Z8EsICEqh#ڷ -!.<`[FYfNhq*R *@cnY^} ·DV^1ԸQ/tz}~ ޤfhyĴXxT(d(I2ĩ{>6G $ @W"c4ȦH =Tiv%\ 1sڗx$ձ:;=I0 ^6EFOISJczp4.OK׆-yPHhI& ^D| y7ᢩɠg-^p0U?E{n40ڎ8^_tcm \ڞ1RșL݂':_nɉdSxyFj޷M3ՈxEAzѾ6l8=J6Lj0ŶΪ5&2w,1hMj\/tY SPɛJRПJN:f'MPOsKMg}Gvdyx ($0fj(1jK-; o %vY, _sײkG8M?"mmeaSޜÖm: 2/T#_%dEz7|?,.Y0 +wtmԆ'\Wz|] ++c'%ߎC 9F _`ک uwǣh%rtBAϾǓ$!A_1ٷ:I"(V_FY^ACqrd&g@{խ0n@' 봸"m&(q~"(6i`nӐwMD+wVm&kSOw~vK^:7 vKnB-$+"]OT~ 6yD[>(P\}^qgd{VNrꎀYpZM<)ّru4x. +gfvz̶]1rc+ +KCx,[M輧%8vJ(-sv/nr(S](oQg:#[ā0ӄ|]Ya)%3\dmgö,@t_f'1l7(6qFndq]C+q`pTRUe 98Jj8 ukdM5_5NB.Ύ,Cz/6+ kz,:JTʪ`KVO}dדpd$l/(gYmrہ=cu Y++#i%}q- Qx)R3k&1V|g3x\38(5$JF-zV0Hj 5$Jh52س +Ю>WnJu`=m`Ctqg6QsTKVڙdP3G2eOᣚ01-J""2"B喵&I +rHu4hBZ/Q"THK~H$ L-{a|8ea@ T3c |=_}j㇫/XzGνSxJ0ۚOoBŅxJ+ r˦]<(6|版ax̏/VJAϕ䍤(cp`ePNPÅߐdK%U+a}ζ|]NGCQ 교y(/Zr)A!Q 9Kv\p=%$ vՙ2"׏\zNd,&9?]6D>V$\jR|V >xr1WX:+봅85E/Ыc58en?,zHUFӔ5h2F34q\X5Pwk-X# VwQ{ǃ!RG!l+I+Dbe@l-juJ|47caWw PJ$ i|_L6VSY %.jLZR[jCZgf[Vo˼6o؄*~!JS 5HLSh_noH/E@WBF|ݏsS $|٣A&֣VC1PvOj50NtнpXM=Gş-PE'~̡%e9. P^VPS'>͛*Be[٠:=eK7 _lv;xbW c&ed+{y.c%pA YQÜ=30"5省m DUQBV?\suJB_ɭ؍hW`k'*av;UYhcQhUp@y=n4 u#֥=A@+lr_ڟxǃ$;K%= 1ZUsC߯s - iO(g~jwWȞa:LAt_z Yʋ +F=׀Sݳa[NVFI (~)@,o)H~hT8 dedp!A@m:hǸ2| 槠#4>¬qhxޭ#BmڐA̝5}pv,ᔿsxw S + +Q m1nO@i쉏3:i2L(4gH6"xL ؍U Yv:5cB_&MVje%?LHM +endstream +endobj +304 0 obj +<< /Filter /FlateDecode /Length1 1748 /Length2 101562 /Length3 0 /Length 101623 >> +stream +xڤe]5UVǝp +8$le;NkVUFeM=%CUG +/ݽvA&ydH\1|UNS䗮ρ?ͤN/P%pQ, 5B"}!N#-6tcn A/VD~h\ IzI@n=`?W9K%D&/׫Ѭ-( MȎ %&# V64/czA3O|\jؖ,/RuȃoҴ[d>mӢ +&׉cSFttOa vd~g ) NR{hMJAq*GΡݨǽS? `g9D' +w)jRX${)3R,t.l=QC&1F2 /,:B&ӣ𽛀w:3Emq|lș !V'=] ,]tZ_U*YJHTIbbC@:y!rxTS 5N5y[pWV"{3IhrM9&l 8(䖄kM.3o74}L%sRc)n_73Vm']; eX"* s$ +Izv + ]KV$f`3'Wj[.#sQ^?K<>ك7`GluRG)AK` ឮZBc̅P,־ʫr۽@d*)$ذnl(jAN=1:30+t[ةSjnq +@ .ZGK[ ՠ1c~>)o/'PAn,Q"ah["PY(C O +hqvgGG6r8Wr +E9(\̵CzEp]xB῅uBPŨpzn_B{9+嗧.^7q _cXmiI!!=CucK>͏Nq+Pe1oyngJ+Y׬P:SSl?s[?'ձ݋ÖꍯfۧN쫤,)`ǔƯ{5aͫp?hAhJ 3s}}:( oJ]5EVP!ˋ +y):-HL`QG/SW߷՟H{Āp֪:2bB黪.ex2Քк?SNi-(I\>})*4m[#v(Ѻ=UFT_ +a֨ ~N +HL!pZUaKC1*-uۯCrB_6-EZQ g-fV7:YhJwyosC"C$/\Ҕ.hPmxcOo9Eflғ3u۬G Qh'`ϫs8ky`meX`Y} 6*[x*5Q54#e_~ <Λ ob%,jzV4=u@)x@'ĮnI5kioBC*WrB.Eqq8ɋϷkb4K6NBK';fd !"O{oUHk~8^eG8|R^c +PcpidQ~ivOyra?DAxWDpWHvc[:o<%$QΡGOMreU.ϋd羆#4$[~q"Zy0QΕc͆HEl\5ixTRƕ hFx찅c~7]"\1Pxw56Mk،068ăBO|6-zVM2"!\0fE/\Ók*ߚ"\,kӥӘz CˡH&Qz 8ם^#}IW"dDOV9Fa'(S*9r4Sob\>v"kG X'`Jc?n&혓r:O׊jmݵ/IU4зN3(ŞD +zAi)nDjˆLIWzSL4=~HlAC-E"'mJ n:FvZ")1hRZQl} :p+Y)&a]VA>? +owT!NKǰyXkZzIMden@ jyi#q^މlYDܗXcc@vp6ly{OG14zXCHO 蛨~4=9~"{Vž{Q1&ƌBywHάz옿M4;&/@rlcCt{făc9ƴ"sCd?TBuuRW Cļc e b⥄ 72_K"]:,k0̍7%B[0bݲ4E7ϝ9B־x]Lg [#0Ǹ?QmkM#ߣ~bMSSdF0[^R1 7j&w }x:2Lnf +_Q͞j3ǮOT 6'F+إI(p_[V„a7--wB~"xlj`ĥe7<^~D4vfV.WZ`+UA߁X +!O>]Dݙnhٿ>f\*•A^:C"QywHꠍb"iZ.:g[ 3ܻoAZ; 55:-2蝂0XXU0ûmⴛVkG3TG,Ƕf>(wiB WkדVLDMT `[tМ2i/ZyCVF0񓋅gki“q}Lj>/ kW5S8Z&wu^w*CkKbI!rs.|H [̼մ|fb$_X; c,'E^W@N +QIk@W x%)(LNwv ʚV;8pzXW̫ܶ7J 8'nSb:ETor]E5p%Z˹nc ;1`s=/LR&.%!o/T7 Nz..r|fXj|j@),\YI]plEݣ.?>"ZBvP(_rynd]e[2ls3Z0^TtUQ$zYrY &ez3r̊Yє}#k 8J:AkKu=k <{9ϘK(Y'R# @~xYVd,JϤΝVb8gG,v^#B[(z** cjufKӉ+\|E +67Yۼt;欌}h!e}>nìj"3WZJNFrnC9,ۭfmMin&޹[ 噓 nPxeW, +48X~f} bcԅZaKcѤk@\ ?U@Y]0 v%>, ꑹf +`<vy<Y݌Դĕĭf^a8XI_>ex| 1Fσƨ;}tA1I])a&=#C[k8k”g3Tț#$Gsܠ6huhNiH)@֩mbI^/o`_~'PCSNq*-ls&}l,/?FDH쳲pta蒘iL⼋[ ~1dX&NXgnjr~Ujlew +Vz K/B0pi78a'"]xBb><샿MMXݝ Q1PCUYK֋>>4˲ US\%0m$p&{KeHM{kV&D5\ц5x -)uClෘfANd$d+]~:h0H!w&ƸFיņzMzSW4yoX@ (|Dk@]RO;bDN)NQ[tUWM_xx:19_=ƍ֫(\-iɩϊw}3ώc ߏƆ-Wd5Q-#]^/o(}~kSƠ]rԻWTbڃ +II\9`Os*bpm yS\%PǽQ͐K&wHSӮ#E>:1ަKt>-aé+|}L$ӗڣ2 CsVCEx^,3AgYE#r#e>|XikDYgfQu$bxg { Pje4<'׳JvWݒ~u([0AQdwo ??41<7T 5]~y7?~Z`47ջ +- Z8a☜q&Ѷ`4K9w0ʴ,ޚ"'L4iK~T$UaS ~W̴XًX\n0Dn/^AJKɜ0mUhB ICI7yDv+~@Zp !Î؇z].I w?MԑAFsËnRArp{n-7rivM5tَtؕI6[~/Æ{Cye5Lkb¸pCri]b.y@ᴬbJ~KiW9#P5;mZǢ +aK5@8wHMpjUd 3ƟB(+ׂeoUHdp~l'[( L˧WnʁQےD Wd1ihIԄНR3'zF@VyDrC1 'M@`nuIxu[G eM/~؆Ml>;Չf^pz + t%p|qPZE&N +GՒ7O܌&ØEEҗJ]d98:?]eI QB V`:h?0-h5ga$qf- A`۲ȿ)k\qy4Hů1}@CRԴ\597ėit}tP84mmi&Eg,?#v3MUFlh(%D=LUi/BZWP[RA+ Ms^= +5+H2}~nsgfx 79#qn $|>G=TTi.+褅G,OM\f3) 2mw_m>t]RTU̞ɹ7j~VNM.|.֔syw~&VAT~XƃHNfJ0zI +z-`)$H"`$6Њ~ l P|Q8ܡgomX?^*Wy|pPSKt{4Ϝ@7ʧWq\0Wq&2(6Is :l9Bl2QˋaAdZ1κGtrIER&xɑ<:ml.Xfj 8Pmm| +Lmʎ-M(~0]ȦbB+yelF*i/;8yRS:Vgԃ`btam L<#rP NHOҼ?JqA   GN:s*p\Z2@ew](Z&?/S='}ˆyɑX{'6qwC!t/|J} L {ZxQNjUm_Pb|<-U"3ӷ Ty= <({?³m8;zcK9Έjd-paj䥂Kf#Udi;<߻,靜BjgZKJJֺ8ic)v35Qd{Mn4@6%٠Y=Ƽ| zP<&anmJ5:4+vl#` ?B˙w`1Ղ .+sAamb7e |Y6R RIF[jnpf6zyds Lj &;=ŰcoК4͜5CXÈmXNLXP^e(?\0ƜY, ]1`}*BUu[k$7Bk槉I +J[ ٟWC6k⮱oZN@vsBd>j/X)`?6!a=cul07 + Lx>zE9 Go) t݊D ė\hǦNT]ht7_G:ڂ͙_cpo`=[C&Kao75>xV󬨰 &LJya*iSx6I6[Bd&يm7's0"*K8i͚\M~GANpąZ$xS+N\B.fw^ OZ;Agt)PØbҾ5EG+\b+%\,%ӁdQƃdyOۿF5Y̜g϶y1 SJHHSL0Q`)qVH4"<N.r{۲\C4-?$>b xR&pU ӝ.leДPӘkBb&B2'֊Vnpʉ#oOZP, _ d;1Y S0l5du 0s_=~r~eD? Q%Sڍj6q=zNj}gz̖d2k8M]ٱ?q)z0qއ~'[!o2|A'n=ȹH >HbgϠlc,DΉ Ձ%av=\^hyFc|n<6R_껻mGS ʈ-+TDԂ]q}܋"_HEխLo߽D6~S$Űce+3p?X sI{+TH: {ۛlnۖ^J/tv5f~ف@RA$φ4tʲ7 -@p/yIA㳫 Y%`|Sj.;9ՊFP}|yD)8]/ 2,^->amI02 xU<(x@p,n06W+0+v.m&0#fYi3j瓛D|^0beDйp"I8f0E;xSL`ؚPo*>c˙w*#A[N)s_c- W $™,j~HXU 1"vȓqE]W 7p{Aی:YıF_°໾|sHʚowT[`NA!*wPam4TWL} ]vDI('zUI-!釿.l>rtI)~Cӹ( ߪXQULPE: RpIY[2B+To.< .bA'Ԏ ȸens4ճO=_ҋ@bbq+&\ϣꋯlԛ0.Q? + }i=W?7̍|`O(${JfX?;$vל. q`pS:ߝ?IP@#q"ݸR$^$y Q6mL|敖ٔ&$EuPܤMqf&?C!#]WۤteJ~},AਲbbfPR<>]Ses7׻kw44DB-<~pHCF ޞUb&M jZ^AP>e¶mRo >cYOK4mBp, 8gl.R1.Aa#ҁ/ SevdԲK79E8f+.|O + +8SR/ob0rH{qQ?IU*F¥Sf'x ϛzNfR!sUN=޴BU/0_mS讅30'ttUޚWi q+ h0xv&#u6AVy4˖9NKdU 8Q7~HTɘE>EQ1m!}nWTgSuߵ@[]G&R#8z;qWCh ++BMpyKSE]byC*/x}8{4/ӲdMF5ow m-yZ$.H)*(';VO8?ѶUn|f>I,S5׊k cTk7_#A:$~S[wKJ}_d8!h-)1uH!!-ϋ<:M] .4;%Rwaװ>#BΚ DR{巘I/ +MRaYO +ZjZ.XzOYjL&HnGØt\}rH[u +p=#rVw*ZB{YY iᱍojd6sױZW8F6iz#d| a-ErT%\5 D)Pm8?(}pBuyQ)k6D.rE Z,=?2)JZ~c= .`8 /N1c JM5D{^C-:|iIwi +Y??z&~o|qޠT_0Vuݜ=J6Z@{X֦AM5gb|%W.5HB$vɿ`gQޙvQetvr HβK<4U^;&>JzTIH4CMnf.u*+y`ڋW/u7:AI' Hb  `gf#!)QG U_;ռٝ+9tJNaBuMZ_%6и 7$Y .k\팼$M{21jǢk2n'Ur"7\G-0F+zRkc}є}Vqcψ= ݵI0XKr#j =(Ux;cLˆX4Wt$iMa~iȐ2,Ƙ8~.﹅J7CVX +ܲ+gp}H|B2DIs3&Nza?+M;b)jԢ\S~VLxB5x ?_$~W@ċIJ*{O+?tdȝ >;3o)TG۷z{\yhHd8*FETOA2aі OZ))@-Xت}%TPTf\Pfܠ4| Du'H_ 9&-'@"AY_6EWy#d@тb/&H#Ƴ9VՋiV >‹j%A71^^i̛)uZ`ʗl-PfzJ/UvJ1x ^}+2E'Wx,4|"r=)ߢY +_˝߁g؞Duw P,H>2+H}+9HT|O}߱B&yKY*>ݶikEP/4GzQmGԈ ݅]KX{JWD=C 'Ql#lʖ\ O|1lCb0MT56:+m>]G_TNӎ&JR a ~3<ʁf\ص" i'P戠 kxa+Vg{a<ȩy e,a|/uGlL{) +Z%(05?5w"w: t^Kyq<6gwiSͣq':Z'J5Us5B(|ߵ_&͑H?ZI35H" *=yA(_ pDNN΂4%P{PBj-*k=L]x{V +A)@ʌ_rnw\{Ez|{ q\ h@\|ȱIMWb1զ!;7ҢAƊV,&ܫ ,6ev;F<~Ve908KVמol|n{,LkZzܲ UV7@OۜCVySHx( +X,0*٪eQR8e{$=yۂ֊8a6fSvQ5E\7F=@ ς:a ;zbr\_1Kc $oO֩8WqM q6^i9z8Yw}{;4SA\S#=} 1\_[xV+Nɷ"?+^L)JjS@ҢHR<ĘF_X*l`i,[B'@`>p[jyD: +L$^(ص7]/`HB{sޓRزhm}GZJ _<3XRPaD&SLy@ߔFydI`2(THTipx@rx?#6LrvaXſasdǍ?5O9|" +s-ZzrWjW~r=yB8~{CVbXhT丯tp㔣^Yq'3 +)Ͼ6rT-7o60% jK{swiKi_[K6t}.iwܑGx|!9M3{8 B倽a8k]<\~g]2Y?q$H{"LTگ=G:E$pd,y/."H+|KmsPC-)G|1ZU Lo+"XǶѼ1qLb3i?jAf4e+ ӧCf=g8l6`㣿o;693m[1o .X"ԱFD$lYB:fIĐV Du2z>-[L%xXh8KnG v-^@$kH&ϵWB߭ѯivF0H"ug# s%x׳Oh2Ldo3X"3#2*Twr{u =*c~'.ʗY69 VA~4[U"Nd%igaup=]xˠT@\ +#vWC D!N#xAXWr7|m46.ѸY/ XkmԞ&lտa.zr&67?ő 0=CgF䏐Ofwjk-۶m۶m۶m۶m|ڶm|31;vVfJO?;e <' ے=V>mj\؊ӌ۪\LYOd" + 75MY/dxnQfWLFxk3CT NxNc#aASd֕R8)Dj&r`&R68kdirCRGDS&*=H>m+ndo8q>{ߎ [۴`c)6 +&h#YMjg>-75@G>w1SRa8" l/G=/[$vTQ݆b4y!=i)8u{h?lK= XQPeF< +}N=3Fh R]YƧHfStev1$:~MTbr}MiL}F)*#١f[]7-`%tRtď3@lPAgBl&Ҹ9$`A.~|v? W`^;{@,Dw8TDž];Ln!JjV +g4[j(i"k ) +"c66Ώ8'! ^ ھfZWק90걼Okj&shPzW\B: ++Vy%) X{>-)qCgh-{,jܠDط̏\omf{!kAg4_4l5oݪL zH 5gYR8ד]T%8ʟ?zG?if| ;$'s\)xb(?_A,4RW,-C"ghK~;j^2>Ti+|xw%P>_Y1%xٗoD) +m@YZ!Ӷ*~T]&9HLqش{uN e*5 g&L=JM?kg_?DJ?7F`~u)/լj6Oռ<>mv$>C?w9RQLCTxQ 6j7ؗ/:/1|.B] L0&m܅2zɲ~f3E=([n%8W j 4G[]Yq _&+:FL*tKxaZ/4ZUy{!hFb{yy1}r$=9+AC9dcei 8})UN$Lg* +1F[%TFl3@ ý ׆:5 _Thn2yVN3t&rkYG*::zẺU5",^7鿥Eؤp~oR}릠.D'oY ~Ow)ҚXh 8 &H䐚aވ~)W}00{S<-z롷XA@::p?fO0acUJE$984Y vR¾6Jڎ#˖j!´<PL[:^ɚ\&Q$vh5YLJu˧=VPiwk7q?<:M?d6n}FӠ Gias&5|QK10 m\K'Nv d +g ăd-byɁY3&S.;O(adʗ ٿQ= _b5B- {Ub5J Կ ;zfL0m37ȣEc{ &_с6aRz};Ȩygp#gӚ[; gVύۃwpnṾBDnd8kOD'[ WK'y* =ߦd*r@Mb p>"\Ȃ od@`RibZ-v˙mn[kt _db}L}]p|pLa_/BvW1C:?cnDoPM) F3eTB~ (8z1$VE%,efEp/Cj_J-H3Zv.0^G7+;c 9"?Vl^F2 ;=9Dr'A:ṃ;4W\mU,w+ytEsjSGc'|H+i/o`-U'>=9Aehq,p/l% 7V&>O1JLZfD*V|OmiM4{FB644x&ue1t7@e ).`~2 +c6eC(@/Pc5C[̴%Q=Xf]QFnfCWg)$fIZR2z]#GJѻVy%זPTMqkVN&ґ}Gc?(AiT1Fd)o6zkgZ[elZ/'A04 +xG\:ǖyWţ^8qʚ _[R\i,d"w1;为3Z#3!S2s!nH\ǘ25 %6콌G 7cjl "Zc?&(gJmPe'3F#'e)p>t9ʙvHz?mTx*imZ,'_̼pލqp?J<6y0*Gdvу(坬 ++PJBV_ZCl,5^ҠwMS9L4"9cr 'N!3SD +mt¦3I;ňW3d鲆 ss *` Aٻn3U eOhG+M0Zw$a!i@aQyzbI{_湳gUQr"79wzTr}dY aس }םZWrtN׺ f̪O +,~]$m_Ukf5|HqGn*#} 7}:ہWntb}wvsਆ\afTgh hpt@(Vb:(_RG7i~n 2:&Q#@KD{׃b1/=".k쇯l{i'BSbUSRee-#o: 4_⹙3"3m7~ofp,kS,A>w.Mp#^@FJzץZsv}` 'EÙXtĞz6lBqysr{]ֵcDG 5zÅ1+0}*wH`JH*#=ODri͌IIA @Ĉ^,3K(I3qQiLA,70J2?lxz)(2N˒MܖhًDERUI,hLQL[{:cH"pGda*b\x  É? mgn8g] +EfT;D#6ENa)\\>klr$=&N C@)1<AܨS`@POɠ>2-=-h;#lG!oLZvBe+E)%HO脻v1"K}[=dkTˀt\~ȥp5dhIgOf4\_Vv +ʙ(RHohƀ"4>H ?VT9ũV|" 6zL΢}AR;=)t1)axV|ah=2'ZB )ofZcNjVIr &j3hSMi3J)(6w@h$nMP1Re1OU{'_ى- SJN!MljZ9UBWq, ٦2Ƌ#-WLL؎yKTr2D]uCeO6.֖_4(ICFiW TKr4}o.&BmQĒI9r(qz@@fp3jżeK>&(Pt8FDԚ{-eGv&l2 +DEk"YIxӃ q"9hi,!f Dۋqsq,D~M9\ZVH00. ՒW3؆>HSz kVlS ΢KnX.DFC@9 +\ub"^ly`@Y*0FSxWD<6QI gQ\Uk4URwN};~|;9J{0gZUϨ)k3 ÁKnu5|.XR5+Y7좔IWh eȘB+{t3zHД 0|ԁ1&)p5[.D +h39rgCKꓝ S8VP0J]m^cS, +|R{SP-_Po %e 0FUcM8d"3gCmn) |/2٦#3<~Ij:>W?榪{BTWA/J݂Fn$_J1BhylY\ *K꨻DYh 2m bX?+ z6ŃY8JQٖh}H~ܸ`. ltCݽrEzJ=Q9_!X-f*M > +j`;Rl"\I9[\F oEՒlt[.m2 s@qiw3_J#2\ 9yql)({Tm=/{i|;K}1Յ0V^+(&#2<4X+*PNck9UiF)ՔyΚ]Fx$qI|6<nX8N{Y(ls1a R*ڒhQ4Ik*NUuZ3{SmEӗz2yl CwK+L_l)'b0;FɨC{C@H>G"dLYA$ߥ0`| >1ZE(l UyUO݁ߨ\!SJT3x-gv&V81nvĸ;,zA' -^@SL֌ u2[~9dp8Dm{T>BbtzqA3қR!A<ֻXZbw(x*,C`1z>R~v1B"w,!>L|=Ol:Gb Uchf R+%O?^:*A?Q:^/LUqJTԀl2'ln:#zsGDZ{m~Q +gz{kUAča1z͌%}*W|s`/ۑ{vb5љEuδX -`0 +YW%r&!uE7Mj +"^$1WĩщĞ! QY–;Fm\7kS$y^-mǩa>,<TZ+94:upC̲OT?6[EjOin)QVtI/$ueO'T$u|`"pm(6Pޒ*PF +]Z]ǁnRfsj-C̆Oר' Z_2UBE6OgN<0%N{l@ZtI^bp Ӓ닓fnup8_;x;݃@Ǫ]+Y8Y!#V;Lq*\3vnkaJ#FYEmbVbn RDks ;4 :*xX#J *h:Rl>/Ϊ^m%:1ԼsG^%ɛ;!2CWST2ls̩>V,R=DF🮇hWI-"̄Oz#վXٿ3sv̸4sܿFgetǖ E!"Tx ǥU_xySfIӈsܒNp+"?V‘op%ɀЪ]0mVНӕ=+ΆǪM5kÂpb(G>w6pX]BĖSGT==:!kC.9 Yٜn32 U9d#Y)3f|dBs.,Y/S;sypwOb(Q Z_"TMSŇtE[C \ovs:8iY l$پ\~'hF;> J4ssV a-黭u`i/A eF%3EP>Ij 0Z`boi=ړG}!H.6}<8 <ي*S=<ܗ5c`2`b*50$T*I}[taYFuM$1 >i]Hol N;B`HBi&aY Nk8/pgJuZ슢Gc~cZQ2ޅ޽1QX[(2&B*Ts Kw* q$q\ͱ8,Հg&^hGƈ{K-kRSμ/1ǰ7݈ o$ ݾhnS&Q8h=<(u=ӿMmx!ު|}8)jO4jyz љxS,CI%[SŦ`?Eۆ|H-[#9q)mLdWת6ܭgpOx4oT$a%0:P~qA9_ɽbnR]͘d]U. 2M(BuvO\ͨyV 3ED5ڻ#3.q7_͟E +G +=qGU3b{Kݧ RC9+|\wlWH#JuDgp*gKMռ=cX`|ml]%o쾻#z*> 8frN]FV˙B²oSΔ~Q7](QV7'z.ʹFBBp`,C[&PerjP|CX !C7Ica7?jgm l|y <;ϯGԆvVfZ7S'ո?[j33ɻk!v2<]\\LS(S h>|͑F UO xL`.Qd~Os婙ßL5,r !"4 z#\/Lz.^M}Bޥ^f/+y]/,Hޮ.IZ>fVI73*TM^~M,[/aw/Dfr60HcSJ x0ByݜNU'}}&[€.җj+. '.WP6_jѾշo+2`Ōl] AΆd<{uk-GYXbfWU.-eI8?g|29 (TqM>~nNr UZgnbb@85?N_Fch3fg {>NУJ@/sfts'ka٦J8Pq^C +ȕ?~݌N.fdN.Az/!~Ctٴ(`h$Z mpYLPE!M(x!!VQIJ +I=d668='!qG.@,og*%6ZF7uVOHW)NԐAEHERMGd@I"ۦO ڻˇ\lO6a#YB^sz4yhhFIf{fD7¢Jzӧ{w+-?OY&B 64ɩ&_kvێƣF*< m/:+OT)3,໱ +xUY #*hKZ FWߋ*,E1mGu2F|:7S I83SH\҃aZ'ƟDzV d<aH )Qճ1C$g GjznӳZdVCc, +B_~^+ +k&e0'SfGm4Bt$ۧ9=Qڇ݉)3HjCX3vB ~\kRڐ+ͪ<B-'9ߣŽƫb,".27o&gSC<2է@7 ZjՀ5CoAP P"8̓XY<}F/qtcNMJ{T^5lcv:M' +NHoOc+]8a;N{ k°%u;թ \ݛg)%ofҦ iNdݒ8|Smz{Sf*<_NWŶ6Gb/u8?Y_Asx5LF2A-qaՎ8 0 zmhde=chp4~^k4{RsBSȩ6E>a{.)-[럤j»{sDEك imcg,iZ +{<ܧ i{Z- k! 1B3 (A0 bI}6ĕR]$SjW 2 +%U }inn͸K!{n _ق^ds#ϜtKcw j(|jKwn& >OpUaM?u_K$ q q -x.Xn~ n.7tHDj꾅=RT(ݼ0cÞ +3 Ke,ۏ觜BުFc 3ba/#4Hcx;ASiA3IN \EndqwujF(f(gK~5I ϯ[k'//?LBpD:O'ЉM +bFUG [7j[Kv'!1V6OX)Ca8+;gdv{B)t[x[Aod9aSo1 5#KH;0qv5)RM{S]uv'=>48?^(Uc!۵Wy w#, +\ygqy0|XP۝(`m*W󱸚6kg!%K~c9¢s ?E"5v +S]gkGeo|@ JշlgX׫ê\S0D<؏t2YfwӢ]~m\?N)OAu%Xߴe72.>7uLCm*qd}ĮD(? $T9OpVم5͒8ڙ'"cZ̚+K&R4Ł/u K }v |sڡ:$pVHQiU[)'%BbG~wK? QTy㨠4 +=Wn Pt pnchsrGQL|DЄݵ8Jֽ(8!hי \,Sݕ@g]@?T;ld߰ {k튢GX v"s&E2[fU j + lxa=sev'&h/oE-RiobhG){P.ďd7&ZEe +=Dr3[]UU._QcoMp3(Ht"OfEOwV]8E#`p RfPpw +F*yvI&QZ˙ȢϾr'N孿;pгI o wZ4Y*Bk*d{2`(eksU.^=MW sp.<~4W9\݌B8Qz9٫[RZ$ C s\֔ofgpt +:jOs 5R>,G!#D0vv + N.u`nMs27]v*F+.vp ŭˊ > +@Z^êK#h7у ^; .>2&ԧ2<`' #b +P6~DIӢ '&"M̪9!1\Rv!DTLH&. +ˎE-ŒeSƷu)&=nQz^g Pv #| AiUiUt7/ 0WvgފxǶ(KzgsnD>HQAY`)FIM8xҷrfܭ/|2p3ȬWfՃ#ǵ{z;݈sN +:6b)MS*vpjKl2tdJ)G@!,Acc M~\Օ6-I +ݶOQxPK{mIT3GE(Rr#l3]3'`_TqA#Fߌ&Dv‘J!μЂL#7[yLzwZT"f[NX\렙?gZ^K\X+z>;-,Up +'`l#&xd4!7ԐKKp}!9Ed{8]b_(P-¶zKg7D-vx(HpPOn\^snɥRRitKZ>04*O-ĕ#KcWXWUF8Λ1Eˆ*H+W0GVNz#.B(5 &{z+}:{9@QRLg ~QgJ><;@9@ot4M?9kaWpAeNiܨuv%?].XM +I +^'M񐭒֣q=0)"YX^ru3**R4="yC4V_mGѧpSJ(]|V2mE~CQ +FS -DNi} =\|L˪Lk+,t-Y*.Q<,F@5cu$4R],76|=j^ >!ֵgی \VH:9=`N,VY?VA?UB#.O͜zXUݕ AmDRjW +ank)lk{PP@o{DT੺Mp;F4,pb}O? X{B\? l#ґO:%F$$mW7CEO-A>z|635ϭ\}{؄.j+V.3 %쥢@{] +_N] +  +gz\]nQSttyaBNJW>NC96]:ɉmm9m;9m۶m۶mۚw[]z^ZW8H |oHy`f'"L !l/x}Թ%Xb[0o4SQvRV-galjf.Bn0G\ HիA3@3Xr!(1vKu^٤9Gg\IwN^ +b@VܕK1 z.&OF6Y RÕ&-{@P%Z(9E+GXc.}CNi&*Wefxo9N ثZG[eU6]pºLd35J4#z 4d`\Qn3Ps\ &9d g,k`JnN[μޖhb]h=pŒJ>;:,(ׄ.ZַF9VDJ"ْ[?%dЭ~C{I2W&\xvW3s۴+sȡWKH-ʦ>Y[Tjү$\56 @x\}45>|aЀ~]F +^:ӭmr pX IGP3IaoXWk:Mh'"ވ=k*w>X$,}SsCѬs6%9CGY?oRy?EThq"յԛ&(; YLke*fpf˜sZ,ѯ'au9J LcԲUP;|7j#r1_H$ ^!b+]J`(@%7InrCD(.Xcz{H7]e_Nox"gm5BSkMJ4nBNz6 а&o߅gfYnj97Y3'~MjE i>U80PRZnYx9R{v$==YoE-/oLg" +zHNE/0aCRo]dӏjevdP1vj\)V,A ;>𳑯/ت꾯 +;@~ # +Fȝx؍PvBbiY!cZE=)vGo_ N֢%p^Ԯgg鰴\,t-kT 0mMFY4f`Y¶`u,=AmT}g+KQ 1C&Yԡs ;*:jI&q$73K_T~H2϶Goq*>oM$,3a%CN]PbTYgBj5!.Z7Y][;EbI;!1[ `bpa$)FOI0ҀqB8SgOӜH3"xWH' >_V7W{ss|s̟ +Q+s]31:Oӡ\1#„]~N}>VEdLD-w[zLdLm'MƠex<{DB >tozU}l5x~sDCO<| w3h,Սc(,HP$6K;{'YʓuҕTF $؂;[u̼vsm 9ǶuLk6: mOzY òڷGQEԻ>֚jQܕ؜K!.b5@"tSF? ,TQS+C-IrcƑݽ%bh@P/ +#P1ڻ+TVW| jz\hH6?q'c.N%ƍ; xW9ι|Q3 ~;TJY!Y|X_kv"Dca4m}{׍/]Μ)QO~!S{VF<b!ۍ&xsg":)uR&IV|V(} +`O*]Q6QY4 \8J3h0puթ@m7,옱=`` VXfHׄdRxٔiq6bp>= ?d{;^/2qPׯk^vZyZe<OOTɹ"KkbuDoN4_$:T}Zmngp= /f#tu\dl{]B6]4 2&ש9AzlUf< ):f^\!\.w[^`(mokLt/dٚV:FI;"y^[8J̖n}q9XxZ󻠯RWPҐ<:!}U{ӹDܼxlt=M@^`\I~rP#'s*rԐH4Y.eb>͆cgU؀X2nZܦ@ dmU3욨32ް&:ߵ>0>,~.j3ک8[h_irOHܜj˶8lsЬ̤t\ +_^_[O)yp $˨)I~hJTt)  *XZ $SƬy!QM $-.әgJʜElbE U,;ŭ>SMQaVFŖƲ!>59m'*Xv|)Ho5֗o{@wm-.qAoMȬ߈h;3Siw@|A\Lj|PES}?'~7m !`:UzOFQTJYxd Y$8I$1IQ>;0un&uefI^MǸOH,+9y?$#zL0814AglkfЫɰ*2.XOPxAmm-b4B;Gˮk?dA"IjHN#KtXYA^jm& +}x7'YO)֯?4#OW k[i^.6 mnF@EMa4󪄙O_vʛx8y)썽HZa 36y3jwӾQ&4Iݺ`Y !,f,j|,{ lWRtך-`[V13IZA@?oNI%; %[|8lr86x3-S+Y+*+'9JЁP, w?sq2T6s.…"dPR!+>y^j-C5  +ҼLщ@‘@Aᛌ4 "RKFTܿVI0.[}H6}SZ xH4|aUv_@ _; /HHs~yGc|}F?)PK4r,-sΦ+KO~F|t+]^ +׭r:l=Rm[<(sMu%\$c7p&Gz̪6;P~W|JEAh|>0E<q:Ĉ!uc3{RӬ'XP/DVRBSoJor|6dn +8C@,&(0"9pr<@.aDCA+Z~tB0:.~㧦t EꆀJ?{{\Ez3>Tc{E4slxk#N&r +An4q';9}Se`T+`YMw JYjOQ) +a08$ј +gGb0b{GfhdjP<)IހI6T^FR`ZN#V_RV0YTOx*smX aOd#,#e8ʑmf1,\U'u7^ϼ,nN? {4/Otlzљ@2=wg +Ђ񗨻AQz ŻV Bwީ/nS)6jΏ\LB?sHz {+) +v,.$/,w 1$@D֩]&e8SZYxb4dݤc =˵>e"ԴiGdKyZY5~X\YFNIKn=>G.KUd\p^Ո f@ $68EaQ\u=%S?Y.IQӨ TE!CYO Oa됧Dxxs{;gf%OHp +8rFߺLa!<իE:8!q6lOѨ!׾w(g4U+`-lBc_LQ&2|i }2lCP\ee*^)'cY(2WKβqFF?[-)f'fGѱZ[tNrJ0aM 6~iԤ:B>2y5XػxIcKj+TUۃNlp g&xbOb|P㻪30XBizlj W%(D?OL`ĆA5 +uk;j(Bs\I,\jRӇ:4ǯ|S$=9Ʊ8awfw6Ոu%(Ī˓+*SHV1)n}0{!e[MNQqg - wqf'K\Dox}]XTmH`aNDT +%fVmM݇ x=T2ɊͅsJQ 0ٰFXg9# :{fL7ܫysU:`ķ]ҹD`y*x5!Us#%߈(O.͛$Hmr*v'}`x]QEcG6*2)2vr XĈ?:Ҫ`0;" iJP.qqP?!_6yI(+; isFIrGJ'}8nMR4Lpxf#V\lʯ3I9*̀yFLhT:oe̐X|L` jTVVLt9`v*#(a01Hm/ր0U~A)$cnfjk^7(|WH˂yNxA4(80_)_nLyX^ٔȫ% ++V׋wRfI+ Y_GS\H +, Ʋ JE= +x gǢiCRlfMg5?an`|Y_T8ddYhb?VE&|fźsw7 m ͇U +նӪ8)!2|$=B"ܽ?Fuxې!5 3ď$0 ȅPEU+]p'j<}o'D5 ܳ]L{oڿMƌ`DO$ck [?y~ba4' = .5Jê֒HaQkA!K1D 2.w8՛-Y*/biͭsQzG{pZy/Xڼ]-54& ٿky6Z |)Sk_XOi͡[,JtCOAg_7/%g<$0lOro j2qL\u1&'xY$M7# ^WM.Ze>#i5:힆Hm s]+DH4doTaCɽtUۗσ!nQ䏗XEKxiffs p1¬f2[v65$CU$䪉Fc}QWB!i3X{N,/w+_[?FuԢ"C De_VaM p<'8QAPg.zi=K PJРռ4g# +O͓%. gjYը겊݈/t(>p񚺧pț~'k,&pn]d*{Lv~ʴ$\Lư-cȻ86z4sV RllV3M(?x߻hWǹ"蠼APQƶ xG߅GM +|iM>tT&%JF !xSbj'ÜD)(sM^3 Gt=[%.U v&=!\ZM-eSGp+H jpBn[;ղ,&fwp^4n Gvos/z-w +%olbNݼyh?J +U'@HSNdm oMC҅W2b]ӴW1SfϮmWLc:_{˞i3nx>&Wr% dNdJc5} *@Ajg96ujX.e{hMOEQݵ%\@=W "Jr7qdJĨ݉oAZ̽Ƭ[= +w'Zef=UV̕RM82YZZ j}$Ir2 Կ"TJtHSvZ̹S(j-r/=̀s"&癩1AH_LVمR6=`9^Z{ 鏪$gvK(qF&ҩ(+3mR}:[x=lmBO,(UTo:3$4 +9e*f= !\@(P2ȹ"S7k_;l#6hY~qxpZz#]֚SS_<'73%F`!LpaEX e2,-3+'1%0Ð}qXE>ƈn~Jr; +#_E{뇆-D4LmrҤpt*uQhqK)٨Y< 򸂯BjvScXE]o2?vm\Dޏ|קp?e J~*Mˠr<6-Vū|rAR;&/v֮o̰!+֣Og,?_,٧l:cAY03A =7ͽo!@v&q?zPՂ"/zgg~kp %NҳY#q3jv+w -]tfb;Nʹ'}2WgdyK+9PViD콨-{0-S;9l_W1Unu%A uBE穨6I +OSa0Kvp*Gn)һ%!žufsY](,]d[8A^I)1FHR:09BPysv9Q&Z7NQ/f9xψ1 +(@"Ihى_}_9Js'cLp,y4WrO 2 LsL`q^*$LpnUQuX|%h+ '>я|G@BkApL` ^NHp8ޘnYqGol50+I &$#,|@ދ5 Ϊgbˌmj).jӋ?960 m2F7w"t 8Fq[w0V=~̝1)x8'uڑ'V`>"-Ub:1l@S0E ,RHt +VJ? vt2!@8f& }0Lj7%b7 [7KF6ƬIĠ N=xHS*vk ⭜J6YoP ~px$#ztғq켫'Csp~4͏|,`0Yzx$,nPp+e#>lԤ;,HbxcYV>Ctvvx+@L6@R45QnQYP@\3)1oR ZH lEr>E|{3lx䵜\ҖytT9 htV +&]UapVǓJ-K,sFqn'5;`Ը]xLy<uahLq;-OӾed&њ묉ޓjҸ70s,/c"*6c2-} 4Dēt^K [uHQ!?kZI,&8Q͊^dYIb*|7 ) +~G.Di|Y^ދ^+Dt {DW (/۠shwhSwxKUۑA`ܭnf[]c^H]xK?3C4;Z]CA`>՞5O9Ajhbj *e Mٳ1..ޡ ސpܪz.sC)À`;s3="A`&HdmptEw&/˲4btkRflLP1QqgyBƺ&<gf'¢|/ϋ%גV} +O5& N ,/ z i\kSi// `ÎpY^]uJeM]Š7XlI0S "ڪoX+,"O3g_WbbB1@t +Mat: Heo(,D\nL2g7?TXlb?[EM[}G[a\b/`9r _$U+FjPmD٪o O~tDǁ7P>?̔9q*ЌsB(&R-EM:CmfB(t$Mji;1 i{ aICEh*j+ZS@\8?LJ8u ,(^SF&0*?#ixIzgL{I^sP k9-&o_g'oV"Jd8'N 5DQCof/MkKR:20b׎pF JiAA۱v#=۫5s|VךFwj8#k"7D9:6L-4,=0i̥28](Yʖ+wv(є@R{&zw=u*"|/V><ֲ."~22y)rxsO_?a\I ; 'V- 6d:ZbuSrCVi4-! ]oX[hw#Lm"BvItZoC AWI<jr/`eЩ3Tw xw[8sk{-d<LFvtK)&[3i?`IsC0 +QeR9N-t+4E O7g oЩQhId$+Ibr%_HpL7I9ě"@vsd}|E$'I,*dX:X42%(g)32[Ɠ/ߩ;&DT{+T7kG73ekE!fe[  7Ø㾔wkڏ=^uyWNVDBGԡ+PTs2OtKxJ)P*p.NUdXR1o7v<T}Ѱj&m=~@/rJ'zw l~@\ O &+U[LX~kƘrbl&q)7c(?-Jgu6pzN + ovt0n +\ +XK6VwMNKOn8o`X3<>4 Q_BPrQh/qgR8ix3vDK~)OQ^t=Yv\nxp@T*؅i=:ߦ~FmF 087tJw ^ 6ºd~@7G!K?_Si +"hz_xENUBG?|bKZGR#mASQz;R#٦# 7@gvr-wBNw T+зVXSrl]_XBCR^.Z~2:R NzLa^g_/#'[WP =7 _*=EM+Cj 'ac89h0Q45xO "j YzV>|I *}b$# s&U ǁ0<ܠp_%GTہ;p>୯ؙ%ac5 +%֎һS$L^SZ x=P[Bڐ?CDY1c6}֒o od4:ej嗜Tԁ; +0z fn:&|G,zdGFcqNOPh xzgڹrM ,,I%1Emg]X+y47?9ऩ4ʩЈ}m}Р~ЕAѢU'rS,|!-옱4<뙟A6ٙUتAF?QVܑZA 7U^\Q|zwehj%2_GXvk+eR)/0hzd|U ڳZтC=7koAd磶»aVpқv[ 41{!r~L^n`X' rUI@0c>:yDKXMV﹛3x= Fh9Z(by$"^4#SB"#G=|w2@;Bh&\)̠Ijr7@Y,-۶m۶m۶m۶m۶m˺'IU*gfu M&ݚHa𠘫q`x,> ?/ޥz< 6~L YWbh۟&\=C57֕]Iqu&{

-Ҵqd|yXXnjs3\oN^Jɨm?Ȋ}QPc4 wyjЅΔBFǟЊR:FEoog٩jU-&g/1b@p_& )>tU~>HK,7Ϗ)C$AWs_Կ?-Jº:s)|&;^dhmN([M4!F)Qfiׇ@Pfw6xJkӝV;Uk\SK落P*>,^mh(Eͣ3邇/A}h`"32Sy 4 Cߒ?&Jg@K0z%'@ӷGUfz9l.Sd.YW?<+{}|Yjb{ jsL"bH*\-ӧ{*0DujA2w,V'k4s#Wj|jSqڰ(@C1G="WMYs+"Ĩ 8U{pyVxuwo-)3b y#/_y;9)ˮaN*Inh=,RahCjڸd@Vׂ eQ[/Qő-+H]6cS_Itnpv~M֬Q~=n]'S2=)k<3#Xa5h/|6XtUdY=| ¥_y+2۽x d|s!VƗ:İ= A77au0Įax2JU"hDbc<7-N7h'(݈P&ތݻ{5@oakn=hU[c`:2&iKȕOۑU[ +AQh 3cbfTўL*^Y !raJuZ꾰5l9I.hl%5B8%ƅsGIͭ#o+:DI'8s!@'e1yd_|zF2-"&#crD7je{UynCh) Q΄\{=H!14yeN |ljR &0O]7V–T4Bcw1/ZMt8gPt;@1F*lGn)N vSҪ/8LIngΞ6E;S{dAe97H)]A*+[WbᕇLH(\h)?f:=X6a;rRa?_)Bܭ+077stisom|Wme,F>`Kқi"+nϲ!<É P$e~1Yk =MW$0 [ F[uؗaV#&,CE$rkz7{y)Ua6|-~"DgIBg5!3ǸR/OYCVY1*Mp9WEo}9_QEu<ڍצl-}Ia ;8P1"D 8F8D A$yuLD0!h&0 +Λu\Ĵ>F$6m},'-'{US"RhWS7oV¾*.Wx&ިn]Q sR4) +pz3$>ơt ^xm*+_#K%)iHI}Y(\;0B?wJ%@v7 `%;6|tP/Ǹ4Dv(~牃oبuJ5*u4fg\kL>lVA$^YTOe&rjA8JP'_7x7dhW&z*o365V|v>0LύORHyM9.EPI*='Ur@,]3,LWHg~jT@KKl.BuNkR#4+0YЋIRVsw[3VlX-H%,XD +2ǔ6xtO9~^҇, +,WHPg-`A?ZS\|,d +v boxošŅQZYӅ=N0m&oπaA -W* @O.Dkl:'ǗI> Wv?K/ RuְJ|5{u4|':WST^8}n3rYP5ho@כP1ڗfPP=n!{-%Nxv{vE;YLpJ[ZvlRZZUTB 7O&v9eY+#^6_1 dL$E(zEUw M#:2S>nkCG3(4BȨ(`>;&dﵴ 9bã_oVuMK"٨VE)0n 櫌ϓ:j?*9\؝Mۦ`oL{w# >"DuUiY5P^F7+Cέ# sw(شzbФ=FY@2}d +r+NICw9 gq:: qx >ιBpUaSE! \@e#>^FvAX%X2b.n#h+ɨXlkj&$q)`)&"?7"g^!?|?6> |:.XV]P)qJb`j-oٴV∺#ԇd냕_vFõ^7I^'5?{#Xq4NDt5b =f`$ׂ`uBحї48:ι^ +J^I *aAZk a BT icꠣa6nzz-noN0CK5_J@'Cx)5 @L0w".r +Օ f$=JJsPbRotG/%8]Z.'t AkD͕BFSZub#AtĕEK95N Cj'Fs5~j}ό%?ɕSCa~[\t67n'鍾8@TҪ0oa!\v#bzMYԷ]g6Wfó"*Jq;Ze^u ـ) */Slъe19k0&vO[s #G+~qA% Fd02$'rd,{W4=*Hb<};[v.EQuuw'ت<Wʷ@!9-tCNst[WwZ*GyOͯw&G?iI`:η(~E| ` eyԯAf;Ec +bJi>wپŶ!>z/ɀ?1qhT+L9?[:f(wi|Oϻp`7`Vs&YGې hDA2sՐShxRS\;x}%1ʼnxщcƊ+2zu[)?/@7HY]Γ )pzP! 2spCQ\B*xҍ P2/ܴkJV`j̅9P_=nn *ԕԿ;dôqkX2ֱ]/)wr1{8*]=8N(}IǏ| J)C4E؜xP.jWfG.;Q9F,a9AC-h|yӹ|L p}r1& Qc'?R;dA'**Y=3}De埡fE%RQUUqrhY %hiJvߣ`5EP"!v3M|*̜Tos=?OoVŊx *|b*/[!'5 +}iP= 6i2?Տ˩3]줍A<([y8a <7M]s7~U2Aj9(`|BLF +!t1<{eވ[2IJB $!<ҁ?()v/e"\|ϡ'}MU!~|fnyǹ٘M!>([ c`/{t"gz}TXX\ij4 3ӖJUU@:t,4鯅!Aj# +Of7S?B̓P7 "lBUI@U؛&e+cͪ;@ 9GQO}|RX(*˺#>&zRM2t~.'3PӢagZIS/1=U##`v95؋~z.hG-2,e /Gm/ǩ/~ygvỮ/7Zs-2Pw{rO$.UGL4xmWO]4R2#JC?F\ނSsdunW}~sh^3Nh*yIZ^y3[Âmː;ѝm8dKp(Fsz$,&~6g:񬧑wZ e`;Yb!kJ%^cQd$d8\IeY`V@ѯgkkc#THQ]BnX8 +kJ Vۯ;Q VP5i0up#]qQOX 1P@ Ijʞ.KTۯOz +2bh? j@v5nEP_`\|VumДBJpP oR`mC M}bQ` +77UFDԑeN~X)aMCƊV3C==cI˲qZ vjǘ;Zp؁D?i% f> OfatYV|DVf 8ь3w?lKO[`CyMgRV^Ɂ>G~f˱eUHo J,eK D|BRt(<=M ?B\,Bm'q6i)DQڬ?g :Z\:P{h »VCQIqX>J?`}\nI0OTgiBB2utKXMs̏ ǀ-#J8t1YQ +uA*Yׁ hꌙCbҬCȘv{ypB?< H f~F.w8MH$ߒ4\o<;w6?TP0FK ̢a˻$ϠӦ~l<<ʉ'őә2++0z`sd,@@Z н6;^/8UڨK/209zt JeNjP&/_K_܇kMwA+v|l*([¿h XSJ$&8ErgGBR}Br1q-[m G*еt~{ Å.(TVyܦLmBʻD${\! ȱaD7VӖJ n>:O͑MN|V D*a +UY\1ѡW;]c򰅣qN]\4'Nߠg +#Q&tBLKb)yêi5؝wD^j 0Ғ|M$©\-FH$&\; xF čSt&իWEmߍ:S@l =ʿMzC1Snjy?myxle4ڳY6}Mbu-u> /Zm g*m~2I/ĝ-D_,4] j:L,\} ӳ!#2|l[(bx%m* x8r - !aQW.[ +4'-U1ΐW/ rb)t,uA=I0}cn,! S:ܨ>˥W`^UqKb?ᰒXhC0΁otGFװ!=xഇ6%$kf>qlkL@ - kdg2y.$ ^>)Mu+KU4큮 +CyKuuEHEA2*tDw4bdu\kׄ4+hySF{9?V|]cYL"sSb]0)=3&[m.fLmzk@/Y})vYVYf1/Շ56/**yW.LIB~#`%H P=ěN}w;K . |軦oE.2{{J6@씒!$ǝ=Lnɮ8jfڏdدG5S&]5fMMcs9-UN&8Sbw&?[X -7i#$r  0C-|V)BȋB4kI2ey*bUNNVfFzu=08 .'aϛnbrxtie-*]x Kbvt@%[:B4x)`3N|r9ޔ̬ңLR4{eCxT{F$pu1 Ħ*9پV9ZHݒ%n)sň.\P\"BZ!R-HRa`[yY4h8|txLw3~E2e}6z +{a" H$0 ։ٍ(v݌sX G!iuBP 0dۍ%2y/G\5ȅJ`/<eߴ)~5@4A}`7C5FF#FAAOaPh*/tX ?_应\>GE L c ەy\%RyX wbbNݷoQxؿpPgYi.(Er-EuF^+asC^9v8ƏEP|Y\.؝;)1vy.Җ[6RB-h"UV !Hҹ +CRtab@tןӺ` +`XHE#X 5L]fg?ӳ\x([K,q_iL·fB Օ~?m +r ʩ[ 񕁐}J֪143+Vێ5:8#U f"52ߙT71N:tmǼ"}LSO3jQ.0"( +;Ӷ)RIE;9Wje[Vò's_viu@ho91ZI.˲ƓD~եX_鉸&`xW- "*|a~/^y¸_'vM%XC9sҩ~Nxw7i< +t_22\R 3n>q3{2!js7ZfϗJL7QfOe8(Q }&^QLJvYFM'c*#AF}E mh\GRZI D]8.MyJ5`D`5OA“VTѢجT×34>R4 avtT}N*2_5w +Ha Os%pw |dzJLwܓOINDQf(YAe:40^1A*OfT۾&ك -%M%T5]5A՘f]dEIwB!ж.yuo)+AwEu=( -AHeg7ajƢRY3Q9jF~EVVcoHNUuVˮ'a؍)( syLsh0SXu^%Ù5\ϨBXY&Q"ߵ/TSݐvAE蟵wT7m[aɫסagNwPej̾-Vb8y \p'"is<_7hj3ݐBGp)KC +K | 0p[!)E5N}KeXC Jh:65غdG {@*تC} JSEUSj$tYyT +l)c.qU +e(U\y#/PaKF:2~ҁG_}2:V zi4p=K_7yVW Imn:kgVh,  -eIgGe8ZpZv:nΉjY4 4pԮEy O{|VdgT6`;'ޤgVu+%$_ Ó,ν&zjˁÎ*zSv,D^/th.zL@8M7}Av{}]L$q,-mQ>$:&AQ N)mhW/:NƂpk5UNJ +g^-ܾep[j?,%8n>9W.G'O<設J[%do,677-cj$j܊SBo`ɗ +fcXڢix +(^. ]KM$bP`lu"ڿ +PR {+V"?y, e%@:6Rh(8K c[T7 ڣ| F;JjJq2i ?]-+JA}IFDz"23,"ʛ0 yLkN¤M^ƳW°EciL^TsT]9\Z/%X]-t6pi4xO!xtF:~Mss~_!иdŀqY17up4}MUE +2Ly/Mޚ2Navϗ`Hz`e#e +dI9qnS85M%j%oC dy[kAIp4\,/{͝/w0M6JgS6HmFAB;Xϧ {Ѳ29 +3gY2/|}vѣ/oxM2snQS}ؒ&U7JJ=al"@R| ڻB'1uc;?8~;nWz@>e +{Ċ OiS _NB;'Vf<f1FsL?+.!X\:_)wQ<=d.TИר!Jozd M1vʯ8?s:J,Xd&G̯:a^RVؖ@wǟI0;(6LMz Tu'0gFq W藒73]iizƈ#K߂7kTk³11L!Dݡkf,"^g_BCURD(wby.}yr yoykhFT^U:*];_L#g^`yX7~#G al+[r%le'gW[J B].鼋m.;inf |Q$APiaoF% !DC8QeoVxqe%R^ϘC +*N8;9Q1m@iW!H~|$BeXFyEMMVp<Y3 '}DƸS,๺K+2AJGs\l;-M!ՂF$v?c'P;Ιe=@c5:bF\szǨW6rwǹF_+ځΜ(y# + /呉=v6 V +&t*5E`1JD6=j9ߗXj]Lݍ#yCm1MO5s4$3ruPc %z/mqFu3hvkxD1i-E#Wiڧ xk-8$wp_H"D|xlY.rIn n.vs-F wE&3,n-mƳp03AWI9Tw9;Q fh0‡/I)B2ECN"p\lݰC>(1~d?Oj6ct3-R?EV)M-=")csT)ʩkQ5Y9bW/4H-oJ]`DQtvm۶m۶m۶m۶mj﹃o@f[Ic1 Cцy ԥGB{Ƹ͝!U0I%}@m|D::l">3Z % ;oF%GK; zK]5ns:m^dI9Ac3a˻0D:Bb)P96E!ՠD +اYy[#p+d_UG`G+1eP%; &?y,bX~4c Œر޴_ M6w)a#Nʲυ*M5=Zih)սus!sn!z-0wzlP3IR-SryFQ4HK4zgVQY33M +< +nn* 8Aɣ`z*5A#:9q_ynFB~k+}vk[]zQvж!iZdpUPѥLꯞr,T|Vuྖ[@]IϘW~Cqtҟ7@֯jcZvY49AtW*61 /X7;LPOWܱ}T[{q@"\yޚ*> IuK`edA]5?y0O"7@ d-?S睇B)8w !KG:}#Y}%Mo{C6 зH:%aϜ5zkn[SXUtz)#JN:%у,b8% Ћh0iVv}ݤ +熥?/(3CARIfXX":IC<+{o +~~u ]mzp\wb`!&LȅvV +ǂ{Bŕ'g 6ޣ."p.'ziv !~$^ǘ e6 rH% `U!6}U,zb!|z3RI$2^Ȳe28[,n3)M6,r ,` x"ы @ل.'!t: 獭6FӀSbke[p~5r7Nha MyTZY}~8`Jc ꞯ`HqD*܉ܴ%̑ߓտy={.s }cM\; CW"3Sb5xԄ!I%6 +dP^ =ՆXa|xµV4gE4}ZLd1;IeaKp,@!fg+?20V~b7VH^G Y1 {of2Wp*fH-ÇKVLpF n@JZVՑū +>_t9Zk"RQA*; +N\֫>m%> 2(.8^ YqJz#`n}FL6%UHQ"~ϭ(zZI68 )ڏ3WV/Eg0D-] vCsf]܍՘+?VԺ\dQ<%1E{>Ll0@lY=0Ip#4 D&y1 (m^ +NZ}#nLsQTi ˔G!IMR܇(#5S!_+ird(^,`&q&:5BK7\WwF-CZWEr>H.]V8?,*)SqݎW # *HWsÛL}BHX]Z}Fy2,aq?DqOKǎ#F@,Mv_XT DBPY2굲H9@6UD|تMqxI\Rg%n>r[ Q[ !.'14Z_"}<@FR?dg_ձ$*t{PveKQ"lh&U +5*(Ab9xpY"/k|r΀peYcgQǗ?+><;'SQtRn^!P"K|nCA`b 4Elc*eCjmim~S<Z%+HbsKANHOnK|t*&\OQs+Up?n0<%n2hH|*0CODɾ"/i'GB]xL_hOh㼥aZpQVwxʳ;ImֽlSJ;i#& +Ύ'-#4aؘ~ 3s +Xc22Hge F͔7~&?J9Ge0i y}V:?{%i.`\*b x=ÓDe(?qgVIjǾOUv}AQ4 >֫~x)X7.1@2-B*gPr )圞r!/-v9řcT/v+LEw2BA`ǒtO2||t۸Ixix^.a'HB 5Z-HK-17dHIxFۈP"l>) +"Lc6Uz#),h) iYQڜn&;/2`hGwW&~|i-VDy5Ng~rWk~_c'NnQ/Œ0BrW:RZ:M|MyT.Jx^?AdsTAnPM[׌2)^W[xsJK3(^e Q<<0+S ԯe)綠8;aq*W!gMfz}O|OL`L7]C$VX`VHQ[XO'鶠 i[<Nl (40ڻη`FZcIG_F mOŞ.Gxd]?ƹJv4.ژ%;T^c($l hYrP +;)hAsg٪瀴l/KT3ijT7+UGrvf(to{"%Ŏ+GZ wEzbEfΉ:ܑαt_xh z"/Z]G͑KcAg CYVH3 %1Ð2S%@WQY\k| +@Cy5;\7xPYW3x8H u` +$ ?xwφXl06Y7d*CKΓ2lW|+̆f-qJ9tXi9!zAHy >wS8:A^MB~1*m~Bxp>1VI4aM_W ņ8UdM*93[ +uj*!iC\ V1@ZVV7n 嵮іGAn,q&n<.v([1EnPq'H7yT{r%U硬vf Yӹqf%.]d0kj5Tj尤@qYI ۹[Ue+*{ +'S\x| ߲HO khx Vd?Ș-j*#*:VuZ"X|pO֤s3?7Vl"vV&~U# #OE:YOR{[VH`}E0 8䎳$E=B[P9+TB9tLZ@Ȫx +{* >4C0~Iq̇(jaܿPAatHBK]8ihv1n&ê='+Xgʬ8vGX=d>rgjS2R SpWN:0pQ_ϧZS!;lb*3)yCZERx1)w0`+kb9:u[aZ0݄4:84pAI&A˽w"uэDdT~'s =D%w`ڛX=/ fekzsx,Aw,:u @}$(+b}()CM/ I ̰uR Kњ{⺝4)~'EIgZ/ Z9fYyae\swaxue>OTsqb&KDF*2MrUX9p[_Em;(Gz\L+k)"&}UZiV b p|жA$3,Y,0LKC,߱cm&֨*)853|<_ϖ R@>k,7Jewdƒ5 Sd$,4ˎVImXfi)[Șmq7]]* +P=7BkϚo6ϼzż.vZZaIJ%b]M=0g *'\|S*ID3y^f xyP=/Tl/>Q-As Mv2AgR8 a:0DO$h8ʫ.l˙wja8gǯC 17]Vބ&y@cud=n3 ׵4LL!*n3kJƭq`Ҏk=mOgu+ZEs|"IZj ӫbUH&#1ʇ\X?C:eCdVH -e\V  TcO!xr~xGF׃wzȝ,r+~/Gs-ch䮇djx@|?r\-Uu9J|:(`0pYxʖ{ *Su0p +2IEF*# 5ɽh}M-).7j_.\W̲ҍ! ӵ# qN V,݉,;نj{7]cr.gƬ +KDy[_??8=z9R0qŰy]lwXFMlo/C}qZ^̬ܬuHéU,|%P%-+*jθ;Hid?MmPE>O= +H< <.J.Fù  +e@+ +E:e ؜̙G归3ڋY"=UMAe BT[SkN36)<筞K>7rY',1ߕKX9MԡdLH/#*CDŽ5aп{3Ì~ HNj`#p.ŝd8C̠.N]1\[X3UFӲO b˕~ШtFVuM@Gs{B_YI^ 74VG@y]c2L#^yUy4%< Σ yt1”52cP*ʦUmW> +H>%mnׁyOLX!GT:DG\/*r,BKW촢ydәc >e!gAy)h)G\(n"%8J+wKƄhr5ǟ@j*gnJzͩrk3}/wiuڷNI,6xg_K_ a]R $Qe +5dKj~6!j%쾯aau3#rD=r>^p.Њ'θ $͹ӉY>%[k:٣P- N@r[NUq&΃{.@ʎ\"^FLO=tb=K.OKpьM~/;UI'5:U6]V={0joɁJVf䋵ἴ3Dy8imc).jBT9aϬ@m]z=gy?:>!)@p:f"'#W! \Ss +&&МW)ΐ\SǩܙΫ O)qv"qzZual\p86UUh  9iC'~]LњSp9L˛o,MFMP%x-'`j7yBLx ;xg8ces'~̊xxX}m8Aw߽-Y, u1E+{_~aiRs/OSRu Zg氠EqJ/ ZWŵ6-2zm}jZ}!yA`fb.G1%B@#a3dh,uuA0cpFF"@b:)1;s)e L1=e"nMA21oX tIQMVA@}Gl)5V\b˄4f⚪c\/LVQ-moOʳæ_73W?{|?V>a ` k&t$VN ?᱅ X8xj3,K@(!Buӂ$V*, YJ!#z5̿4{tE .$ESTوHj Btt?nt)nW'Uj=(YvJ^f4>܈gzE L+bKVat c][H @pΧx2~^AԻL_u+9!߹NgJ.% 2bW6Xvp1*x3]%ƨzX^>$#Džā×J+`8٧+PV3CEX])![ϙt\%YT&Zݕ[0I8;;ϨjP`UNLibSuDIBYI9g CB7\x=1{_)-XMKFTϤN3:!1XlH 1nZ0,d4Dk%\.B:`M2dw-%Hz^l +޿%xY0~.#TvK ] 6L-4Ln~-{¨[vIPAi䲹D}/L R<P0GXwz₊P$O=t˴ Ư4 7%V +eW-`EdZ3I SYӧasەW  dv%˲ֆ;~3̟0l.; ruaZz#$R|tUxpGΦ eW + XyEL1*NJu؁.dF`8UڿAy zlP!a'n[,ƬAr_>iGeF`pv@#qXqt&w|r0AU$ɃpG uf.zo86*qDXڙ^Qˡ̄L;|05=c/iK:h(XΣO?2~=11`L2"O8F| SIZA75`!*0ۊM=l]?7:ω{(;?eM~nȠoW_A%Ua2̡(n~DxOkGS9O)g#~҈ xJ)Qe"$'XJyf~ek'plS5T`:X2Y<&$BwMȼ~Z&=p[ϥGG +xaq>aQѶҼA7ب56fBՉ;,8f)U]6{x25+N[:0ޭ:HwLM=[D>_ "D].&) m/sF_uʪXzJ(Pϥ=(I*ռcH +dY.R"nF[}-W)+Q +! e}˟Jѧ✛sg x}5"ԟZLIaW?Tg}+ԳkPJRn3R@,fأ#sTҀD ~ӯY^/@M51H uOx hEWr-.3خ#JEH8?s$WoF^_?򻉥P(/EHk-t'N+Bz@®%_$Cܷb'?~cjS;N.4ߩc7- RTߺ0[كO98/ϣ0S'qz}k`L޴HB wgσ^qeoShr_f2.MkOۖv Oj17"۠)T͋d &^ +4씄WXH+@@B{ItTv6D~4Jd2x#w1^N;HP&}ͮ}ڴ6[/*D<O;дJ3s4tt~k`[)SY %JS)t\UXK7r#G%rӊsCu/`}ɉX%GtkFwWE(`OM.冢1@"sB AN:#;Q(NH-zA#rUPnVLvڌ3҇"rtۻ:n +ɋɲmPc5HAeW]7]_%l%)X%,A9rcʪ%! n2yMNQ ;s4mO:S![`ZM>R}@LI׿?~&[6L+r9 +cdfB +*ŘF֕9 _wXLٵ%wW-E*+͗ |YcXif><|shgN_.^^1j'';piB@, XtJvg*+[6ؕۙC#AkuC=oa1΃8Wf +O$CzJιzbR[@siB 6MA &Ge[C^ga v)2^.A6x񘅍IO3\p' *A*v҇~55+QW!}k;GA Y‹xFߖ2S퓪XՅóJ:;*G70;/ϖ6/J&Ԏ4+" +)ev Cjee,(\x42 +d (8Ss0yxT8H{|n%3ml )Zz_NR0#A;ġ275Dgԝ5_e ,S2p3ݾIsC$Ч;@T9Rf1ʭ:6G#g^_*~ ʨhfI|GHB5t L@~>q25OB"sfg$`yh Fptp2<P$!M J gcHwG@4FFzFˤ4&l3%ۆ$p#pxxYzE[`\1l2bAkS?x~|w(H#olF'K/N?KKdJfTt\ۂ$)͂_7{%zާVU4_g/9/K]ݱU%Tur Q^`/u%JPbɘ4\q>m (d#5:Qo Bɒ="h oe:0.NONFPBf1)@h>.XV3 3d+\GzCIud~)g68ʹ F ‘H@w#5kK˄)B[|sBi7kz2gƆ8Wl\Cݒ~ӏ̧y:tiv&} ʪݪ(Z{V2HtA6"^~K$OCh԰EM|HSyI>ZZ;VTyӪ7zɃwSv!"lHʰq$&A(|{&F%̰GOG[#'ok1чNaNuAg7GfzpV +P'U,KfkaytU +=n) K~-o@ʱ=0݈[.4fƘ=G95 ضmv׶m۶m۶m۶m69Suk$yڕ+Fj.T ܙYfbS/a?_} 8&ӵ(q{ا]"g`O&[ѷO~C1~>G&Z2wZ +`z#wQ\ޕ/DAFB`6ECAۘ-uSiF1˒Sn*D.TnIJs] BY[H _0Zsx5TąTh]8Qi~e7|}W Vk;<2# q5N|4^&N9M3 F Q o-N2WCvA'G nOȕWWډiyl5n֤3®J# +C.08/k4>΀ n>+d(͇;:Z>%Ea?k6߯GQ\op>o9wuaOp-4/5+NSx1=Ib&[׫_3mD RA†ҩY%6tK~o=g'TTh|{D24jٷO(B-H%ތwz[UoiKNi&p~KX|bC*eiu!$BZʙ`( &lu#>wkd7O} &X,6~*O4mW/IRCe4vNֺ!G.ePEb7'>W%{H֠,36 +_~w)AҢv4ӁM55 /:LJC N"Z;:(єeFFAg@+vQ'b *#aQi\XGG#EAKTKFE\nK̶Rhcg̐6j?P/18b;:S7( Qʂw, +o1^5;v40D~SMW{5L֬@(kA'epJ#I).4M[M2p<*AoWW*-e94_ +W +%Q>jz!5-ZW  [liR87dRbȴ"X7- +/nfM%Y:zp:< 1FzmBŷ@;,=|sdm;uwmkL2vTbd)>9YX9cPBi9 ɯՓ d/< s(Tyaձe[Mj'8Fn 'ɩnWTXe7 M횿t_$&@Kɝf.cI~_񁹙!rj|La)kˤ@Œ}  X3|&l>gkYޘi"VxN.{- w#H~`ԯD4)U8jXV~?+Խ|fb +Ѐ%ec+sreĵȟ k Ĝ V + W6r2TTx]d*\?u,n͆X.W}e%ba*3U nu';'RSуP*ftK@x̆ɺhÛge`CHqY^bOIhv=*{yNmL52 qPgfg?FdT9r^,Y7Q$WO 5dh +u~8yΑgVZbSM ypyО,-t?lׄ}s*ߒcqnc4n%=vA4[=lnL {Bzvl'blq/XOG3;#{_08OQO"hKt%_7{$mTZ 3X9I,2>r 1d52cCNb)KeaV&fn7XקŜR|'cykeO2GE.=ñ0U(;6k%sj;O&* ^SMZO1\<葑Zڒ s8թ^@79AUyXJe#cSi;%׆%"l<9hGBͭ?j*E@`w7,Up#$W$QaƧ^s9%Tx;A&rv,xAD+BGehDgZ.Rb4wˣ`qo)8~ͲSUCH4e:)=ln.GߩgdP!"?(oB(_:?0l*`W+NC̿ " +B q7iG YYM7#XʜM?g,@=Nྚ&2 -yۖcPQ@Pb7i|,\)BcKmoO@5Y(vn\DU%{[&U!W}oÖŐ,BTAs=Iv۳WFxH& O=Q4S)$0l_w:KC+^L%ptlcjýS\2bR(I*lT#ۈm`uS&LS] GlNMR-¡vd/0R=~EVZ1 K':/xzV\ f)$I#ӳ5 Q<%}gwpuOV06z.)sf'u„6 DpH#$QiMr5ۭ8P=6FWK+1 嫩jdOcǭGHT@?L$hU;y{+}>JJ.63z=2#@)& x ɞ?DL|3]_猾sa+Eopʜ=#vwиi6{E%?C&$[Ax1$O# HjY꾇V +@P.m ,{ד~SՑ!Q|kzeC(ª斳Pʋ_F>&RDSΩszsّe2I]ɏT}n4eZx܃wusz3E ,i })$@bV5/A<U5,[Zm|  C06;KjnhOnYQBHGYOd$Ce@*&KvG9C"g^oS8JllFQyzk̗KN@؆~hi4e%j9Iʖcל@k [WP dN +Zw.Ϧj2cRV`48eY"@MDʕW{#]]3~ց@KՏ$oBW<*h[ըS<ڠfo:"dWlU]!q:nB:HdÐpÛў8&Kս|)f4@,R9FaK? + 0r!~ʐ픰^-j^#UI-t-f$. nRU۲ +L)b{itsvzi+Sq@gux4 ;e~1?F%8k)P jWKWd{,~U L.ImHJ-X ?!Nwb͍JC㭌OFZoWUUJˀ %ռPbJ_m{MGsԍx;qj\f9`ި DTumd +W +G Y:YI9@zGBneX}3`z\};a`#? S?^Oi8y4+=2(VjzzdD}' F)9CiC$I޲o&,II^.m '[gf@q4VNye$`e7@: ~{?Qƣ;{={@ZˈTa,ڍb͜ih0Q!]@+mYZ.ǩ'&# &N0^$ӭJ 4-EK4nT" w%JܾYbzĄ&QL^/,q42ȸMkqS QbCԏ PX͒;5I 3VTO63%M_TrLqW:u^/ܜ#xa_ęZ.\b$,;5M|6 +qIҰD0SPgQ%=3Ш {u'V30;^<0 9?2-$-l_._D{?io!4{!_:kAkJNBhɪJN=AL70_.5` o?|AXp-/)uxh,=l}Ӗ+q+/g)GQ7n{o=\HFt pC:L٩tyşT5kii6|`έ,lm10O=snz#^W%ܥʞ* jBCAx_'8S*WmGRܨWtD!ќ<'܆OVhJEvF81&AHH_]2Y-B+]f`qu '"Z)}e - J}T}U4`6ħ99,&e&@r?i2bY԰-?ʑ=muBZ#_s45ySH_] + ;-hpV O82ͦ҅zR0d`C%h~p,^]j-Y1v|Ķ}U>}}9 P '39.i`Ҭ_ +&0 +T5t=nk/E2hJ(mvâ¼)av`¼̮z@\G6H^E,.wШN^~v3 UV+HAHPhB0R%v8wpJ UmjS>P _['W 0@0PۦZsB*n3L_uq::Z c)f3_O%'bYnϷd~]@YOs#VCm_>H#so|h^n!#;";zEpzg~io)cLisj+4#2 BD$ bK1K E8! +j&f֓Iy% əy7EPn |]oC~#RF I*DsEiPcTPyߴ f{f7+a'IpP,OXdި?#ua*;Um3MHe"n6roKM( +I7:pR_ + ~֒rr%GՊ/\+YT2' +-&գNX:͛ڲ) 5D~#nLe?c5p`bFgx[c_֥\`7WX{1Vu;_4X5Y S}+)iFkzdt׉Wf 'I#e]d5&AwD1=)&/Pa{/c3}Rxc:!זdjҔoD;[5>XȢ_E 2=R¼XEhҋl+zVCKF揟K\XYB`Yk0UQy!N~v.%q^eng1%8)?4UKiMB<:G^nq +Y/MaY"+}m[K]~wz9Y&z{-z),jl)v' eI_gmQ9] +hI3i2iNTbs܊Bt0"N(oSF6#LVFUl ~eA+><=]k i&3|5pPN B9;a..BlS>6W剿MR2ƑFr2dj/KVw5٥5[80K2`-ݔ'~e(ޘH=&(#godr +iWz7a?jPGn :-wL6z4Nca fre+J=-E<-}%-˃n(O<mV^!I'@UGOT]<#JT p6w鏤zպ|M4]u-ܹ՚ +T,3?;?9e<R'# Oz礇B@15R|"S"X/`Of*OajmIc\ K,Urۇ|CL\xvzjoq[UL`>v:o|>dW`C:~:4̶ >17| 4KsO꼂 +Ѱ&tPYNU]9l)ǯ 2:-Ż[jV=7"&ky9&g/[|(m?R|&`O7 5z*g>婑p +*KWæR&zJp-A,k )0|W>wzQ_g-Ygst{}.iZ?dmJVTӱbe%tA@ 7#ʫ S8G }m6zZg%r3͏N iHI'rƇDo[]wV:iRU4`(l0K|E͉yrq5JDf poG%m6L4_ʭܕ{sPkє Uos$0?(8as H9#V Y.8~NA( +CyNk=Jq+;1]/]|d/$*7JAMa-ߓlrZSxP:z=B)_u~ _ +C^;1ȇSħ 9[Qq=-&?0eu3 }xl ' źȔ^&߰ƙHDŽ:E +'i䟀 >зLl9OT`W}9SD mL, ^> +5BB*@QM_DVfrQ5vjx1Yb: q HG&&#3J8qOrrx~0#XeLY;5KHTˎ<N|r9NtvaJ@! W5b9 o&Kyi3~uryy:t5K tT[U_́ +n@]|@> +<S=Jq#[mFECvwH4e2KaҬ8 O% +l5ѾfjT[MiRC^wb +ju "BuOM΅y97M<Mnv/TVb*,va/y$ҀtZ@> ,{&-DLй<7Oy]6@aJ?UR"OTDY G|7gd<+*͔  SDF3¹Pdr sʸ␳mX'`emx)$:|ZVDi$)CS6ŕvwCufZ̐%e >^sA~U<֖Tރtx8qΖCIbe]h~dlJsL6nK*+R-RՂsMm}v˸Wl^LT`V┾1jfkU2S 8NԾb+l<3dRIA/Tv5u{2aP2zK]FN?B@mg0&Uz:0i zʞ:q~3[Bxjf}gp7hWD ~=|Fj逕R~e`n b߈@|Լ}P~N\"-!QsZM0 fґϠ3< + "H~N2*&h_j}Kd24M'A:jr j 9i0\R?!V/vP,<ڡ1QA886)}㿼Ff$y)_ׅP['4BKuYV^YSf!V q!L万єQqnfU&w}aGwې.#x%}6aH[*!u(x^d2|",{l>v7$'Oxeà)y@话۳ia|#>%eROUUeb:G=u/rNFZGSY*5[/ɞ3 mWK/EXP^B57~CX7en[זV:o@xl"nLq';M_Go 1ܦ-yh1h1dT4LGd-;byA6X{=¸3J'i:᝵$j i!COk#gV!lZQF3ArpS;m s&*Ƭar,-7Ooo#7 <6P+I;M9UlDma p\:0z>jM؜g(S~٣;clƆM$@ -ʇT0},O *S ^y*W&U`e +BִKY1xh.?oTyr9Ŝ:$KPxlx#̐$8,eK82-x9t/̦[_ BFrK C q8Y؊S`?r)uN!i'~w:!#YF\ %> &k\nMw\yɐb~JӘq'e1کO[n#d,"VFV<IdQօ?4H2A`+ӗl! BURs;|Cvvyc]WOpn7!Ȼ(.0}&(%'g~$R@p-B3蕯bz{G_Q }o]`.gGICu v)ձ3ꥢttQn@C_ִ2Y͍ܰX Ɏb|iB QRwn#h'|:o&XJjekL1o7dTQF[+g1z?iϜ>#^,{1K/ +YH5tuA]z PzkD ,{.'EBSJvv_WK2C?ZFe+:OfԔ6Ͽ+q)*lt˨HW. -ʒ.r9u|谷U,$E||oi\~a|ݭ +% ؾ[cx.] +1Ջ0̋|}i#S2;:| +&q, LtJἫOPXࡸ-kɶEX$-PVN[kDQ6 +M bշWDX*~"(ͮ:{0$p [Ai*7'ke57ZztU  7wb9ؙ\sЖ8$p31咲ڟr _Cxia9#圼Thdm;"U=*E?D{Ӵx|U'%qP+M1M])c s=)DM]}W8f0Qix(Y0!Ծ^10n՞qlxFM>cWCL}%۹P!cpUѰ:[LgM 7,XZe@&ކS~pڥMb\cM/%Q_!lq-Wᨫ0@ 7I(nDkr+\r}afcPi5{y=A!*xDZI /_қ[_uiX;m`f=@8T.^N9&l$tEv""eqٔbAӸjݔ'Uv~?'`qnE +຃;֓vJ[jxitq "k<^*-:3G8w־sm%J:74!OVퟑIM( 3>LIU~VHyu(D@+;8xwϡ§L/Wiy>-?簶x . x_3^ǰ@tη2g_I2G17an R=X\AXnxl_tw{. +maBB:GIɗ nj*PIN0$WtQ;Gxr0`nB!JxONƠv?!bIYƙ5l{PZ`Od +5SAImbSg_3L2 e{wAڥs+npu1p2дLku_Cv׉q OVY=Zi0Jp68S3*uԚN  +3t~+t=eu~Qg5H0C0ݶ$G-2U*.#|6_k' kFΕC7(Sz8MQ*9'̼R14* }ǷsN0dU-l@¿n%AkPTWgAZo +AA6Y1Y瑶M̞1Tb5ؐjN_r?~@]g|l +=cU x$#bKZXNw/ѧ&_ edg&sCT%àX%Q1'T^L#ܙ,ٍ 7P)& ܩ)b]qA&bRJh[g8;‡JIXCuPG#z2o $D5iq):\w-PG+5 rocdb1 +@tl+n#sgJGWL&(- wۄJ;o0ĉAQraK0i]ʿuߴmĻhJOP0E]a1S ]~`r,bH:po0;_Ŝbs0着ea%;5%Z˥7(AwLW<]C%Ah'^G/H dH5X%C\B~)1A%UvBX 10"d4F\x7ķmkg#M \/曆/-N%.~tO 9'3)1?$['jCM*XfUCxA{b}2x0TWx!>~T,3aw{+Zw#7G?m5!6X+W 6 S +ۊV.u%%DǞ&҈V:ౚۃBsNq-%vApлj'0Wďfs6&xrд]-:<&].zv{͎ؽ_ٕF Gi!C NZ 1(#{,lHK; =] inםD;G z|35;臷k;dʹ۟Ms2}km$Mw1)G# ɅS0]QE9b0:CI((ߔ98{ĤpMqG_R{? |qw H,o [w{%YN)3x)xӅ@пA6)ŗZ -Xofx$Ǔ^̇,rB[4/>dyj +rA,.=$_ zb'6=j-QCMfq,2įðzdБ^}pN3fLr!g>3iyV<c#?c7 贖[%`af2(xEF(wck7a<߻yIVp0npGW7f1#oeļN\~ͰKp=ZJU(^y+&RQ8x F ^aǏ֩ rCrnuW7;SWH͛f^dQb>S>Ƣ,b͟695\?EO ,ßmljKu7~Td>Ɵ~RЭ:o#ͅcb uZ%9H@Vk j)ˎPdC6L+~6snb9fM1+JL~dBid 9/nM:.ilg(U {<`j&}SIͯ|g{]RctqؔLYf_5 +O b!R!榱 +)<׾k +~{pj0{ze ƶS9|c P{KXy@Ghh1 -|;& :񸳢8_ 'u:f:#ÔU+m{'iZ[YW{ oJ!O\t)ɯ{rm?JݶSxl7Gb3N]\ȼn4:fSg? /I-1->XOii8c&ErRŠP`&1оn)F&Y9"V?ЭDD_)Yvg$: Va7:M+dT CY;MJ9F% |Cmvӆ )0: \Ty "" w]#Nܚaƚ6l {S3=`~GCbbHPݻW=TCϗPf/,3j("}Q:xfbi;9Qԅ<,29!W2#!~[Wj*[9 Lە_LtH7|bѻ/CsYҙjK]c#̴` ̥&k:SBTNLO fp +o 5vR \7&=C,# +$ 6Cܛwcb%.Ͼmǐ>q*0[_(2c̵`u%'8-(ƺ^e`4:\eAG6뽒}d6y .q3Gf궱^*Ԫ +endstream +endobj +305 0 obj +<< /Filter /FlateDecode /Length1 1756 /Length2 103623 /Length3 0 /Length 103595 >> +stream +xڤpݶ-N:m۶:~c۶:NǶmvn>[[5ysyɈULvtL܄FGg [0!==+ 5?waNv‰8 :۹223rppy`b'ΑPΈP`k u!p03w(wFhAhnamaoO(IOhgmMox{-_ t58tq"3%4s5#wUL N'P% ]-"tp6G+1ښ* \}{?‰9)͝:z'Sz[3տ%9,Eֿ#ߑw;r]?+L?W-J0233%1LDO` M, f0 QzMx3tvp'agdd"d1/ AXQXHA?|/;#'3! 3'!;!#+ϿS*Z%1W!?*7qg5!߾"w&y;?]%+/C21=gϖon+/Ƈ%7}+ x(ݴ?xi0hbmToS6OXX{7 u ?I9K_c$n0Qp6ju vNH[V''BH'ۙP?}0t4_.:Zpì,[w<}s; &*6F5;F$% +.ږ&iTa0uA{c@2wlev'2z$ qy1Z%odA$HSvf/<DžɹͧaL`RHes+z ؔ_,Vn )ǗbW̯y2ϡs]Ev"Ff\MXޙqMlkx(etkqbu%Ҵ](>#TFfd\#Uڧۘj5;ci/Մc/ŵ!P?\~Q0d/׎To%YyOJ^!ñ|IRbDsN0^oj8k:$m'בřJQ==;( < +kzD&ǃBdZIr`uYŞWdA2۶҅ysL_HUhj[ӽq 7 +|RbxgbٵcwhT۝o&ͩ/YqxlUR=tz"Y ٰ|AAAWi$Iu"/9^mR l2ÙZ^2AD . @ǜޒl;,Z32<_k'"5)wJA':ǯ| + AUݑJQ;9^*h@4 =qC$O`WC苌Fh?;c1L 'kƄMhDGW` +!!riF '$ɏ+8fԘzRt}lG}k $3Ζl2$(N'1#5‹Oy r?"ȗ6ۏhDVZ?Fq4~6w@iq1ǘJhE2ո/dz}qm-\\&?(Q|ؠ>6tpd@1)vŖϚE3^2Z/DA+xw-1˫?E-!\;1MI\4,K?"̃馣r9Y=ܼ dO '^^`"DӍ 0/!i'voXN`|?Sm3IJG䟵K椖Yydrn#qV-ٛ֞)< +$+l&.VWN,j͐Hw0*㩾Oe)^5aҀ?@-dqHՈ'akmS7aO0y$۲QMPktVlmG9pvT+ 9a=xeDSpx^4/SIv$G$c%{Tt$@]$ QC7:,>,=5h0i PWN#ܟaJ;Cp5 e*W?*{%,y,^A"0?5+F:Q/9E/Ų(4 XqM ;~bzǃYD(MEbOLtU~OzG[RϘ6 t=N虧Zq7X~6p)y laYݢܐKPW%5!i%1/ME;DUW jpۥ[ ~9d&pFuJrg_R1$D(aonn/:C`gHOv@MDkFVen;;f["hMT>lVlQʰR`.9)/n.'jD"='_ʷJvԨ-)^NXkf!w&/nXؒf3}n!{lO PFj[ +ܟ$΀fj424:%ՠb5~>_h50 +RV{#S ga^[?u|̟b.Uše7 0`b =i#fH\c#퇂~*8gBۅ|[>!uZ@g0]D{ 9VKޕph#56= EOӘD7hL7!:}-֑IpC ףuU q3L+` +}:Y_ݦJvQI%^Z5Ro R PDٽ"r#]U8AdE\R8ˈ[f/SPUtBX)'N((.;I0O/M]F]fbO>|`¯;4BO{.5oezɘ΄}9\<}wN@:;gX|eT۔lDqfemxupcSNR !n&7DJbh4˖ADXnB߲M(X2晙2pӷLau&+9$!w б W(*BgDQ\64 8.b~q{eT> fljsn Y?f W\p\YIN֪B2iqd1ȝ# $jW`aIfo<#/odJO`L|DĴr~2$b84 \ԩ>F6vof̧{3 +A +}z z,4%"ފ9*)u9|T!ēL;D^'d [r^ ֒R=LlGp=)nHiiGE Dw ėwM3rIt>kk 8ц-}2wr:pܛf'<G#w;溾!TJȃjq)#"5O"]7y[9NScCc|k}%:ڷ4t50ɮ{O#h RR +R?qfn|9$MǻnK}ҢkRnTja?O%엇Rh\t?s#K_u|}( #j/tg䍱2(aMwySӳ]w|%Ŕ4MFrs냑_s 6".fJigRxC⭾Aks$X"wtv705.u@PQi Q;"*p?xf1]3"0RFjJ?RmFhѠ_9fb E?cYE47-ﻏMjMGQ6#jg|қegC?W/@Zn`V1lkGG s;i(;|jfռY ̖@X^F썳!S?:ʹH~O$uy yX)bnRjRH*ti^7 zF*Ec;)!VEr9jCm S,sҜ`t"LƆs)_b 7pRB bI(W38Z^3j#~&՟0w00}tF,HP\) >X^Pϥg$đ&㚒NoEvVFvzM[ d6'Pg,g=޴/k!*%In6=7#T}Q.~@)iJp;ڲIOtlrALEoZl +Ľ^TGغ)NCnͪ}3hKqVhTgp$6Kȣ"=.d[ X60Uµ帔iu`ubUw[NCl:>MZ¸TSx2QJtvn"~4Ƣ54~*i7ɡOlYfj5p tc@׃hVE/wFNY4=)kr2 IeGs@-srK0mj$EL[C]7EM"8K1 'F%tz${IŒi{ZIOd:]k0}ئ>\?sq3.+bWiMVZꃺ=[zFt_~1} +_(:҂^olA. JeQ)jG5r\b7\yY5D:ŵbX3UrRp.xdoW!$gs+{w 1f^p3e0t(2Sgva]yܗOoZP.{՜#E֩91nG͡J*6'ft'g]HO^<-*&O$SԬFՈ>s]/YOH*d1XEiD8yYٞ}w`<):U{ˡY"M㚣B += r4ӅׄyfWb7]|+ k/n8~D-#+[kz7ii0I.=KW9{RJ, 8S)>#x&j=w z|ih F_ p.2?B8Pla j2`]U(y ~rO*Xj4u.5P0: +AVS}/mh|^{)8j$VP }^&Mh)r=v5y?E[b'Wg GRGg (%LLJEޝqxlY3.dK݂m3h7Pvm2(¦.=R)k;O~'8$+ohi. U+wE0Z6ZB#)ZesU&`)@dG(J[Vؽ{ vp &~Q>`La0a5i/DO/VYTQz u >oq-[l&` "%Pm\JB2ZՒ1DGH:V#28m|T%ŋ2E⾂xW@eȆn[3Bmfg5cj|ǭ n+h !íZt=iztoR f$$c!$+"7J ܬ֏#,eL-qOL5[OȽW=F$aBk,`[|۸ԑohZG{K||˽m-C[}*GP^xg5*c : $i[lC %aӁ{'%Lk֔? }j^M(o,|+Em4Zeњ) ͜]'EjLMWB5)=kcjPw=C S~ݱqFnՇhI}qIs+ۇºF)B'.< +^ݲi<ܥ gڛsÌ(8uk " *().猪; ,@-8ڪv睊e؂Bːvt LoA5 7|;˟k@H\=o IKK8\r];zZ=31LA][v +iˆyYeo>m$4sBRz+s˔vB )";$M;[w +|@?r &}%b ++AL#TZ5zuTKQ,}J-&PNNtC!ku¨&60isl>^[DgZW@dOZnɺԤ5Mװab6:`/,QQ曑g\;$$h"i.62#c; G퀼=@@\&i-| +ŘDYriO]d +Lŷ1v^XX'c-&by妿еE?*R"x 0C7M"߇Ta4H,"YqŘezdt'nHgN8s_ʝ9 B +5,( +Q5ofjV,LI׿ 64=b3@'{'Nt_cI&Q3ݰꮘEfE吆ɫ@Q{Yy0`}hPǕW>OPeD5Tގ(Ɔ(=P(H_0},I!5}ND[ D cFJA d-}J}1Bf@8T +m:g?Q[olE;Q?eY+BEN~ +nAT;G,tf!) z6~2*"O=T}po _D*]΢-V_]ǚwu賟񞰎A'w%>@_T{Z(l픶]g{֙ȁ3 +"k0ֽ֠j0 CI%d˯9^] +5@#Ԡ0-+)>+XU9=‹$8}+^ӋSd`[}KΙaT1@h8|М,>jgN6rۀ"x{'e[D軥@׬t2TI Za#%_ "%3MMƵðTF^Bc[gI\ATX`o1#FB/ e xnVSz0m:?t8.Sft(gR}ٓHp`mH|WR`U Ȉj!- r<2,Tb-;:˾eĢv\y]qKWkJb}v]؃i7:%I#w/d`Fs(?x 2;4k&x 3|̓σ\PZ%f!Sv'y6,B=>5 TvpѷXpǓ*͟g 8/4)i +]Cy%mDJY;O1tgA3jcm~n ,QGB1['V~pei0UEh힃ە@9sBoj3dz +NA)B9\?8%>N?ȴi,?FOd䀐Eoɻ|zVjQ,e!'@|xr4=ڷ}AJKpO\H&j݊"BsZ%qc7P'\>QL)=ŒZieљDǫGկ.( J'tռ=203ˇ$7uijҭ4 Үqw5yPl +IhMZs8:fh kSuC[XlscU%4 r0}^Jz\RK„ь⟶3=qAwzڑ:KHgj{O 34(옓ƬefʆdKEc[}U&#YVocdwN1#X3LR}F:kj)A)QCKpyS.PW_ e5gqT3EzkE= ?3 W3LO0#D^C+2z-IQVӶ+LS0[Ζ^zKژ=3m" zI[ Űǯ]z/ +DNCy]ӛ϶j~gP"A~=`^ R<mAVđ-Qdyy}~Q*"3ԔVڠL('V{DN0!_E&5pP̈Kb TXESDEd;XIeevpS Zv1]hyCBMoȿ]e 6n1SB+rUX"ryuA!B?iJx,zW^MM7X#SU[;&p +m^SPFbi .ꄁv +GB#i/)8sle]ipQV8j/v*FR1MU_V5y_KQ}tԬ_c0*Uk)tFϧ&& ɉ+GK>Y0*1a"?)H0׀X꟔ +̒8柀T?fgA0:kŃT%8瑣#`Ӭi3`*|8+/<jb{D@}* ZDO.UE's8b8>/<6:׌ZM-+mȏJ[[0Of2Gj9eFuGߧPk:H +j^F읮J鵌҉{S=hC7 }'u=DGiDKԂ8' J>s8 +@YV!Q00OuKs nDIbi_晭̈́UeeV6:IiZpI@k2,z6-7o+oi+IտJ܈oG-ƖtFm@v<uѱ"'%'4د߳zqs{B n/,|QDBbq^Ϸ8ҿg}Tؘtt+.֎|]FFu&>KR}ҝWkUDL&tA%߇ncI4ۗ鬙 z#cP7]D@2/z=DBul[yXl@Q?lc(hS +h&:~EdID=jęʶA[sdB@ Ȁs >&gu7;e֘ +T1iॖ +ETQt>'npeEp] +)A*G΄ǪcT*TnY|Y=@o?E@\9B*mbVsTy6#\+3+Eb3E*$,p8M#*eDvf Q1z)#dp*SΖ 7?-)o%u_wZLfW#qgCs1{;w4׊5)`CN=<$5]@坋Mv$h]ZuB}cpY  +d)Ƞ_; Z< yzE/=yY*j\~FPmHМ`䉜ٞ;o !7Keq]<ʃ*L'rY?ZJ8Dqwye &>spS_%yT >xBjOV{ېr5vUMH Lf`q6fȘAuVZJp̽6[G5C,`A)*O t]W`y's'̂@W>]KD^ćl4BfwQWeMz\ ^0L槒˂1ɷaJ|O\tK2tY~(3+}}j-5QjX[T/c?=/v6aw?=m:*uS0PvnvѬ\zamƝfLs=o.I+@1+]$yp҉zCwk87r۱w&ä =`9fVy׶MTX3JZѬ2Zًq~KWv{FLq2 <Tub%%‰F[>]#=ծOrkooSY"xĐ34D3P:t8A#tZ/O;9#|@XSz[y!T^9a oW#cHhlѻLe3sT~X5.]WYȡ J<;&/-}0 8{Zǰ=ۏQ# +?1hy< #aZ>N(5s.0Á[~ųw]&%V>^Jl˝g"!JzQXJ5p1rZTIY0M̼bCB1 FIi5G8+?#DϙpE Ή0iC7Q~Ƒ( *$?D΁㺨SJ:~c|Nw;țk|go?é}31fۭ +RHQ15-4e.)tаLtQ>N3ve?cx[3t +'?K1+ktdZ/x%FvO40 ^S@\lW^oL0hTYgcn0WOm!'swx *.f8htκz 0շ&? +Q(.Mjsbc?Ɠ~ǬXfጽJe[N*渗X?w0bj  yu@sø7g? /KhGLx*G+O M'r6Biؾ(6G1y8sfD'hf6!VҨtsJ2aD*!!+ + ^SUfq@Ҋ O+gw!Xf9ZHKZXZyA|E9ּ,7+C:jF^t긤tQUVt@*2drD|\/UzKoXZM. ‰ipu4ۭ ].=I%ϣr^tjlϊ:jOn߆i; +rӄN^}{\4F@<>9wrYKܰb1tHwa:7ktr}Brqeb:ŻRhY*dHkIUx&M]FaN|5o'W#3RNބ4mvc1Fl6 x8j%~MkRD@(sHJbz{y\Yi['!qgD1$X7VCܦ.U L2E]xyٺ/aN釥ǤȢrS@yy[^gI0I霁ɠnDɵkU mRcI?0byxR9Z~gNBUe3$Q>tAkZ fRve!H9(qjSz ?k5]q63=+<@v45iIJ+]p==\I,3FAdpR{c?@ƙxZ4ZqE}}[*vW ]m%Z(ԷOD|o ޘqBHK0<6 <gzOP|3~CHz)h#KrDVo'On K.  ɤ=ch f(&,(tpq~c] hZ ŬF6[vħj}#oJGʔQ(uq'շܲ<~Gtv!| _aQ F=DgZ]$st6߰)b\pJ+xG2iŕWet :r̴"jqM2^u;fJE9MGy\TRiXm߰?D.MH*#ew4CM jo<ƞd[ΑV3sg +U_J]=¤FC$Q{IC8ŘVO '3j{e +|0ܷ1ՎQAzho`.<U$zK\4J'qQmoy]E 'bgzKͬ#M q(LDxðVNSA)Z+ю\n|{ ܄\v~ bOMb5OunO@B`u9U$+r["n qٌ_SJ ضm۶۶m۶m۶mzx& :Ӻd H3t5z3?ƣ\MHq/ A,Uz UӶpZP:Wɇf!TRZ +Bpu鈰{^, 1}4|fj`P sڎbr"u=9,_4AZj7Wy&!Zw=~H*g]c69(+BM¬jH:YHu/67an!!^ pyu5εk9H[x;`Tr pZteS&GJeB¸TGd<*gHrv}Skeļ=Q( ߉] CjuRzh[7bz%]YǮ7:W@F+k,,$ˁvWD7`Wsyǣ{©6$9+蔆0ļ^3x9W#H)<θ%! Os#?ENVqa-ߔ@*3ֵd{zX vu-2LVXqJ.{9^1hý sq; O +)G\{-5!]e_hjS:XTu2∻@I+%i[ y+MJ$8* 0 K Q~ܧ/ub.eGVW莒XPZNg,gL98KON$ $?Z"Y4%w8t$?84=;d;V|*W]&W2~#bm`G6nDZcC`2i|< LMN,Ubc8pϟM=!ȷz>sˏ`9eK-\7۰ѳ,z|=6y71͑HYA̐;;^WlE~.Oi 1uNSً #_p_WzNf$A g(&ĈoJ7n@ sB擃BSEz6]8P pCQNqLOqs+noX&Qfj K;Ў(w%aMIy7J{LU@,hu~! Q^7' }cx2+wU;64P-GWR%BW=)9}XOs|Iճ,g%$ FH:j;Ir-`uߎViή~D4EGq#SZ_h|ZW2ˠB;dyH]:Y|V#R6 v3xq?G\̹?gs6|EJlm-P.;M_gqDXhoy<`4a8Φ^wLr= OM-oヲZ-N^{-S)ŧZW|sPԉO,x*G5I{U:BN?lP' C{p)2yigJ9}tr? Q +l8*?VqW%Bp:-DQ؍ jw[Ht\1UԣcɍEQ:J:O&7e^"QUh hԅFzČӧ ?Yu˃iGZc%u"nPx< Ʒ")=m_sH>sB] 7{Jf;@&"㜄YŵsxGB[EC*SFY&C4b +X Xīg&O@m(O?< UgROE( \ڏ|~s|'4/g}նoٰ',1bݮ5ƕ&=2_LlcWDM)/RfUQ9O)?SNj;}a<ʄ,ٕ0c%5Td $pAoady- + &ǺQk,=f^b_+ jEޮ[ !y"0pAјv JBH1뗧|Atv@W@|xc̹ ,M8vOIlj '$2F׉$`K[ZO' +,*WQjzP+*x)Vkqs/95]i 5=s4A<Xv{qMc +PVpg a)o Yyt3_.ʣ|M9.haKدYqo7NXQHQKg}%Ѻ%-@?͚~'n(]m PDjuvk'syնנS642vma).U "SAjT)'U0xrjZs{Ppf^cڙwMz& +(g[}4r.,s"2'^@4=jMfæ Mx(> kTtHpQx])̺k?Y;GFZSZPѡx1>2)HVQ[HJ5zw:W?#zs}175k_x|Xw0ə9l$^%-˼u%)[hjN]Sa6UW؟(Y41aY?.ưsG{RTob.xzR3<׶ XK &m٦zhb)4cڜ)9^5 XGw2v.A-!a=\CXxv}?~h2mot hwޓ"Q8zK{ Hd)LtФ{{3ɑ<"azi.OĥΨv7r{>pd0W S A~3{+gh^匡z| ꥩ_znG+т~=٬RU0_˹cZȟ2oA5օȼWKbKuΊxEdO漘T_8<^YFXկb7SW=͗H&hWOMUr4+T6i~K:j.(a0TkJ%KI@%-mG2#PQYNɽ]¯%RC1|&F{]:?Hr>q/"%f +5E +>,&P'hӤVR1 ٕF]^=ԗ Q6k9O}~ ETze0rf,z,ZBQthqN7aLHH 拏z7mߛ5ɣ~gͫ>1wuV*A8:4 e4i#H(BlqO o)n] &eLLnrTg|Jܕ;BPV[$~QUc,qBZ˘ +rFDQ3J mD ک0Wt&^0}l[ٖSGkI%DzX< v^G'_;mX}&:"^#CEQbom/F.Λڹæu? ۆ=Eu7=jaq_8 +vZ80J IKy^hY"Ve-jB %`.d|wR:*(4{߬&=@Т\ၑ~۩]O2B&Lm<3V|4kXf>H/h*5,sZuy;XnHK[Sgu2yx5sM:j76?x+3I`:'㫤B'L>h&5#I>j6+TYII|l;nt# +3| 7ڈaVzdl@Y( 9AhJQ5rr@Jȷ2L/Wlƒ(7~;7J3BE(.YULL4wڇ3z7pz#Wc!< 8O%pll5ti` +F@ .}37&lsĊdzu[<Ll|CkpE,N!5si}HfF 9l?jS<^]% <[?:GUo})[_1 \~-:YY W ]ؤ}Fa,80:o ˔z/ВGq;-";P:e5趖eة6/ލUep3,8~ߴZjq^jam,ONMˏJJN~TܔhcMLʖ]RsyA{iop!]Iߜ@c섬 ۄm})d§+r?;Kmk 18wLUï񔗖]ͬv`0 gp$ȆF&,pVp {,_Rk2clUl2rgN$/E|rC۰z1Ԓh? go<<(/hP?ֻjei)֫S1M)ݹ)!_>UM t=$' q ] v35+MfDnX{69t،{g@5{Ƚ@vV}hZ-T.Y( )Ai{Nx4AHlv_&56f8 +lKwWP4Iq^4'ZQl"eHGA}_n$!y_87\`q6 9=@C3BvKq +ɭ_QIe3 +İ- %eNosemofM{=\jO/W= +RJdRxq@X!,yI+:>mfrwHV:4ey~aE)ɝhn!A,cg0OTad>mʋ] &=6VhD][Щ0n?Uܼ 55;滧Pj)w8vUA5ZM:[h[ی +0iP w?vKYt`?CĒ!8eϓUoE]n&xE iwژ1?5./6H +Ru]eZ͚6;,T2\s=xYi]3CGd_&lpK @zOfC"{I2;\b (UpgiV ȑZ>=:K4G·%$ WrW$8bI#g̀ϻ23`|A9]LkV!f9;@n',wWVTP;4y_ IV deZG]_V +EYV)U1:DߘsυglU`Ԗ%o6(w֟`A=RyLM5SzL{6ͮsY7]y_[mMXw;Aqy.sAmn܊*o.ݤFimP|:Smo&K6u'!19;kDg0ލhKDO„!> 3rf;#^O)4HEQDk/nޚC/# ]1O2L1OoR4*B!<<e<}ZP Wˢ@U>Kggo I`#ϽV]GuR:#yK?MfAB\R !IUzƢ>ˇݍR?<&jĀ@s2D 炓ˍižyf*OlOÐ[= -+l; 37r<8u"rijXr0'L-a&BxցsoN~lYCW5O(ix. L$L)Xnka^<˄jVcJG`yS@sT._B,2%>PVNUv\+4p^v=xr?={;| ) 0IzajX"(2ɃP xlQSyr"^|˦%LZmYۅ .p^SvBRu)XSHxݨ1\j{C(ERj-QUbz_xC)HD #F&{lMzd<4qf0M;Hq[ A jE -C`ANeyңb +P䒋 נA SIBԒFp%4Uͩ9׋cfCK32S`{0RIQ2?W:jDo'N7CrFr't^Λ-PwW)QU3 r&^q%Ht*B4eo`|CnT?k|I:ꆨ*V B Ws^F42z6 햽vi `EGE>_ˍQ*n(~`w:Q e}PqpNd~B_Dz8r^B,kE2|OG 'a&x8,Q,|n<ʘysre8~F50xP)gf/GQ,ylVwA,N +A<)dL~6Y(/6ͨ"" '4^\7_OrYv3=H?f?C\ͣI]eA,]0` $7F;~AW > 4f@ݐ2Tx}}IP'8TQ9K]dH*\KRn=U#e脙ҊE:P[Șz䶘]W#w`o}eZg wwbT,E$OĸF\şq暸qb  ͖nσc}GŠ~st7qXOH)]Pw061ćXFU Y H)P]FDqg.Ш_dۡƆkQ^= VCNyGLpkM‚ձF jf9&s_}iI~3}0.3r7_njvAr|xvY@#L. _&:9,D 4Xs9^7r2y c S$~ӤG`ӞZedYOEMcÇe ٍ/M#HIE<ζ?ݤ`l& ࢄn[GJ$ LkGqeac<]x1~dύV֤9Q^b/[+poMb*! 9{_HĈ#fPÿZR{U%G:XցONTx"M d{F0W&ZGܰvPv0^-! +}3K?x{HeGi N`Z(%yks}4q )60AM(S^yh{֕X{}nlPèm1mdzO5/#M*=Aq=bMQ~>56!HT>LU(|L/WKM+õ@hc.Wh[Gep\=`榻 Td"Q5cgxts%^Z +lMeYW/ӮcUHTPl~s04Q(ͮk!؍7`˵Qζp)DzywȅAM 7 _4݋` 4b)aL ';kk.p0YgueHUыT̎֒7z \%R=v cЭ@쯱+d +Co$t{rJA-sYBP6T{h[%,f&ЅeJcLd{MƐ,k<6]>{vAdDqStCsM.Xv4f:Gg>7[3Dl>A#Bs}7,q$tf |lYF\Jh~6[ ŽC,ȹ4]H,ppXBX_ gU:,Kqh +=_ 7F#[/wID˖5򦃭01i%\ٴ ci{YɉvYe|az '{m0n|HX^<J7 |U6jGޘ7uF*n3rNsXaxʼA'̠INԕIE!"VW~˞'0FfbW#0c ZPPȳ='f7 +lYjY"$uYy^\6z4_ ML3fXJQ M `~pF-—z U|uf%!Vx\h%@ġ)T`ͣ}}}׏_)Z2겔NX2)Zu~ݫfE|Dw#Cv/?[. bq[픜NhtRͼ'HAZϘLc7{VĽ<V>&"<ӯ6{~3p7>;YM"*Mm/)$q{ᚾQ#W0ٗc$Ę4;LwJ2V'™@Kx=ڂNxX 4>DEu?1[ȀjlM[7|G厡"P66([7_շ-cZV]#Q d +ӡi~R6'eѽAΪlSYJJMjkm]L$I]L^(hy` fO߰Nwk>Pn*ty ^4:gI:*x*zj>Xs$jl, +2Nx&UFQX^ZP:fxS!V0~j]VaTR`e-w@ 'M~)(xT9 ;&j& Hi!T,:~F0*|q~ O2)'p~yRPzaq7R`Gckl*@9f(8h*Y~+ֱ<X1{|[xY=:8M5"D+Q̄N\lxUĜGCe8.1 K +30Ƨ>LۜGw) 0s +9# b.c]0Mn~% F<[a.3+E2u>iPU"޵{t冉9m"d2p3~{$KcΫɼ""c{y\n0pDXntS[н; c7,|qxEXn>WWJվ3%ZodǬ.PKo.fp4- +l{`'uYѼlEEHc/ +Ki,uׂ 8SZ<6~6sN-5Y!Σ_z\Byrsbd gQrWrΟfFtŕ ϡ#Efؒ-b @8M/fVDk+6_k @J6"Q 1J;2'@ƛ"4vsoVxO6rF A{+.@a!4nUIw8yr4ŁqxyrG(\kR:g 6,ѡ%V*sWEin ̊ Jh1t簢tH8w=gLjB#'3ݨɆS;Ɖ(f!/eA?-[IÆpd!臡)ҳϟ`#Yc82O~UTN- #a"Y6/k󔵢'iDE$)(@vN2^RxxMݷC/rI b.= ~es|QX9prWLVzzBe9C/raMo\֗lG+bheMiQ۟,3ӭ1IRegzhY,Lh!FMkߡx<>ԊDRLr1EʁuON%7UP??=^V5ĖVG~ mSqʣIo}iMGGTx@ܵ~޷RsrED_ + % *Ȇ4i췳Ť,Wɭ&cuWlYP/}6A +}zжT"IvK]/7;<$'2W<.{CwbU.3:*jVU:~N1A +# <͛;,P&zW#~CtrRD641#HӶJЃ!UoѯPejTG?:''x,aȑVX| +H4n}*gh$:oH6X&%E 8|lʇ8%8̫}(A} a{e_}aHO5@NHc)qSěM'qiuط{ש7O7FCĖZCzr.M3aH D>{οl=H*C^2ز. ~3 fEӽ FZ/VG; 3/R;Du&~  eYi0*Vϋ[FB2+0K~^/ߚXF:|TmyZ8Z}bCr yb\&< p d#e +JD.0AG2vRm7m20 FW$LR&z`Pel‡ n;Q']&{xP"mT40,%!tDnD/T:{߶s V\t088ɓo:sk'ȋ:>"C +l]Tu>PYj}QDN$O/aOћxKdK7LT~]`iIɹ {fr' 3SAh1 +7 +ꐟ\yJϱgVXЋ` =G~Y!.%߄{GhY52D+U5T3*RvwpiP`9n-V{B!I 85aD-S:+/kP,|6ɾ +ZxRB&ÑoX9Wp˂w@rW牪m-60;PxE{(IjD*Wm⨁Yq4wfr5$*{eUp%糏 ɽxK«/yT?|6X 1Qwsj:wM?"7EJ)>xq,H @n\wB’ZA1%{o`CYk%rS oև(&4 *d)uyCnoWsp2 cJo\~# 7JEqLy"GxXf 7 2ruX2: | Hs'\ + A]e.["нk5W~k̪7QL1=6^Q bcJ4OiG7A <4'9Kr­= 븾Y*b95k) eI*zy7Ró~ؐ;}ysѷŏe ٬{IP(_etiO02[<ؕn1iJ퐆^5e}$αkVK65{372ZG8;} mLy|T10qP*puL쪌V!v<~1`eKZ38;;2` A@_*m^ǣ*iW- 06(6&Xai TdREқo-}@MY]9uU-$0`77Tȫۥ< Mvx_=I\8XGV5uEf#ňؤe6HW +ž7S4>t?r6ߩvs'XX%iO5m~ϝm IKco^d[?nA@_H- V~j^n Wcш+lPSjW;} +ȀA68 {GOeZK֋Qr0> +bR։fkp}Ÿ&Rkx L `^@~`me5!$vBFw; 컅npIG%z^6k%_)ĸۗZ;&S } +$QIÉS| +m!#qn?UO:|Au;iؼY@xm6U+G5hOeCs^ȟ'/WMk9 0M}>~YruNh iZ\&R8΅ý(V' &uaH"BLTHiN -t.jZM.لS/,4=Q0YHfs0CO8S#gC9OórCoڋ43J}6zS'AS~ \ήە)d3=MҚMky:;I9N o?{V[;M`6Ct"E.ePPDK:v@~HޛRiʧ+ K!rCoD&=\4 *خ+غlZ??720>̪Y=PrevTfe !o$*VY@z̷Q|k; 3~[%;.cPAD1s%NEtmV*!lXkjĝN!ei:()8IJȋ"٠Bϩ868T{:-CAq)J$ dhEICNA9?>iB!KN!F3W%cñ`2,5BjMᰨ#ڲ`P^L\~MiIepPKԍe+bYވDݮGY2UϮ"֖%KG+ L^H7@ap}fQ`9T%~s{oisN|5GRI?a |]5.Cmaw WE:+m>{o'6Iw=Nޜ/H5lY.Р˭V2ޟC[&ڹuVqa0NCNZX.Ʋu,|`$d>YV]QRK+YEkR|KZdю My{#SU/`8[.чڻ)Pͣei0Œoޮ!Fr޳P_vu V1+%TXs`#'auVMs#O ,,8-BdPd|XOjl +Y7hWfG[sr< ƣHGֵ +%iWp:Vp_";` ɍ|>E|YdWLa^ +K! >]49 +I;2*(SݞvA*A8H5Fo/y+vT#n e +-'vo|P@5xs-V Um_tߢ{)ݹ(AlFCײt'f=M$#!yNŏ/N(?ΩW ۶m۶mxm۶m۶m۶曫IUyגKEQ ?ǜ@VEˏ` Cw5(M._rq}!CB!ש|s@mb^XyBvC2[~̑ 7_;-馪xᤍ-maçյN D{B_¯mʡgnbnj,KV,%P' T% ¤76 K9԰eaa;9};&spLqWf5`hhV!=IW9ŦYQګ)eԱa,c-1y뫽8a>LV'yߎ }fRam_5֚%CĿoRY[?sǓx]C걹B<;yLRTA]0m#컞 lWPy|V; 3B˳1W܌J pUFΛM Gj7`%wi,”ܟA]"PK>n08@7(XU~*,~ 7nB?z,hͰFF]EH [,NH +6=Lz! SX@۹B./A&(`:h fߕaB)hNtrB +ѶN8iiG0J7Hb2fE-@ exrdUdNp @hH[&,8zz~$9y9!dFGt/yr32B0T4xvQgNKK&}knجvVFOxỿ4XI'* 8;@+7НmҹkHŅ`@\(m.._@ϹeO9Ik RѪ3qW&b +%4Y2ظ}׵!:[4S`W=Qw?^] xHUKž.q⃟8"N[͸I 'ܪtSwU۪9Tz#gR&iOT;X`h\䝧bWxLjbq(}J}P<Ŝr9;ʘNs2S|-PGA_QK؂%6RxٴxI V_4,}I2`'o{˖. n!?Jb3~?KsÜl{ O?͍|}Ҳ(*鵑q:Y8un!$c?YCz]IlDü?&zM%wvaV/p҆VW/v\4=Ё7v-H rF)9yxxEN"77|Rh ~|xy6D`HN'9b- wTF}_>7Rx+,$Xt?HxI5k9HOʉAfVݏrGX5. a\MGU\{cuiE*Hfc_Dh( +_Ȝ8ܸxѰ '=uGGfd1(|Gys7 .4V/.{2A%B,>6KTB!T#b|5VqӃ9-|ܘte4Q"7ogcj-k +H"DA-wF +xןzgDdAfu~=cbE2׭>'~6S?Mmdl@[-R@r\,@)-|`>?8 lEѠQC6? i2".$If!@ulF2w,R'mD!;Hpm|hݾ(06e +"r!PN%1)%U$(nw1Q[´t2rBVvM :iȑc10,!ōb֮P-o{`x#AM@]4 bM qUs]%s|#,ãٺa ZW=To{K6#SZn|W )~&MLUZF)(C3"$9ΰ xQE[AK@|$NNO(7Ϡk6@2Q;zt<= /I00Whr  A +Bԃ9dTb4y_ I'H0 }iw2O2Nu/:ܥ3K/Cϔnbz-Ր#`[q3.~W> +Q/d+5Y_PZ#Hق B@p9p`8hc3ܓ`HyKC_#SA p1[D?]rԏVS%q"ג +UBVBݶZ?_pDw)A%{^> ſG(^RN4="A-~[],uKZLiEfe$3C|6v~ ct١glPaq8c;}"X\ ;uJ|xrKzHNDpYt9}7ߟ7(eZ,t-eky/KxT̓"ߌ~pVs&j7yP 1oEo5yY>BW&|Os,uS' I +m7#TLԋm3եNk(bˑzc,~֟K +)74/s&A-\]ㇸ^=Ӱ +̃Z#ZL*'Pńε|_r 6aɦV>Aj~jpi6M M{m:sF%g,EI29fJ,IZȘ]otf,/+b=E +qZ@ͰIa+B4xG +@f* +{v[l̹ |ӢF:F iQu #{GC.@,ad%9oLa$M-s|`؊zliGW?:؜Q$5 )wSEQ5)'׾mp@l7)ɬٯ#rkvARAesw]0L#,!*"[;RjO9^NhBMu$&Mò]p}7z`J(ۡwLZ<&∝*v$+af )No?kvtf8 ŵ'X; KcWׁX:P쭧@ZIGm=xr907e'*ئ8ؖ$!Po}s!ؼ%2c5Gx`)f1m*;sg^n 1>Ѝ 6,k/C$-ZU_3v>\R#^G>2L>s[.9%ΌjV&`ԛV+:aٗN 1&n1O\vT7?o**&We34|^ƗҿGG)Z[l8SiY;]rNACש4T>MU -[/ME%sJ6LǖP]7u-ߜWxSu_C7H="?v3> k=ލzGlu"rMlIF]a34Ñ/ZQK,1f%M/8Ծmx2{ŤpM[(Swpy1 +cݷ +JmvXQiAc\nzZ|' y"w)x'뼪sA8`+s#R,k|9f/8'[e@X=wlۺҨE>6aS-{A6JEOln+nv@XrcgVMKDR;p&T䵳^):c8;Е +?Rpc,NKM?0|Ы_(e2o#Gp +~Qkp<]E RQ,rq4CgIfR0x_\ 6$~I|0 +^FFǾewl9|Tr Fmƀ PF"]SѢ>qdfsEȰP賯·M(4%FhJj][Ur/@:~WfV#ǥbMTk6+CF]hsڦ_e2+VB[EMiBK p5=ݧ +>^5PhƨrGن54-si]fhl6?N.+2s,<\kLLEt'qX9l'Ym, Hpﷆ%ˬy9YN%@ ˁ-i=;ˑ/.Y|3! +y+r@@dh4O!u3`B7W@F]9~`)kt(]``T]#!$y S"*<>נ15Ǖ鍑oe`@U%G8 yQӵhvK+觖!KRŞL*|>brFE|ְyQP}#qˍvv*B$}S]0xoN\oMW,<"={ {]U^>^r[{,j*'prd{PwV`Mo^cc QA?WmE:ľO--,&2FM75g-S^,aDj^|~TqUXC6*D}Iu8=kơNarG,uN1b9z%V"Rx_c3}Ǡd&b͆c/܋dwÁ{s 4U~YHYTqB uhH"spc®Ze1=c;PA606@g;bJg2/DdA ,@$s[۷pIYuR_xagOVP4o?=0%rq%#n*jŚO&z{~$`eWRY[ +oj:K`[)k=]"Ba]c8{}mҤO@%r2˥ux?ƯA3VdH\y[q8f߯7Vp(d%}PL[MJwbB(-`.3)b~Z KW]Wى6k^:~VTIR<\+CQ"7B"X mw~w˸HzFOS`TTFG[Lq/kX 9yp N*ȅiKR/Pl]c +aI)?h#%~x58VG+0PE Iȭ +{)yVB[Nߑ \ѝs5+'ak^Av3pozEOCs[]=F13^D*it"dҥvjNT8.[9[pI|*[54Bh3&$ۡM]kRc`h*6)򭐾8tm&5uE7JW_FbP vT ٽ-<(~\?PЗc{ʁGT-22kit N>qZ.O57 )b^K063EّOm%O4m\6pG䷯CA %g`,@WM2-s.t2  UV#VC(ZYg,p)ˀ}b?M\ ~q*Hl,s-Gۋr<&4쬇`m\ v xH&G QH*L͒^.X=mKIaq)yy,9=&NJ<>&:EƞtyknhIQ0f WVRF$tWT'Z60L'Q: VHN{/),[D/7ߚLdfCͯ_e8z8zVU2@@1Shi>%[ۀjpfFj fZp vlC;hsl3Ʉ.G<V S7n`Og4.1f,R.ReU5ѵ~3.jE10 ķ@ĝZ5wg47&T4ٳ6XHҩN|w_~Hx/)% n~RZ}< (~^6%Bt̯xu&wlB&b&e%*0`k(j3Zge 627f\&Τ?1Hn@޲MK<<YidhnQiꪣDn2VՁ&W̛BߖD

xD̦VRAd`Vo]|T1UN8=:: \Zjљm|~쑃zfY5QB)Aݴ:n/_V8_b2;.f>uuU)IHm[S IY"^gzHfh=h-cRw#+NXB,ngQn˻9?>}!aBu?Z}`z8PA0[)OLC4(6aBA|o=&{HŚu ً3ҚHhA\7t$O.pP ?Ms } 6960\bR۠vзfVD'a޵l>J鶝wbO;/ %zN` C/hPLYT$ ml *22}O5a*p2TOoƕE\o)|8\ou%ZU+꟫ICi[M50F(~k{9ߡ#wxk<]h1WN@M&k2ID|鎉Jd"pǸ 59Ag޳4<|pPi~[mn:28.Q2) 2_RJ(\1>,WBK/*S"9ЊRLՊTw +7T͝HD{1.MB:0uM7Ϥ^]XW=MAyO#ASBș)l!#k`"\]ώ4J'叇wo^:6_d(>3͵$QSڀS x"_>sI$oMS%8JTfauӆ8ˆAͰ<#Q3?-! KF>va9lTQ(cG$`][crvxMSCs4g,` z(GjmLlSsto).Bg",Ogf}v WL +~Jr>@'fa:^4aq>κ6.KTIcS4˟Ĵe%bi/e[8RHN 1rQdƴ' Abeaj',$d|2 )_ocpmd_mU]/,w 'wSP<CULUc c}V:O.[N,;9b!6r5ͼ*CwL^:$})-Tb^'gYO& v(5>9xAЭYTMpdTU*LJ)8mUyFgdR?:q?Ƨ"Up*vv7Dl)rr qWA^2 +W|"6>j<ާGJ2ϕٗz"{1$g&Ogf&Mx +oGx6-=4y±2Ga"@RR8:ͱ/k +_HvwtV9Ze>n.#vrސJ'Ҡ O1U#Px>oDɊIԞ /ut#9I7y33#/tɜ$uكu9VPAX G`>>q6m9"S8W #e%R#oٶUHii4op%,0,A+mUS_y(hV.qc=$at4ݾIU=K6OeSM rn`k^ C7S=zmc^e1򙜈B<'U`\܋So:l B6d?;%̻Šz W;~K``UU:62닾 9Sh'LDK [XƣyHV.[%oH;b鉳cK9/<&N'8b[g9?ijAg >;+"3 ?+R`sdQwQ:HG&ttq88\5AmH,vvd\b A2Ѿ 얣b )P}I$҃U1qFɉh&Vd ^ _d|n0:dke=x8ɠK +bknbzO'^Qq7Y߰p8ofUqmaQwDӔx7?ras#§m,xmNcz)ụ tIf> iK3iزS{+fv|phbt K&lot|I*4[Ʋi"]pF-] `AIRvN7,-P3T[5TB` z!EvηJ}Yi=xCN?8ݱG ˜ʌB۠N>ŪUpn6"#34"G’ӿG>eZ{0KQDBxVx} $j#^J|#l@Vv澜H "ZfA'tEWfL|4aN=b4g_ęéXR V +HL5G,T*GUM>2T҈pU!xw"ޣD\r ^Nd\MkQ$wT +%6;l[|}VmW<׉`H*w]u ;Ǒ~ T:/GP''t@iQF2(L"R`Fh9Wp"|~+FDQI_ D0ո ˊ فr˼oL}U]F|qڒw" e1 o bwiVʨ\YK#JcKKC uJ[r>W*z:ى׸VP/?gq.Xퟎ;AhYIimpˁ7S)‹%Yڡy^ilȉk0#_4 MX'p>lCR2B=5zTV +nڗR9Tje3iBɯx; K쥗7^gicRg`}}2-eb]JR&˧mǷV0k4 j f\ϓNd.[nO9̂uc+ףIkEדZ!Tw߰=m1ПtnBWtLYq/Ä 7yI?fKPpS[b'K +Uz\d' 1\1$KnmD-= Dm=|EG&\}'iRm|N*$-8s,>b o*badλL}'׵M& [*37zl5CEsd&h/m1d4ckk8(#~V3N~vyD+.͛ovSx #+[W{} +/Bv&"#];7,"pglD5[ׁښLLMQ߽]p8hBQ%;"`iL+ɶx,$byDK袴"|R,)vۙ'=BNɰ+4$i1xKڗq OyGp"`&q>ꉰ&T +g/דDY88YGYPl/Y/(Gӣ[byol:ågvlIo]$qO c,B}N4B= S);jHrui'!(=, S]K ?c*UEu45u@AB*֌|-2jIi~HD} +HH;ҔX OSA:Fm;T<5_Jxy0,oﻟ*[!j]-?{ztm!߄-_1|?b8>_@Jj=*x TE?'-Rlr#erP`sq -"?R%,AKLF'V@`eFS9a~NWtM%W1Ruum OOfrЋy@zf?-LE:5B~( +<Ϣ,ufHF`ƅDC a.sUSc^_sL ;H`”z(Og9[.w/bmrͰvzMm"aӊ#.\{9T:e8m&K4\H!FQtPTI a.ԵLau뤱 `7UТLMw?%3LQ$j/ؿ\EyaRCh 0.Ė/x3:$8ގ4Q1FR]9yJ8p=CBY|^daL76*3.'@9o0\8/۔4%NYD7a!8}ʎ9™+abh!mlDސ[T5J-{DتQtW,}Th2{&;[4ijBPryDYf.W7D]yaZ 7v^Q&xPcH'Rhw*Υ&pX/mX |ҧS;;bà$I8_x *٢t +Ƽ)_o;Kx'4/RIzuoOZfI!뜶DQm[m۶m۶Ym۶m۶ͻO[ -#[ /zl' + ~@9kƔNm&cZdtpMRzƊD%{8<Y]?|PJbK߄[Nk?5Qo6@ȳK~;xm'ڑ׶31)9T@&zhǸSd WFi(LҌ’(8&1[-ux7t?!]{=L=򹣊- Ϥ +Q}U?Sy fLXޫdA,K:n&-G XNNđ͘0|FT{r׵H!c,sA/IUw׺nd[ajf wER$3p;JK~V큿| L +< Nefڢ+/s$mIݯd|5nṁ3B7In؀+9jw|.N,i D`?õD<|@q̔ӗk·뾟 >鱏S$ +![̴Hff7lEjd8eB?F#,,50p?1'-;Io'Y\0M +9NuNQar TfU)"JaОoɹk_\BAol*t1BAƂ)r$!1p=Z4,GD'Q@&Ej`Wh vp!ioxa"Ce0t>Q\ +Qe$tdSJN:0C l/=";JEfY3Vb;M&yȉ&׃v]p&'% p M8Sp21jVdh{d.]kKaoMړqAh![3f,Ή=OִPdni@Hr$|LhLhm{U3^So|! ; +j90:jWKƾ,Hd`w,p+TL;oыu23i] DwK5 wpR,bm =DE8dR Of@Y* F)s+{(V9xj|AU`dꣻī JƢiq )|+k, è Vjd[I2iWGFD{;H -^8!| Snقt_wmUEwAo1}هxw6-EsFU%멩yTߪ +[ۀhVN&WE{ .}Z:;&J Rm4vjkM 99E=ZfF~dYqoH-&J?|}61 hHxdY7nquj: W_ӖFYqqep-E:hƈU{EjfIefM)[h9 =oU iw#eS 艀Ft$ՔoS޽RL8oX\_@\ 1ۉTbr&pc<|NPn0¹3fAGQ*'cC5$ +aE^-~D(GQ|?P7zC>+? SSUJ32u8QNנq:_0J B[2ÃnP˩`aJ8{NBF ̍j6|/;g9e9$+) k1Laz總WTz?JFa"nf꜡x>ddHQC߂ުTC}h]IMΔ/n)ˁjyNE=UL\,nhi_| piMVkv~2Eog-©{c!WS8`T &ʢtCY^ޭ-y[7ÚtS8la\k;'|]&[r/* 9Ailb9g>0[Ӗ=^ڵ!_pEA4rp|IB^_wڍ"[Vwh\o36M. `_eC똭M>gL`6o}OMd0Bz3TO)o8ݬ\PJruZ6Hi+u㏮cyf;a ⌋#/.~:+oҤJ( ݎ'P`[J?f*/5dCyΚ02NC98!״t0ұ-X{x Nj`W<[(ķGĦtׂrcZ>5EMY# +k|^2>,B +{[f>SmDD:偌_ΦKd[ov"!zW*,g;PuRʂ,ȓ!Ǐ5)r&ULc`l|+'~߳H8zz]-dp >N.pŋ`Pie"INl` 7bVYlcl%gnDtRW<>GbE.H'ʦ*[9C^$ˏK?TU}GG2$Z¸>ٻ95o[=y$F +!ѱT;aN ^S"vXA Vo>+zW$w|L;&yoʎ=^8odoju}pݲk+/+3~+ F9H8;ԜaRO!Z- `h > lCݴ,ޫD<_|?%}6"H,dpB=d.>;q. L- 3Ʌ$}j?n)88;y2dojE4ˤq{ +zL1*y؛<~d*=G^͓nKVeR#4[QQsnQP En ߔ(vD*kc8ɏf#]H +(ђ dwIcbMP`#?"xG@9{4P\6,I^ka,Sm<17٥rVOl^]5ռ/-nwER=J֣9kx;*S ^YjSK{&MȐ|  8yf/|Pm,VE;! +N.uN=#dɜϤz"!Q +k ?H1U )1Z P'JpPE #"+0 +Re A}EK{6sfZZfcCB  2ahFvug.Uz_H >ھL➰piZi#L"]QE(t6ݐV)]2"9+ZսiV~.;9ՊFD+S=<5)Jy6KG7ZO>+SP]!0ŀE(ׄhd_;ʴOGǜCv]+Wk@[fCfD4" X|F]VŸꏇ^IcMB{p*I1]Rp~"37W=ҝnb=F+CD]gI偾ť'ZIݺ2?=>jDwMz18"LK:MgozBL=RB=7D`.[HRbIpj#;rjgVSRy`t .Cn ϽDI#xLWΤo}9_Q{EuۏCĢnxos 1 xrV]э~- tRMOB^f%Qaഓ;D_8+lOt_W@.uZ-& )d,Q({8㴑CV292ٮ{gBhx R[*`,I#zto,sw +`LSvyXa&KpK3 +hHH3LԢyr80SH$0'ɕuuPkZPQD2Z\ 'Nm3O]Rzv>@.Ew9GPD鏸n7* +p aVc#WV3|(<mW7@dSN7$!Re*L" +UICV&_*%T+>S< EH#l:OU?aD6ZaT>&1ard +X1-a* !AѼ"z@B@\%szvcb l%/__~L@wR3o >y.O4u!hǃ Cܩ˦\BU;n[]?E(JfՃe_fbT_}I'LT[r/#u]:;:u&?paЦ@ϒE +ܽ " k|VI=>a̼yxv crЙ9xsz;u 1_(TE8̸.`RmN)y!1`6N'n~K+H L0I.K΂+Vׄ;<%N#U8jO1Vb.8Qn4TdFeе/p4t!Xbv!Օ_PPm P{xmى8$]#p@ +ذtaKWxOo.5}k1KBsS)c4͈{Ty7o}]+I)3i)Œ+>iG-~Îz5RH"c#mj0po8(L͞.jN]KI(K؞LTWfJ  +OJ?Ml8ʌf*,NѶTH$ PtsT&A`;NkE}pu 7RÓ"TMؑk/߅]A,)]( $'DSbu2^O t%n0z13ۥ>֡5mzSFtGKul$=w?ō4.g tFY+# y@~#6Ļ!"`VB&E?ޚhSm?dq]#Bg9 N*b swmR㓃^{KsA"L]ixoچ:q֚4cmp +rRn?-߄l6a ,oeMiA@? |QRdq`HRK܎cM\yO٪%y/yw"Yjg##I8ʃua. zi*H]2<mlhjXZl1! դrx(ln[LtSʶ!z,!&A$<3qk4@j?Oлq+F !tC\:8;E+w/Řߙ?d^äȦf0&)]|Jkg9\Ǵlq:p&bOQ.mR켃&C&ˇƞi !i@ N +Ç諙f-YM6,"jG^/RdXD`eȢ%}dy1B6 ;aR=>!6(Zw-cO#pM@>̍cCHe$@r]$嗛#(-'*]?w)0l>dk 1dcm; s~8&+(Q!y܉2v8:㬶+zO'ieTdyW=qbTy[֮٠ ρ\tæ"z- +3o ~.#2~Ӆd{1/4ON(7 +<$|msi0Q䂼uqCl1.zq5Kk~~D'GCIKߎqieW |Kl|S;pxFtw:Uq MbKAЪ0gP,֛{ܪ'* Ljݑ@w-#;'i8S.ѭ{j/]̷% +ڟI LSؑP%Ѩp|w7|Z|Xfźaiۙ5-C +\ϫ{!D2zw"N7{K#3(<~:Dʡ:7"A<=eO1pCʞ+)f[ dN&xa7[#+66.Jm1?|Aۗe߬(Oyt"Uvh#pS;&EI{kw%gqUux[%.Ԇ9}Zxq`tn'26*J;[Z0^&bX#j{-QVJ `xZ$HI +$){#7߉="e^UH˿b&@3&NXʼ|{ hZCY㡧أi2fz܄v0΂"NxH8PtRݢ?Vt4q#| * Ö}ؓOh@NHcw +&/Vi1%ED>ICBvQcјK`>Yʈ_FR 28E:[~mMUTz^7W 0B<0Dw^A"C=S׫9Ƹhp;Rgčdwԇi9|`(}j1WNJpm=Q6.3iܓo@Eś9MBi)D'h sa`6᜸fEmY:駢׭DQjSh|?lQ.P$v'_8ߦ^̡\ +A?|<h0k[ofרuZk'xiJpɹsa,^ +݄4N-Sihn4Tl7;R>I&eUF2PtfzAsmLܢOY}P"sV\A +ә/–`nj;v@rkĶq')yKNOr`4l.]]}.L>ӱKfSW_&d:t7QE]JIVfQ̙F2CK!>I]7 δO'XngKnhsf٧M7\L3!c%,y_ 6)K +K{O(=NGmhe̺wcR$njHԨ ǟP`g_# :E oȍD8+9Ѹh c5IBSIE?XW{7 +:޶m(63X GA@ FљE<S0Qt{GKkM(E-Ųwmcdbk,`r"k'SW#1iMV&Y[NeD癝prkb63hjPkUm_`R4j46zTPRA[&xI +}OlX < \ha&U1 $Uj"_`K͗[ev3[yZv~)R@- -ǷkA';ڻuF|U!A>JZVg.y_%</Y(CPa$%36fX "mCSOxjrfe03 |&||+2j) )~4jujG!hq57.ŝ([W%Io1&fyW{HFNnFruZ,EgG9̫&< {EzlĞ(tϒu" q5j|ٿ U^btRZ<1$n&U;9 T2㔇ߔFL?T,@ +r"*<$+)oV*gV0zȲIcϓwh +ru-z0> 5zϼ>~ 1 +`kmu#?s% YR wø^<ÔL8ga,mLy%C[ռ^|&u u)|C$UD mY+"N fwA~Q,x*aEs o2QHs# L1( Y >d>_P!pҀ: Ȑ\GxVQ#?7Q?iɖ a/!?_q;ң KUN\^v8)e@e@4xw0Wz]KU榬D532J,(p=z9dI`Usx;#FIKqEDhǟ7E$=v[B^Jf\f$q̼X'rl#l[ɨ('U>x,ܼ&"k8nXjDw ?FfǠA#~o*m^ MdaT֐I _$=o_Z8 p01m!5)a>=P {g +7/A +F5$жWQD9H~^yY(>8@7Fm+ؕ@eC3~,%H(cAA2!457j@fj-RjQhr"ЍtpV027™.fPċ[;XL!ǤEQJrɟ3E{}^mnVxt26Q؃Y;R9LPUL-ZF-Va%e^@Xfq07@):UWA_B >3ŎaC[?x#t&{MKѺnAn-86֪x.7B(qt#>|fvѨ_惬AU6$mS!Hb1iXE酖Avu N/v4izy)ygj-#RxI3T^eo>^,{pg]ݺ4]흑zːSFh_FXf#xsww{dǚ@[aWk $oː %\OL1]j +x^pI@F|cVWJxeϲa>Q'qcKT:}!qH2 C Gw*XPx`[wo 5dgR=S$@=@83-BrPA M_ڀD9fـSFjcY$sm݀C +M} +.`x]^"#\=\(),TaY9?~څaax{#)?42úSwAY&8Wv2Mr<]ZD[iH֦w(gqy-ysL ]Z+("B"ɳ׾$7snh7RO:8%0|m6$X `KA6dSO3RM,QL!-ܾu/yz9Y&[R;zq\#ףlqp7k"OVWT_әVmN!RQ\S*{5I6$C{tеBѓ!jEh|?R@@%kل̻FY+4?fq=fjy52]XD ̸fmy>weV^ǃQi_Ez*g@uP#'܇XVzߊOm=KT'Mc3 E>w M(2>^vU񊡮ޠVY@l˲\0Z`u5*2PX:wVfi`R=񋊩Ώ_Ε "ZxSm2^pZ@''U+!rax%(kjhӱ>DA^fּ,&k0pRő5FI8A>J^r49ܓ]$V8s,Z'`tZ}?\*3nMxbelT-]it+= +vǠj,*x5Qj3N~ {.wRe{[?}HG~5r#u,,Ң ~ۀ2h|x*dѳc?<SKLٖ" >, q~$& Pf lby4$ +L`5uUsnƪ3_0(ȼBV%Ԅ1‰&V5VM,qQ^JRA#ʹsTX_hf]S@~Dι;hCܒL-3S5/⮹_њQf^o|拆 kְޖW7,QqV!_?pv &*kw )$4Y37/9۞3M|Ko &Lw"4[=d@IMD 6( +irsL>1_üC~Ƒ#X./ FX~f¼*5&|5De0YŢc3B$ETOG\LJ<Ǖ=!'TC/Oj BKc$mg3^jtAxAb54;gJ}yݕq]M Íop0k+ ;!U>1ݔ{6DG?bȹK[ǔ|MF$Bf+@IT&" OQXLmt(1uħ,MQGMKdFWf_J!𞡸 ]CWV+CNH:a4@*U'OATh70WgE\H2HU`3>k\ Kj~/g$Mz(\4֓D|8+;l/i5.(,7ifՑ7r@v&^}@!x,}b*:"K~8¸7XR{>1gnzdoEJ+LJ݀Dj@L#vg*:ޡDhɪ ގb'%+~p.2g$'NhF쇤4O UAOF4O, K~[I :%prcY~_<rPaa\ItwnQcj 627×F\xL~oB*7}1)k}¦7 +v0 B rR#Ld92w7~gܗ:[iGZ!odTm09[$2LI-l iwA ?hhC'di#jựx{cen~PUd{ذ~\Iw] 7ԕٛy5  @d7f/Ek.r@{;1~bn p=9o3Uc#c3Ue H~mb?I]Mk>:C/{Sk5geē +(=і9f'{Dt? \p><}}H)1LEDGQO`>d65ezn8ܧӃĘ|4LO2l5y`?Ƀm 8u!xҚh%tKGLhi: +e +_ڮ<h!,!|?{_ 0 ` +[F坸VrEX*KysȦ&^.:HnQPCh`*YogҬg(wf&+Ahbͅ_l("C'Κ*։z[4XivFGz}0Sqx_"m< W_ddLY$y!l[Ғꇲ\xw1juVgU3Ξ 5MԵcFs ,H3Bڜ̏jyprcGȜ7 .ˍ`k5؜}B7.ADxd@ !~WBbc%߿9<*y^,}M +HbA S:ܨ^W WYF-HP\Pøs7oϏ88ft_T& +.l~:aSSԀS`3E_'GLr$qr, 3[_=&wJ2>FI<Bc΀HuTfNUZ!_!+HZTZ+[6zf^Z=Z ]8 Hq7z^Sj86z xZygIU]8}OHVBݏ0 k{vE(x)JI(& +a=]L0w/.KowX%M*i9z{L@;څ)]Ԗ@eh80P0'a*{m=#rX9㙪IVbv#o< ,gX2u܌Ql$BFj qv_]b~\;|s]6)NjG/ <~1,Rgc) +OHta>ŞSu斯5+QiLcϚ1 4fjtk{I`48U0yi O5Ys +0fQ j I\t0-|aPɰWNӵ" 'lq#pI\ i%@o)m\6fbV"0JX[O@iGm&iU,W ۓΈ"E1lާ}}6&nS=谏2g`<߾8{`-H;!CG2`0JZ<0wc,OfY&fS01G$E=ưC1͛AWU#BWs7Um9fj яlFnZQGy+%lR[&P8 f^~[ZĀ +Nh ;F v0ʍ|] +O\!t$fLS(" ]QMߐU-+{fE\X.CG;r"w N=ui/\.&Ѧ꘱!7:_N#owSH 7H˘\S3zu }w/+x Cـ0]T*m=߻欽0?d졷'VV-yqoeS Qs-(ΞA4@b4|JeP=Xdecz F-d62J Y0]I5З5l7${;NNP}, A' jkPHr[q][heߺd -n\T;4 '{/+a m'xP~c%]Z-^nsAEKYH̪Jz&מQϟc.5F=zhӧ%)z1ucD"&ѥvޝ/By B4hÉa;PsEF* sA&VQ ͵gICx1L;+ K֩$fa&ڿ׹),>6Y}Pڅ1Υvh=>;MTMFiLp5!" } Դ5HO-^ (XϫBo.}4?(Zfx<`N,@[gZZ}:(gOK$`vnBrS쟨!~H2g(:XՀZWTw B}3~CZGZ|%Q@N7q3;M8I=]K5HPFH s y%)sgׂ,iC+P%qo5iCD  +(].;@o1JB3.xeãTJ4zgۭkP y,u,֛1?#Ԟa XzUp~EuD3[Ac  *kG!v(6;f\"-UUkf+\šߵ[6n-U_@tz +Y)D\U]~k[?â=w+HK9{{Xk!@,=b4N痰̥qbrĭl)Jc/ +v6# V>Th&SInܮ`wDx bLSyJN2PϻGLәr0yuwEΛ($vSofwGc hW“VŔg `> i>hiZK(>A0nB&4=Cvϻ>aQXjx#-,&"y I2=nO"BVW$se)gT&4'KXe"eX!\MvQy:]XC\=hNeU f'/,SEo.I9Owc?& P>} ,X'V\m&]tYyWYZCsɵIbθ0--?_DHpHJs>ѓ_X!զʠLN uʛ7uiUS0#3#xED8\hr0z8MUn~'t!2# Dߓ/CRX~ ;_ڈ +|<] +/߇~W+[{ ET|ƌLCW+X'fdjcu{rG_ +,'#˻؂^V] :MP7cxM*LSUk,@PRH QM#-\:F0܋U߿aUc>pg<^Lת⼠09甩˥MeU3VB;UM)*!Ukdȣʺ4&>Lr^xgI1-x}{0A+pIN}!h`9~mC7ΉF\YXA}qW;?x t75QlEovꕾ'U+54|2@C"KJ`; vrB'iDfd*.9/KX`[d|u?jOh,Jo7YMbpW=!:h4;俋 ;9vc8DjhMމf(f5C4XcrHn @dX̏woYK![!TDˬP<"dI޹@r_[ vc+_.k֌&&)=OA@ȿO⬮S2,HB/#ހdHMEr,ڥpc'־kŊg䤪zMgv"ZbˌJ`@M-f-a)؁ͅ +%B _zcx876i=mw#87 e%k )k1E.&`+_ ٲ'|si,B+HL iqXGh +qD *c=2.Q3v<кEk͑ā-{̠:U*t03;Z6qqe{TnYSPt4Y%eLLZۉZ +Z攏enCJ(z@@5Z!s>-H$a,mNYI: -gnh}[.+E=^2. t" p0xrۿTK 2gA^H &Q4z9j@(Z=lR8T2BJW;RN%6" d H)i ,vMtsrq`5.6#_J-B)x2t9W߹K)£J#=+.zӎ啫^J3='l͙N}XX eWFM%;B. qjl1j7,; H7痻=F)Q>aevpo]%y:,SL=lPդ*MƎ4oPѸQ9 +`%kк^k8۪RZ3=HS2hI{|//i4>M܁m>hwh3o_4ІWQ0agVW 6i*K?:b}?ޠ~\Oǥp{uXFPE_wvc#& Y#rOGaX_H: ^NO{Ox㝴y6@O=0rԴ6Cq2A5Ph{cS'㰵O~ >u,1X Z.*Q=r*F7pc;>Gw (nCܻaBDdW{.$c/QbTjәz)9';xϝw'Qh‰}~̳ ?EHA& $Sa]2HMnN}I Jvr]-?k`h/^GG#8 룮ׅx,#l>ij*2(̹@ַcUxQڬdT+h=F|oltV~k_-㔚HUȵ'3I!TV'B9 GÏf>a_ d<884+O'%3WHJ^3oP-:q/pT z)`Y(5Vu*y4A4Vja糥 )`8M %RSQ$y^7-][j;y!YF-qY%=+o5iʣpkûmu?Vm4dPYIEa ocOH1+^[ͤTgE`h_Perv:>J78trNq:@I98$ӥnIT-M҆Я;l1`*E7h]IǾKӫaWf!r4lpfm,0>^}-1:<R/Ժ=<.Rٞ [ΓHaK$FrO.r@g+epYm bʭxꆒ/I Zc~}aN5Ml.kBvvHQM 'NE5tX6m(2fkDϠ%A;M&QIfǰqcl#: n܍nn>g"B8w07;Cnlt-j΢p/)ڈ`MLh97ۻբzVU4*׋/M"ꇡH^sK-ZeK0P"θH + 7,mHĺ#]D&]-1aDnJrY[ +UCOH o[SO`]ؤb͎:.b_&!&MNJ?qCMuquM3p rgֳi@҉ßo0#(Ϧ(:CEUjHH+W^am""[i^Nbރ?LVMhO7Ǭ#fL?#p/f2|D4"OVVtVQoj<{+ +Hs֋x ɒCm/vp`lv&[Lu%Q F=sK뗫E& zNAH)QêW+}u l%ȸ|ٔɱʎ%3V>ltk!:#y7;Spv"ƸCdm +BACm|i4anWt%ViwGQ9%" L*YsjpY{1x gG#R˪{b49XJ^8E&D-RaS`;vdKX +G`CZ=Rvqi!;m|("+cDnV %u"!}0#$$ToZ55]{JRyY85@DIRur/MJ+СIGNMע%ђgquʌ+HҕCV/k7$m!=uPmܡtYU'U*6[|ohYpf߭O0_JՔ m ^lUtɝjƌUFpS Ox`fٖ8X@E~E![`oQb7nZ ]``6PAqe"-gŒq6NղagRVWZp~l f,oArViIk ݵ /6yAwFi (͋Re 257"0rbumSwRhqD0C.)&H;e5AG?xyzHQ5]z[ZA〼l2>"E23LFW2 Swvc8iD|K++4a*ڻoQ"*&LųA,C;GGdGy2fG~5MGcEHRk+g@Ё+ԭ o^F8id4cV}% hNLāIG)8Ed +ٱj6OmS䂴MB7-O&)gLʭQXn3'1-~kI!," +f{ʔH&&[aDJ/%,lnz, 2ԡ&&@z6!A"ϋrׇR+$ 9zt(:f̱諔U Àtlj7,8I +;!$fVB (tC ~jlV5^qb"I&W ԯե5__ !̼ke,=z?|v@lKoM3m<yCh/c/_rZ fP"!L&bO[ \نQƣz.{YAiF9)fB7ώf{MknV;u}P|J /MJ0T%)0r.GlCYK@W'59AШz^м %LGD<5"p'`X*N<%qK||!RM& *}Z}u^U!c?@drY 3h =5``DھK'{!pEoF3aZBPvqDٗ lHHuעai̠˼گXeeֶR]`!S=W#- H_@fWOze{)9K H$0&,2~@^`~ʾ,4 u'CqӤVjq!z{ 1ok1y |hBH#VU]:O1BJ8y6r>m"ĿSϵ1,'кa^.PE 0QFv&y^ @f9@Cc#$ WZD +Dp'\VeIϕ'~JIfBPIJenUL {Q m}"X|Qp>y=rr%ppN0 +rI6qñQZ`:8o/u*tX|#Hn,y}h&M9(q2λd"$I-p̭!9Ҍ!ϵe3< AN\3LǾ/ *K6DJa Tr/Ds\aFm!&l@M%ryt o| 9)Òΐ OF=쓲aQr#`LL޵o"$dy!ϒVq#۩Xz%6(pLrΛ47f}Gw.ekKBHI>3z#$4B͹@[nlwl۶Ym۶Vl۶mN޽?a??aUGUƭD[A3Pps0ڱWi(t|}{fn@f0#Q<:!=$ߊ4mT&z2YzNtwT.UU~rix&aK1vSjPQ~Ó{ryc6Gc cV"¹}et!?X M-$q mϨw +[v K@ϯ:t $D+7]"l_ҖpR$Kk_SjK*c9 eq+3_}ۯ~a*}PmK]ʙЭAf10#򦇃c仗ߟFAEU~9 ;Vat-#f;NE9uQxuX,em8zZ!P{1d$( fbft)ǚy*|-**i8g2|#813M|D(:x 'έQp is/̽G&jR}1 +vZau6a3ح(˟0QG}w_%dbK7T`-ջxgSt )‚{leiGIKQz,|c =cWw^ ϼX@oR5Fzv{$NCBQ]"/zV$ddaptYW%Gȧed$̠ఙ*J)xB-cF(|ދYq+%jaB Mp}`uZHMYixw/ I Xp/URy?M@ph`w1Y + + lv!l翖 YeA]CH.aS")?$鷘gB;_Y@Q`B7oZ[6nDTbyLԬ=?zA]ӽ} A͔e0K1 jzytyhA0I,hI0?0O +oލp<527}F5-reUniNC#l}%ǩ`mh1$U^P_oNBg9ܡ;Jgˡ=iD$($[ _<PrZ78P%6!1 #XMp`8lPr 4ʱIu7BM2sp:w4Rku\6^}ިNe\b<W(A֐z1dJ\- [N[zCE vuu<@ꔥy)jgޜMʂEž?hk=B WI%,tPYT迄mƒr1a;/]:孬Na`5XRcTg9{mmb]:av7 yc: +%oO"ehnZ p|Y%oM +Bntz?SHݛs8**BB䞼PXi"Ȇ[~[:%| Kh7֡/W &F?80yʣg;KF& Xxp'my/M&g+B?h34& A' X;r^j,zqXڝ}Ri.@[R=RhEio<aaѷ|l/Vz wDk;&uO9V[.ݗ?9g_[o5E-6eFe2z<&"*ӱf[9+\trPRY P]]ļ!qys MX<>©/ߍPj9]`#zn6=& c74ϒ!YФEG.#"}yY39T-fd= +2. d'tl#N8( \A?BW_P*uМ yY\j5 +aJ%>8bۙYGDTuZi`5LXw|fd80^9|Ϡ .Y_ؚuȂjLTn*ZhJ7ȭ!wpg)(D: ;ごxkDDž|F`}*1E{dת`nc['0EH* ;.-f$QcϾqa "3&_s`nyiX'1f|qz5cvjTݕ6U7]ߦH̙uVpM?c55VWIkHTE5-kskm!2:}"yշͿ-ZIf!U|A/דW6rxah(8,r,ntXC_"5 `Tec8ZXزef1g@+cu"+g%խeP~SV!Z!f`d|d2{Yd! 'M;crwE_4R,1{Pvi$U3(8~/;GlaYsd3 ![T$k)cZy'Gi&Xp npԵΊ7Iq^pdv\3r/P+e~@ +/uKuJAYlC/~Ycl娄jG# V3,L}EM{@l݄?"Q XY25wv"5 k G9YoK*4{)Cc>)yxe0H@elN@†ZW|ft(@Wؼh'DqO8S+inMxUC̼=WbrÑl$L#:F[/TU?b2TLKd(b#3:U;rN&yݠbp=BǗZTHzYCMC^}qxrNSOoj+,ԡJ _%tCɫy+ nUl#m]w4/*; I'(y #2H +evi%Wim\3fx)H~n452j ?~i WmqX=-򍀻:z༬{#!{ 0(n=a " ?Eۤ!$mH |}<˅yé]>޳[GQI\^VUJq|65 C[<(Hi/e& xr2 Tՠ|׿qsGG_AoM >Tyy]hEL[?&hR ϔ3 1fvLD)^^y{-gpd5۰ &kzi;" +\ ߟTizט튎TߘZwTRISi4Aqz8gD_٦ Pd~9GۆwaՀz S0sI7%Z~5eaJ+׻ol9+KsfrCzgM#,dOIr*Vژe6[ٝ7Bhmv֢PT:Φ04Hp]R<',-E8PEE~ &`Qyv +ʩpDAS*HŒV, ubiWQ$CqD 4#ɽ uۗσ|8uu 6'4J~}P%gU'}V\E,{(Z꘺9ݒ(>[0gSlLF gq՛O:(vczYxޙn0qrT_)gMmܜRokٝ-fn n$o~VjفBMphWU;g)nUȏƭTdt;猵$$ӕ)'D8$^bER(qcR#e8:]nCZ)<"31`rWZ&cJ K(e֔Ė݈\wT +, +uJqڶ6[wt̓#l졦9J&n1W-g# qA"摺MG=z 'y&!lQ#A=pb\ yb@*W@m}0#'0³D߇Gv&ڃĻ^1 2mS!O2G|tv +>,'2>؎\r]IXu7GM=!s8?2`/ښN-.,)o7M:oåtpM|XŢ(bu27BŒ#Q"t, OUa=kKjBƀBNdRؗ JB?oMQ|w/'zg~S5{KnU;Sami*^Gsvv1?%j-HiNƱ< H "Pǯu\ƫYi ֮ʼn,Xyg~`%+2'"2]"?B hէA!i eYĜu7+(M$ +OBbϙMCk8EC"˲u9:m-猃^ը1L7հy0őІ17;0\(ߧ`j^<4-pzO$U5RυlFi82LjWI&V>Bb x춅0[=Iy s{L/9cJ7,[ǁFX֮?Cg9*cz{cvD "-5-s0|F'dU8evIg5,!/NgIc͜;Rمf %ftw0Efwl_[U [O+Eml㺘K-Ix`OpT5"ݡ־H1b59R]RuHVӱYj;E׆Oqb) ہV&1ǂ/*c;zbp|͸|DByYn[O”2\Fb{m^ۿL&5NRgLr0rFH$ +cٯn'ϬaՍ`|+)5wG8ib?gpUW1pHpMdb}t40bMzkUF׀ta4ϵETQ8 +.I{~kr?ϰ<%u⟫B[XlZfTsKThc^_%E-{dA\ zcwvL_K[pM{p:eGě.̨^+4푏.s¶.O+ȶfoJGT;R7l|zg;1I8bZP}5jc8#Vj={ 0'Gc~Sm/Ň^w2Gô}c-8qP/p;Rl`vx dͤg@YͶ٬mD`&MG]Q@Xq?~ov6E @eox=[ gF>ټ(YC +/V&"(_'L_ GFR"np (Ϡ'tv[("Hd *t@sP\='+]p(zs<ۙ{Z7Ƌj8f`^gs D1_QH8b1>,YBP=Fd +(uor,usϳoC&Fa'I=yU6`έW;a@ɴn~ +)7/zMP^n`,Y+fb*.A8,|y߇}1٧YMA3Ln#uӃ+[)e +e'؟Yi>>b㓍RI'*D`<3$,gQN9a뵈xdD7Lj#*SNtx W]€ur0)R/M8%@t?ߐ-TV@1DVmU׉$)xK68!/4"?ty<ܟߟV2 +:ԄNIpWؑLxuh{٭yD&n[sJGVk1R +!>Kt뗖Ǖ=L /0Ag`.CԪ+XmNhCb32i ҈6 Z\ ஃAϻTIAr߂HA g*t$hlq SlJj`kϠz T=)jlI*~Ts*^z(#'Hԟ<"YUv&biYEAy e{u)STJEwPit2Nʓ A_,c`w'6G*[JfV ܁;b,Up-5 +_K";羘|,%q ,=ˠ P( ߬&$!nYP}!06FR2bXbJ<ޛ +.lgy1S0oS'´.4"Z%%$^:hv]WBK3"V #/Dd5W ?:шWtpe7;YB_i XR8_Ȱ}5AX.Cn6!HAg8#iO?f>egph2"u9Ą?⯸MUJ `~ +Hcjwb -8\yvJ|@B0g48S[vA xdם*=pq)Z$+*(I.'Eb{ Pk72v`OQאMʎZ +f,cs;j=@1YAkNQſ[|ݮlB#$s&zΎ #  l.&v;rG+nUQ݌@@`ڨֆ!Y:g˶i[NTvp;~6C +/*쐥Xaœjo Nqi7Isck.1  +ꤠQ>V5d4lh@Q5C)O)VS߶x3?%%ry&~dkכW"4Sׇɯ^+^D.WFx7hr JuUW;:$qV+RWo]Py#; +q;Ub[$ ٗJVp݇8jT9!˖nE+%Jܘ +Qң~B)t/vAkm,2jA)g'vDyQeZlH ĽMP5EQdU̥U}13=\ZiU̢izr0.Uc0?2YLlV:kC[V(u#h +ئ\wuPȔJPsსcjY0p L|dx@}J<Iti=5ӏr@S +\] <Q#4B +ZP 1l: v51E6Lky6Guf-v~-؊EhhM2,",MB,eTiMbQ:iȋm8hS#,wa#%c#)Y{=Lm1P=X̔Mniۈ8 Z8#\GD֙:/lYЌQӖ+ܓД2CWm=(`L4AY66?9Hl5Q$z̦ITlKځJz0 +Q&ORʓr">gM`Ko$cf>ϕ(@zꎈjZ@TR:~?d,@Z,1adBHc,wI+њv!;80DsI:SeZ-xA>KL.(.n (`ꓖ ^ߗnKf߸" yDwR6EѢ"D c8xfPm f5y씀▹FղRw.g5''7Ӑ8+n 5.o*/!V#  !7x}$ go"x8p/Z>y؇7H\pBw'*7eO!Ya>|CVcV)vD˨{ezөIsXi[Mqa?c=yv6>0 xt/ͤ{Kf٘~:%n^| f>QRBOQA03zg^V:Alƈ_1İO9T-l}3t Su*h*qs !_ڎjD"C^eJptʠV,^`h\>a>CP?$LkN2^;(ɴ]t02~2z6Swˮ-~JFx- دr{F}@+2mqy08d&טxK@1SdNbG [=(2{Ep$ibDia.Nɢ.h8䙔iږ "}y\-rQS/2_,jT Dh@:˗tPDAcm`W)`LXzK4,fkMkr&ȵk7Q*x>TqyښH4`"g] >7Hް}VoMVq)#EA@W'nRB(cK>|Fƾzy*iO5> eBn#Π1T쌭tH%+L+ʵWK +N~_NCa]16Ⱦ?o Z^&⃴x)}OFL'wi@%G |iJ=9Wqr5!azPȗ2䟗RٰF$k?/g?Vq_Vf c0NF+>^" ;+cRk0~<&0*6+?,XqB{0ІO{1i}jMZ + y F+" +- -t#ǁ2ɆgRvq=lu{ͨ)_/'Q!zevB-gR`I3`3W%g(Zl#T68ZJ6zέn2㇯jmT]='!H'w4s~G3z.a dX+9Np}Xn\ Zu%`fBTm?F_8t!k wh|T;w$qTdvI =ꀸHM}|dѢjLbba2V|S B$&e9嗄R+ZĦ̰ + +~oDԝSeOq$xL9G6PIAON/S]lQtٶ*&'A-.mhwVI.WuCZ.Vһc{6S`yF3ܛo1"3+B7Lôi8I р\+%8@./%AV]4r9Khя#!e;}?@:@IO{,/8 7=@Mpn_B8m6!ZDof5NK 87L +(̧~4% ;$wY"pg{م`~12C wKy2)g@#Ґf_>} +o^JoN[b Ҍr:P(|)A#e;{9ܦnuW`2N0U!CÔ2(AsM3x/_ +ET`]ܡH|[a@y+;3Z17$;݅RSy-xҡ<2YƔMT^rdbЕt(nh Q1cy%siWQ+ PӍZGx9vQ?uĀmYօ큦C ׉ *vo+?67Wr}-s*!%hdLP:Ϧ\6,3{v!Q9˜5v+~ ?P ,3z7~}|*[ +1BšOKu, SfY4N-6 ĩ)kh72C)_7hdv$B62vҬWkG +MLBy}W?F5emhJ/l t¿eh|Xޚǧ`ŧZS6Vݴm}fN$Xoވ*̣2 _mb 5Ð%E==)"Zᔢ3M S!PD/)NEd6TP^A) OA)=puӱnR5ϐV,MgI{"n[=`tw0ZYrwTf +$WEUH.b_T'x>NE1X ]m[_5/Yq|f%a1MP6ā., yuj8' x]'Tl3D%0^sGe~WDxzh-bn\b9K.ꅀ#}LޮqCKƺ=F˜܊ŠX$+腰0 AOHr u ײ0H\;bgkmī +$2faEo'4DΟpd fi/ID8nO#6ȼyvڃ?r5kX7W +9 d5&_ b1ؾ*dFlͰ듘&NN,H2\Djv ZO`V73.٣ FQCWMkad7mHp? D(G놊yU׋:x#R ^T}A6HwsbBY-şj4&V2+fUK ~/[PS!(?lDQ +؏N6r#<>GiGE5EtO O=H*9hy$T=Fp+|)`NKB4`[Ap? \JC %ekaƛҌ:=]b6}hݜ-=UZc_Ꙇ7 +f(9^;(dE'g/HU ]Bϰ,rlk/!;ژgL%;yԜe<'Ȯtdv[fsUf? {iw Y>RwS':O !s7NX 9h M\˩cDc2G!wx8ŏq}ST.4X1"D~d4`[yY D,!w5+Q0}q2AU/g ֝=A\꼞#;kcm;rO-}Vyh:YdPdy3Ƌɒ_@'EP eX9 F\=ذ lXJ,͖2?` (vGf:j궱5Γs2f\˹6t+aYh}N/gge,G\B{r>&C|4 "x\JQ)wSa]x"[]e$ t)\X>P`?"-?Mtfٲ/u:D_VG?.ۿ۷F_K6{T9,_v;'9҅]QQ%aVbY&,ƥ\-DzqfG+@UEDa75D.[#'0u; ̇]̋ *:LPŸ>_(j谽ȭE8jS2]MCw&uDW_ Ww`T1֣?Y"RN+qP(,2dw|$k{/ BL91Q,s=6^/<֚w|@4(~Wkj'FRa itWbmK`cGRW[E lA;@I5qcUAIqZEłػ*jc`}Sڧaj`Z&yvA +OD}IF` +%Rm&F +Gi"?EE1Y-vF x+fw̮EqQ1/mrwiYG%b "6pcZ[#49!rwu҅n[cڶMg>x|c6KnQ%Ǵ~QD ,]xq|*!nYFS{I`[ p{}ۜG)gwփsCr:yR;MV0Ux6n0>KR4pHCջcP=~G@BrƘ'3!bX_ΙMqe<AH/XgL;^/8vԂ^W"A=V^0 gj:iT&ϵD +x@MY(B + eg!%PJVˆ8' ˓h Q4/=es;WF2|=* $%k5DLxnb +fꀂ<<_4- KOӯB^ª򷆼nmFN\JV"8`$ {/}L`]k4O 뜓Eie7t1ٌyۊry;ƥH,*2暵#Nn r;؞jW E󔜘L+PMz39(}6$jVMSnWHuQ-y3 "Ζjwr1y5@2;^> vN57:ԞJi:PDhac-^%KLQ1ءxR +fO)"z5tܑv;KRd=f$Igg*Z4"J?-X~Z9oS#4ݳȋtۣ4jThW_ *7&x㊋CSQ5 KXj I:`=ӷvq,pC"Tm 腙#R%b+aaʖ!FmA .ՓDzk?6 +8udr<z*|YLn|̬쨤jVۙ9VOs()̸U2ufkB]KqR0'8 +o՞#mkӛy]/CZPE(YuaOw7P_gUX8x'˄YEv 'Hn]8JD6Fv!BlOfmxi?9?XkR?T$0u]i!9nѱ#$m]I䋤'Smŕoeq.\?w8"YM€CMʛQqbWr. +NΜEam!Hj?&O$~kWS,m1LAдWtgj0W qFچ/!f32\ gؘv~#{N9\c42͌A|JG%5DG%'|uqPh2pu!J XOHB2bB;{d5o)c/ œ槔#|ӝ(U{j{Z܋"Hɡ C7n; /]xTsracv&Q{=)Zb +Sp] 8RR|p6Z0]c5s);e=|lմ Eq$Z9ZQ1ǡwx*ĚmA_<ȢŘ(F0^Z(+Ajdב,m}\HEՇ rTN.dȒ~13M:iNl*V(jh"ԍ R+gܙ^+cݣ/!\A +J%v}UP-v2S_ +˫; e +҆ōW 3S U݋qU;}ξ&kZTR,& +]}t f)s~8p%n`Jwve(_H/doC?qE~`tID2{NV΁_ǘb&"Jڛ!Xp:6ŒBd:Uc}؟=S-3tYة:ީ/ ;ҫ;'}$᩿!\;-6b &{$̫KB`Ϲb9 !N&H/>{DA 1;~M L*_κXXG]k%fs%&0F_ON87@ƺ\ ߈nl~浑ygJՍ{׺&g&%L`OYkCMz +DV76m +endstream +endobj +306 0 obj +<< /Filter /FlateDecode /Length1 1122 /Length2 9795 /Length3 0 /Length 10544 >> +stream +x}eT\m4Mp; :84n=.]k{yf][\U{שuU5MmA26fv6†LM XA`[IIvBj49 .+ `W@ lbY4,v djI[;7D_LYUEdj,m +0vO(';;`e5{XXl@VwJۘJZ[l lS ` 2 %fLAf<+!`W ۛ2_?7rre5IQ\?7'!a +`0sq8ނx* 땳1ˮݿ,;[<RM@Mdh r)kR:%)iϻAV`qsC`G+T 1AĵlLAV`#u0i69:IlLǀ)my@2ۄ9+nV[[ x[係~[_?Qοl\ox@8 +do5x[ o[޲`}kA`'hd?<knvV)f +hR7qrxSc]Fm +yaD0ku@}8 Θ4Χdzf6GT~k [t7bdGMqj-O c'wZ'p 3kz>{jZ6AwSg;bݻʺ,Q@S2 uq,><mv3kL>8CwìJ&c'.> $~2n&k1Džh/ۣ5jS73B LT{&vCrWKgO3P{5,fDŽlco0D r̩Kݑ=G_-xˡIҏ-C#3>a?52.j)UDK:\n_E귯̂R3[&SB# `^`Dhwϔe +'YA[T-Mœµ-4>*Ls* +l2|=0l:6nEoDFD>?{^"Èt1H <ԥv-@M>ELfv%=q&-[=cQIz*KA˜\}MYg+2fh!#D*f ar +fro? ҧ@wgfR Fim_%( >1*vmLO v󉳟[f'2@3a2?Zƒ^'}A/ BOks>J6+QdیEׯzuWfz;QD53~VsʳX:ҍbIGvg-` +M{%_Y74P],Nn3.$l{z,߫byT!/(%>{2kCY + Jс2&SYj"@h/ݺ{z#SV*H($t$in W,,v0*PXc^T ՚Ks_6j]ɭ~,צXz3jz$*8lǚљmrk#I5+zh +IT e$(̳^Eb1:5Fg}UL);W m{8wnKY`諭roK-sb6?o%0wKO{n #njVO{!snHK@Svjdߣ1r'sreb;Ki"2tu{B]O41|S;0yQW{0f!{j=>D+n pi\x?nhY "I9Aq D|LW~3B2܅Ȑe">=>W%IRn`l~71J(!/;w\5cջ#iЋEG6ˣtMߒwDŽ +dPc̬3R`pIɅ`NiqxtJ_bɒK~keھcTSd.k*鵑 ky= :bkrM{Kz֌@IFYUIڊ'z5/`E$T WEf$WlS+͏E!{!~x_JSYf )Zzf~tyXlɩ9-0wPzÀX{~aB Dѧe`rk86>lkx-5r(Zr \1SZM5.]'J#>p_ +UR?xL8jɰLr:ٗn {?BR4˼4tOg72o'`$qYڧn~]:*l Q6xIiH u=+ʵk/iD\J\] YtAA/w3pJUf{rKfRu3z~!g2P"&4?ҙj8eֳN,S$cu>BTq4MOh;fmr1Xwٔ7?GCD'pMv O_hj@wjo9bZVYhe- :DR.hF'mlS_(}oqB^WܤacP56+XƌA$טܒQZY8j=Efo狙Iə8ܽ/v(fn(3a"e*^8E +b.\e3IZK1Ŀ5ћV2sP!Øemፊ^/SG ^l8㓟,DT Ĵv/qw :5Jh2Fik!5cCWMs`K0p٤ff~1!^P^Ye6*Hڊu `M$;+XG=m!wO ڵ1/U"J:R7J$* kɭ鏝˶l//\PSǂO/ ?5<L|jьד #DyZivBGeg킶$\q*h?dw`K6<"fΣ}ȸ.{XKbt鈲cݚn, +M*'۝u@_PuO4-A}t |]GG/KK[}>Qx nL6!>JOOY7>jw<%K,`J:qބ끒Bi@^{PBq[4wfr0{o#Tw_0]Ft*"jѹQHuDJ.7X rGw%ޠko㜡#R;L +`3j6©tBK]n7rsZeCrDWS=rWvA,oL+nMZHӿdPg2>h}Bt_$u9Oz@jbӽi? +WN]XI=lCڝH^NPE$(`%:}=9kw{pK8L6."^oܰT݁Mcz= „@jwXazZx2qBd*9iAԠagq\t&4BW" `' ~AibZ_!`JߊFX@z4Qy9Bb>؋pxb1)Dž'D+=oLR- V벏b@lt2L~V%ށPqH"wY-ź^Yvh=v̐&5b(/p]lq%Vv(ߗE'3,%a8PaNηO(noʧsC|LRmʒcb̞OVˉ\nȨF3Q)9/C QSPO0Bg}A}SFY! +r~yIfU +ut]u' +sU1 ?ZO +=fs{Gc7qpP[)ڗ:JEQkA]7"6_z;Q}[g(m* ?FP qxe^dy'`xie]ea/>P|$K6­mT)\P8C$ϖ>C'GhOq^Jbhg$KUIFz g aACK\A-0] !W'$FxgEƑDi^[Zrڄ.Of2pW~\ ,.ɺ_ pmx=Iˁt'_^ReqTs@g1\r6@0ʌ|HH mwA oQӪxPob~IaӠKH=xn{&L$aF;$հXN +!Nјx - +X7KRl~/> ٪X*A; bdBԿEG))E r܉Gd?_]-=Gw絧$Ozpd c#vVEfʾw+a/@~ v椒)6?!,h5FԕV\ ۘ` +x?Dgc-DѼ-E;^@P؍"hC^bB$|Qv|-(g\77Fn:H3sQ-ٶ[D5hKȟ +XUd֤b\\8%U}6M.(GW5{jd8E.cw"ZWh}ʐ\hD9.?,b (yٕh+q~Oi2{Nu\y5lKyApK [.Yye1|d}.$T1G6܂0ȑ9i`'s(6f̹olL5ErbC.E` x}/f=^-W@o msv$ +WCi0v8?>@|wg`⺓j}Wc2"?H4Z>y:÷V݃P.(:n2|/y=ڟn7:*q.OǨ 2/?O\4~wt#[';}p[6xfy/'e,ڕN|2"G N ҥ݇ X4x<6ʫ2:xWݩBWb5wQ &Cg7]ؼ vIGD2)%iQ ӈYpZbuY%C!(3zn>oqB*W TB6^a>4Zmѭ/CiM='1XU/޽J3+LƛI҅.8$,^̗]}IR}1<[`CuF,R.1m[9#yے%3ۅJ4֖/npZ7?lJ^3}T%$ő.j=Lп)c$Bɋ_X6g/~ci"|( XrjCmf)^vCg ^wDj/J u^C(O2r_n٘ʓiӂ8guāg2a Cwا~޷J=6u6}ץ>}~ *=OHcǀ52]NtU1⹛Lp#LIN֍?1Aan?h~ffphoH#$E> +KȖ}SCYnׁM*`Ox{n%1ujgͤg~9Q(L Libdla$ pz_5i@g=C𪈁U[¶&ZLېu;ſ&-$xr!-XD!ڮ\t4!O? vsу(؉ڔ&tr|hL8At󋴙"crɁ +1!BIOJ3IшELW{s "+n# ++'dF-v*5km(ѯȻX){Ł*=wj|/vBy`ou Y1t"ܗ +f`owpdE\{)ZPTEoՇUI7}grg!z[e`&3% N &z 6giφ0O8hmpԘg[^Xb%٫jimɋ !J"ewYmsI-ބR)|p4:r>]Yv^s7*2KV_R2/g6oT9G*n +Ju2Zyy&zF,_pzg1I +͢NQҞPw w⊙JwML<#k%]]DD_FړRnQhv4u7UY -¦ +]NJR@ÚPb"lѝ|UB2UJfXpM\@ji(32ó?]SF9KðH|DiIW?24&G!I] g(S"Ds*o Kp`KËo.}tV,чpJMcLe#0vlz|Vs"6h̥M>4GȒ?-i-AeNF li_DZ望xrb]$O}Zc-#g:}D1zhxߎ?g<,f/,hl^?+m=f\ +h \5 ,Qo;G_<P6.i-|qXfmZ1<PjoSn 'T&0fATڿϨ.Ƹ#qtQw~i>Q%0'{PV&)}B5-b<n#bΕhdCk4 +3gm+Y2*ʁBiWK;~oFͫf7A$]Wz -?;xEˌԫlp\YF(zsoƆE1AV0o"=*s. Ԓuѱ}x~;XA1>{ >cqpGDO -V: }tZ`5/-N|yJCQN @>wv[hJlez]FJ,!=8]u^!U*YkYgV>.Gi/+",ό4>#xQV!|$4W_kHxqKn:CKh>YA 7ixQ`O\1{]/ 0 fTߩsfJana`q`xre ߂QUj?@IhW9%gBҸH +AMΒR63-'V +o&z!ǚBkzy%@B,yy{4üLcPJ%YP3ޭ򌂣uGT0j>%Pm07P8o\ 7@/܁G/hX;e߸l2GBz +KM Tɜ=A1Ab[wUv:Y )'\P@Vdl0!sb!e&Ǹo< +endstream +endobj +307 0 obj +<< /Filter /FlateDecode /Length1 909 /Length2 60688 /Length3 0 /Length 61134 >> +stream +xtp'o6;؛mƆ۶퍱m۶mɻy9yj瞚!%RP41X;21pd̬e f@eI+#'`41W@!+$?8F@IZ/@{3k?{ۿr6f@_Q'W +ff :% -L'k#=@ho1u۸Y@=!' ТZ- +Nf֢9U!_׫om7No@S76ut墧Ltt@G-J8&f!? ?/ddivN?SH:-a[Yp3X hfbO+Ho-;%@oʿfe7?5 ;ѿFw2Fmǭoff 0ϚֲfM:ƿ:tJ6@53( + ٸ(sop0z{4E@ù*QNJi- y1?9\\`_mIE ߔu`GQ+98W(ipX>_P$z‰QTq`¨XG7:.kFKfMW!T}辧:jO|D=]C/ z?q  9m7!θy m*ٯ!3pF5WЎpx҂me<`_&K=`Umଝj>hWBhy.`4O;\Ho ՕZ#V̅t6:>X]K!1<ҍ)G7 㭇-62)'Bz}#8}ƚE +т:bŠpiGuwϣ=r5B{ Cd <`pSJ{thc'5e |$L@25w:rY\Q}:A}x{~I,UL25u-3/x%D'DY1yR Mk#"`͸0Wqlt=st1{ϓmLpaD4ސU-p$%JXJa 0QCmճ;"5B4f53s,#v&EՎ(HR噫 a=XKͫ{rmZyۋ:9N45jf9I5=Ņ',8FiGmgiՆJqDEy'\تmɻ&s u(RV4/C*Y{Xzj!9 _}dK!UMC&HHԗJ?yu'v[T[GVOɫBepD{f^`\dg2T>NxC'OA] `at ՐnF~H[e,[vn,US>Eƃ47IzR9D,мϸ780车 Txʯ>qNx R{ى1z>s.uIv.,Jd J8 /AMIUBގʹf MB i,G3]ʫ)q6W2[XȐy6@ZNɍ?Pٮ:n}#S jf$ɇ{YM:"׵;J߰?ŋz'y0\z2呻mX%|m %QȖ`Z"i`{';Ee5)vMFF,x!I bd@ nInB/g &Z(x0ڥ +"z1(J)[3ey|*7SŶ쒀%py ++5<(wA4{g#snfh ;Z`+!b*!3er0 ~soH)\xL 3|C + +\>%7Im=|hu%Z9]aW6kpƢ$IN\r͈K4<-]:b~vN$̃[+Q0^` ^>0#m,J3Y$ T5A8p|{ܽR\ 䟘3Y/ӬUomoԻ;@ 2/ozeq>W.L#SVDN{˽/~:AHzwXZLXw9;̱9# >GwF&A䫧e7?%[U-r֭y=$%.R^xJ(cςVzZs2\w**N.ԚB?:0vo2FI74PkkmpJ(Fc7\==s\PJ嫼ETU(^'o%әFcZH& e Kl4ztiiy$[:$a.\1_۱+*B-z00࡬0`oξPhQd n\m)D3|p1-~w6rޖoW"mwYM3$c:\]T"ɛ@ +;1~TX/@;l Kr!eײ' }q)ԥtGϙw: MZ$|BSb?2CϣK𐼖 ̉rk|R ,˓vmbQJ:]8W5\*eIb,G?mK3;4r§QO`R*BcZ;=L3-^%p}xf +z#e-7iKרӖڏ #b-Qs5R1P:chU +S~+_)M Bq r5M BVt +f!Qp%1T/;5%m"p3G+AbT$)o@6>$KZL5aL M(EDfZ%C7>]IHӃ҈(;ǰ4Z"]qX{3͌(D?Dzav̕j _7Op +Vg Z91e +,I"C䛔C#% LQO)vڰq'\6s =9?LJP1:G2d]UWy ctAoK }ifĮIwxI7xF JM( +eD{բ!X`*B&*|S`zko||~1w+}ʀSH(׸v/^&1\V:=՘wrf% Ȉru߅7HV zkjd "\펻͓> Q.a!ԑ1<IQBW+QT3/g4Hs׿sYzRдא*W",j4HX;O oC>drw~E6,_M bز38kF(,!flmQ}wz{dw>- />?u{$ B'-'\/"&I (.W*bӇEܮ?s??eN:[yv7*E/+ߕm+ (it˙_)voJGT0i3] 1E:Q: nLF%WF4'FUB>ZvZy6^ >i!<2N1kTP m4:Gl#ܶIj@.,j5Ws%w]䭋Mm_Fs6Gl.|SliȻ|}΂|Y3ׅhW7~ZEjޟ͹fۊ$~ȩy "[MߔKzȣZEOw;g;> fyohLwD>fn>+bVN4/;gy +be&9$Eج̏0fʃ'tң5ҫ6^s9{\hzOy=Tvެ<Ȉ$B4bz*^nMNЊ:W-1Fp o)N,NuY fHGt!/@w _._h"_z͕04sCO=W& ( R!/.x}%KKݶ^j+m dez:{'Qp**Gh?jA#X$ڱe)u14fygǞy&:LX,}-jܲa [b]QHb$iˊ+!h#*O_Y"0ÐuFbm7͞`*+ĆkIћ*BxB^a +j-QD/3a1ܠ)(R0?f{D֫甡}I #Xi +!oߣ39j @ޮWrny!!O6p\0>QOc :ϳ]$~Bk +_{toRIJC-<[C9 C_Sqѽۡ1Qʃoޚ_q84O9%`9V#y#'6B2;Ng$Q/S*9 +>mHzil_Y;f=0wȉ׈OINTu802ݐ_HϠ4Ҵ[-PrD>}^KP>Qs唥-[~z6Vb}޻ +rgNjT\"Oߪqſq:(N,gך!0V10 b=Lam;ǙӯM^0'i"m^x1qNK7/6lṼhTܒU|j$JMb'`0p$/ >rDS3`ڻc^0 y[Z]2I (~\ǤjJM!&~\q".)Lz +\}!$.ׯ˅ sˢS^ڀ➙o+P_ u\bqLgޕ8V6o (FMC38Wc=OQzM~EebRc_]1@}Ω +%jflQC_gTVMS]D6m:71R$RhڶvfNH\^jܦ{OZ/ UbJ8Q)nn${{XpݲdLFG{rIZ +KZt^58&kv4/y+ֺbכ 5f.Ri[@YRȔQK6U1P?rr=mwƥ _Km=Q鑄Z>u%2`ͽO[z+X#_9PiQR p{a<;2`"1m?\ b D.w'ɟlU4 Ƭ6;EMXǫ!ql7|/0kjBS>vrzdm&ٲ~z]ΤҵƣKY8M#1 @[.@-]Zf SKczx$8zhCoɑc6Vpb.Rʼ+"#َ4)YCH2{ÉYhvV~[%HiGHx cG:.`p6`w;y0vF*cSF]rNv-9.ZgI*7#E|-fMv2wRu''x &bZ}w^Gjjh8tۘcx%KbD3⿅Oz.]z1٭C69uUŰ=ݳ3 α|U;| ݳSV\ d:P cyt;WV J2 +I8~J<"ApK4ͬd:RmF՞gaz$)(N=ƎHR%@,>C1J +?qVpE y乏^'EI5wZ&.^ 9q,hϖ0%f?yHV +FƷ쏭-7oChp/,]"2٭ [tI6>g@ei` KBNp][dnܬy&=a%[telʺЊai>-:pfxޓNQO5.n{^u?INۥcS~x"[x?.$wSn,t&uba^b\qh3gN'H5<~kqV (4tfFRxF:ӐsvA} TTt|_Y)3  4n1~5%j67JF%Xo{hmeϟ~zㄣU?{ н~r\>߲d:O¢o0Mg]*.[ٔ Ea)q֥Y(Ph}{9p#oYWLNgX6y>u(%,/5cR@ќX -HlOT1x5quXCA;)"g,lӁ M^ syF&!+.4s8aZwW)Tx貴1e<"sO):&h{x^wku?wh+UNwBi,kdTڅB xa%A5{QY,|Fi[}vlJ?V6LX$g+q_mwjȶ=N2vj,3}2Ox~cCӼ90)"܍|Cx'#[0:wN +2PO;N7[kIDy%@@~Qb A4vFʩ #-(Ghej%rg K\~Cg4ezvXA&3g@өsw% (bxey +u1|-Z:E|Ea_dT6B ?\ +BQFNڮdCÇOO™Ft~fQFx(SmCG=#zm2'}mV3tx +L&Jré<5? {pyBN2B 乥qݻܺ`խ܅L^Oi7ȯ_*rz\ [ps˰FƟю`oha3 +3ViY[}TK~4SaTWQ_TgM!}4 ~!ȹQBKL3ulۑ"On~6yea@USo:Inq_j˕+W¶{8MzX3їvGB[eDZ#)@`g7(vǧg +)N;lR5% N!JYtmg> <496U:=~IY~c`2osI"w"q2N-"G{o8P>b9&;_՗Rhf+ ywV*;'hB#M==Zx|4C "%.X+#T2JnCXYƢIքE0LTڹӍ䛰3x +<9f G~ +Ԓ*!;7 +HĕFj'9'iv =(sQRh픠sro-7=UEVu4`,FWD9MRQSs^֝ZR9/NB ĄjB3́HZ/+ ? PWo'8C@#.^Qn6lY%o9u=" b~ϔi%~j<*!Y҇粦•^rH/XH7\wfQ&/@d5Y{?)^qyA(q BǑ[}BAv}5{IJD֪7*+!b Y}ŰrRʇ7UF_KlI7Ikf!`V2sوHDDʫLZ3hۄ BBbP]KO,Gf,0y"d눤.0'fv8cgiEד! EY$=RJ*-Dj(m>!dr\ #+y~IY*/P͇>2v BWe +I_g@{۲. ~q-31A1Mo+AFl m7dllُYdoUnU*~E sBu&C@N^ Ri;t$7B=>֍¡/(8ŒՕp's6QQRwW^ \ju,@{'SeuD8Tq,9?_<4g':hB2BlqE[SJOS5Y+uW=yM\ֳzspApQ/ +@|. % +m|x57Mv32(&U%rz"]Rm ״C)n㚁k,f\vR)?H3].b0s{9ݱC! /SgQj)<D$̳wr@g0ZjaCGn@3fJJ>bE<5w8r$v -,j+t*N< 9HyOjMǭ*8ruT,"4s$,pa1k) 8蟓7lѬ@29adr2$LJ֌> 39蚙45U/F߬4Ġa-uAgqHBƮn%cKUT8ݽ|c);|A' %me:6+=Y @ǧr2h\ +oWK07/h.%!;4[l$h1{FuB֠5K+^7ҩ"_J)\{k~{ 4XrJ)[ڪEo +Ls5w+4WlU# +))юo]xha8얼mCJL}{|VTLd؉5Gu69펇d5B/d>}u\ +.?x ĉ޹~Ȅ1F +->xJu &fDž0Z,؟fY?oUO/"`&T>ػc{WyH_Wfj5ܻHz7noQA]''r r&R?3$(!14Z9떠]]W쥤[{}(^/'NFI:\~Nh~z;&inht~@ќ(pVgM8Uf:X7A Yx(W?K&3 Bx 6_нߞ>e—zS-'NPݘV,!y+p_ y1]a.õ@kYZS|?9 y~_BΓG+>w:\+{&xJ"OK'7~+582957 hK >|jФ% +QbCNIdΡ|KkCs@zJujwm^X)yӆ^l0GOǴe eTx :z0W~E +&ZwE?|u_.~£`7ldRDEQIE wM3Qvie> ҘDkSPN&B8n +PmYM~i~Xo\@m58,Kɺ_Q;2ֿyRM:*UM[[s*|+SJ;͖Ѽf# m+2B ͻkπ*\WˉX"+XYz2RNE)rW(+A*h+wٻq$ +gFԑp + OB;wDQ$c姧?ƒ Jz!N0_mc'씿$(sceYpj1"y\cs)TEK8(,,OV7\rPP/_Aݲ~o}=@8]cc7!k+u7~sRby4>e:4ʊZٜ@;QpJH%z4J)MqJ1Y2/Dь"B!4iU:7Rؼ-njk2NQe_xN+Sg ؽ 朱2hɯ0g|&eԻU^LV[22;U0$䯐Yѻyg} +ي==x/G}l(*k9ijVʣm :v>Pj?vii'/``p*vQ<],'j~ֺIϗwbݨ$f0o``TRko$"^lǺ P|q6Z8ߑzZLi8iD(y:]x摾T`rCwxh@<phm6j `>n3`7T#s&*=.&M$je3;O(݉7bd&hBH pY>씑æO ,/,UPz2h5;8X)?XW耥'?DЖP1JFO1kړ ft*S}~^C7+[PJQnkz.A(ZuMX2T w >Đ{_ 'Up&Mdm]l+t ܧq{;jqXmjU!<MQPoi O{PҺpG5.^t[zV't¬J_ +(B[ F=yH+'@Ay ?Y$AWY>Co&%t 0iq%wgto;k&Qx׏P$1w8C t&#Y+ԇgKМɼ s ۇ'Bh.>-ٞ3Sю7DsM.{oV_:Wzv2PpfL{\)cPp-#AĴ5,u[׹֚*r[S!e ^i#ORAZ8U\Dor暫Pk8_F1RU~ӵPRt2i,iN=(%kͺq#<7Ug܌_Zs[k`"S dd3=~sdXT01MQwվeUpa_񢁚D"&'OG Uq*Rx{HPʾI`|>*[%i3r>Jmrf~/>m,i MDj)@hN++f[YʵEPs{i= !=Y^ >*Z2 n9c(|~|`Pwh}Gs2\'JݭG)ΆZqg6f%g,mk[t@UMB#]8V\XElդQ̷Y: KޗWT5[Y7ċ[3"B"cM=7M=4 sUlvl<93Tc00ހ93VnW!ʺR2+? Du"4'O}|$\?%.-_i5dQ|@a#  #OxKmE~'M +2NJ8P=b"kNNY-?D1irXX7-mN2RXػ~eG EH#pGvF^AYa~@Ӕ,YeigN +#EYuU`6+08T9U\p⣢_-92䵥[:K#IC8ły_S8$cp[ُWMݪ#=+^TNky1m oLܳ>"HPv&ۣW]L(Nuac;+vFUw jH8Vv#݉32Ed[U@8~*5pb1oO,5Q'dv$r]W]l'' wrG eΏo3~c0)qojˊ/0˿s_oqw8VV"]D;}S;X{5BApX3aBvx5*H $YpY䃐sؕۗ q:*DPX9t +関RJhٓ!7*QAxGAX90`&6X[H=.; C-`ߟ2ƀ7Α28g.m&le񫬸Bz bٍBʸAbb OEt:q#mښ5*f|S\f҈숑\}ӐU[AV,G`; !_tK7Ꜷ4ad[m۶mVeWm۶mg +yVH_V.-'|vn[AݦT~%k3rպ&%'0&=Y(lBpkVns{l҃;k7L 9nQXxcք8?!5ީ#Cs4zMCa!Z +%HౠǧAAI¢M˾bx6[\ mq41O|NH5e@S#oJMO4I3ækctp.g؀>ZE1G C#2hM,E:"b&p[8֡eUT.M5*]ꐽׯNAls-2MT,eQdHRH Ǿ=l+[mCu +ɿR[п{0;)5چ; SVBUu|(JŽ0  zduneV:zUwE!/)𜗖^yK'KC;_3:aV0d*&yf^VQVVᾌ[zvLfĜhB +@Ԭ 4m}wEQ`+jgZHшG.Y#;L9tlhzS^hE5,qħB\7UQ7y1Mvhvں\ee}fy#ۻod"1䲻KyΕĸ}vV$s> * 箑lE/p_Lc" +݀=r2Q~OgY1-27߈@`j#)pD5DKw1IV"gW21 NH?ƊrA ̤Ln!pS+p1p;{P,gLpє P}IQޝRX0¼ZA;Y +s{̀ǽ*djȆ?kFIQh?- ݲ$ '+i B>; Q:G}ߦJ7ubqʜ^"V_SLV+!=ՀCOd}1 hYkjE'/N8g˽*C-T _&Kh#Z.QbDP[' EΣđ"4+UcqwTexXg]_o`Kk+jCMVw ɍ0<1΂x]Gܒӧ0_~vNvv= P#(X:m9˽oao^ j7Q\@gToɸ|=Ur9p4Jd5=bj>Qc+ppk@qXIR$%Jƀ|=S+ y ]:llې߅Kka& &Dp^ ui0.ſJS;2 ĖB"J"≫Gڔ‹YTZ+˙tX=)\D8+m"CTOX.ͣi8Ws`O˻΃b1%ī w=ג"0P!ABoA?wƏnzʦLܕR~C<{&0Uc!<Cm6qW8pUaxʐC_|Sp]s]r=`nV~wV2KYt9.tF%lj]/@_P)q{1F䦅lDmG5,d9IG6DO?}OTSި^#poa&G誵!nP)͸Y'#yœ16~(r"jD +̜rb beѷJv>^v}Y RP!vyw#A+jvB^9wa}؄^KEP&)Lׇ}'=jp>)vi362f<'wRG?f23B}l89JMHCҵϏJg*eɹSq]n^2W7'@A>~u7?3W$qe.KCREfV i6t͟(u hWhPcц5u:lQ+{$ŠЖC +3:} Gz|B@r8:0.Cn1‚@ÖQ{m{_M$Od1zG5-ȪzYl1-5qTuH(O@͍݋sn;bF*نP.h;^Bw)>=~W ɵdKk ;%L>z08q kҊb6Um$|q%Ye؅27p45՗sqte8I`-ĉ-ibʍP:i;{fz5@T4&a3< v7=\l8:h@c7'G LXo+bQsWO:e*J:&"(>u˝ayc8"U-*'FoN/WK@IrB_mw \;?KđW9𴭾QW gH2#GϘ2G5p4g#YLB#rwb.\ !ԋ҂N Y@ee]RP~Fkc'W44 5${r<H:f Pgٜ¼MIÎa&=JM78ă#Xs7f`ct<a[[gB P}9-$LКim[$H% ncHDh5HXRAkI=jmr;^+tE69fyD(Yb]Dm:3Z82ፓO+*cboH0oY5u&ja&ijs**'VOF1gAW[B "&"[JzF{|E$ 5VcW&JX bm}sak-)ԠLH.*[C:@eE\\d~)%f͔t33w,8MB, g䊯%pzZcFnt=i>/N,u#t~]U"]t;(t_1ۦ$e(FQOf.}K5aKT쉑HnCGٰCs{ +Eɗɒo'bglXf#H]r\-\d8 _~@F9hُ}IMIȏS|ʷfP[F=Jˆ.U rE7:%IwKro hh1HGXnEYSQ%sAQ̪ku6hS6ET\UU ,^9[3(ꡋ,TtC3q>vcoӿr0燥Hzf  T\[dґo*3[qWO=%J4vz-*d:"_pŖNJTd'̸yz"U#ڱsK;[_G#j*\cWUgѪL2|L=E,ćhGWLO.'`qP#B>4& ߣ50F?NdmH* :hר`#4uiļ~Jxf2ɥfR956VB Cwx{)\~ae/)Z>]){Crp~JpE[ SW&>6hU"Ue ۪><=YfNjM {["X9?*.ny̪\u?=- a@O9 ׁ0$8ܩ L,]횢;)~wFbLO +C d{]my#, 8ɏ Z" 5/~?ԜGD5Yߣ6,hOgh1 ]a"hL9 #o%y qs^a*z8YiGЪ2U?Ͻ ^ O)6cO)KFp <qf~뗖 f}#c[}lo&mrVA!e S"?OB9P̚I`cw)qYhgTAo,n :΅kQVMhEȢ\ez^XCDzcQ<ƍӴ> ߽L$b"aKܫ%uj7{2Ʌ{Сm0p")u'Xqt028;ݟ{IOOyѭX%B<]{^M`l:@r<0XjidBpu lV LKpwstO2!BtMOxu3jV՜s5їS" KjzcK!KM/bDG\,fc?ui:&Rn"♭>V?uBܶ_Ւ{:1B%i +Hq]Lz3 1y2SVvnX5VX h"n~MYŝzwrm `*DkkX)YI0ݣUU BˠV'<دpw.Np;=uÕpoDZ*RdId"H_qFB \Tͧ0Fnļ//N?_g[6^mdQ<m )딟1Q>($؇ިނuu +HamhG}fCub'I5\y`t|C(æh>yT*&мj,2\B]X L4 ٛfeɗ2"lWvL=J1}Z,`Q4.';X/.a$4L@8c:1GۉyfsWF4@<i%0Wӄ6dIvydѝpXN 0.q~νA-@v)2j/qU)1m$տg=ӥ~=VEÈU5ܧ Bzߤõy.bgJq)Qz[d3AN1-fHyҵ#f-~L92lkr%Wcʀf'$Nλ-*F[~RgpWWi風`n e&?ZI;2d 8g\3-N)a|Gzt/i3!A!<qZnKs?݄zbvxjF)?/f +}b:.9=x%p1J1aL2#/.#[wA>53amR>G-jzxDMpGY0ψh£m섚51ʞV,i;]h6Eñt`)bu䗉HF6pr{6W:L =>˙ʑBq^;D}n(# UK(D6jLrMmЩh7$e,u]4:d8 y~͆ (?<5nNki+u(iƼE=6.<,i7IB``IĻ~ +Dw5=mZ7υLi46>(ybis"lHB +9̎#mz J2(ѱuJ{p[o8PO[p6_߶`K$vcp37<P#h@6ۨaSnQVË}ꌶ0Tt 3{+ygJ"sJ4xk[) +Ю},cXl<Ϊúw释 k =j&OӃ)~ ? |6R1;;y%$z'W'2U@] Y,/+Q|C>`l!6^"̔sVz[.Y}jOZmBVp\ sfF>ߜ@h\r/&`aCU?\+*JЇQS4e#Z@ע'nU]-gX6踋L(ӟ/7Z67+wڳa^B3?nt}^wI@1ZBvm T H m7%Ig |2 c)Z'XlM&ux +㩬)=?`:;#ݣDHlQ> KjAR4,ZKz]`Yg"\$Zq7S5!(WL +WLl '!V)1Ј\(c&5;uiT0gwc9-DOQh.=$)w><;1;Îh㨟 + *n&,zFG :X [MQb=S`ɃǠ5e<@^Xh䖂2R0kB hzq.[؎ )mD@"!fwSH| }O=+4:sAT<Ǵ1IN$,vȭAD8B‚`EDEGk'[Lѐ?RIAo5eC_y3~|W;:{U^Ng1(bcc]*Ts%}l-_*iRZP ̹ɨٍ\_n%e/w(j~'IEТ8}ij}۸_ iTqD hrg**\fvXi^S dJV7_j {mn)8GϏ̀U(rUTc~Q7!L 02Wk?󕭣 vX}'jy +{ *u?aOp$Vďh72w}~_(ߤwhP;o;k!+Wn6]DTa3B"蒏)XQ} E2*jr^wN#Nw7x + U(jU)2fMV $pe̽azgr `$AlhK5x~biuT#RѬNXw[Ԝ,羵i5$=7hnyAjjn*nw:3_%J:Obd\[Y;Zlş)pMC(A)9u@V!hߝW(cxYɧ蠬uL5HŃ?v*B@l2o &Uo{eVw|UQ0_N1@)=?MׄP7.DH>3{cAy_EEh$S;ec\2 ݋lko7 E<6aSofFR?]paqz.Bߘi/1NɈrR`y?CkSr`43p;lSC[0 g[dՍW ՈM_Ǔ*B"2M'eRG%MH{<`n$.~ْvYtS$/-fŽgAyICob~ᄏ SP]ΰ1Z? >JbDUL^"gB4vx8z d ( +fU*c?o~vNS_ӐU"JazY*]+W܈viҡTZT,5E#a4}F(OWǎ`OkzVŝJ9LZr24XZr֤sls +lzthA (yGcI e}!m*81Bkΰ,NJ.F ЈÜAO 7oa]F~fh"b6Mʤɚ`xZ#+޶ ts*]IgJ7T?b߸fKVJ/=Rlx1c6Z4VR'TJפ1ӽk'z:q3V&o א3m:yv՟J mv7lI7bE{3]4~ Ll7C`;祶z0Xqި|Uuw _44jp8B&ۧ&mge U1:O"2I3E[Cp'֑1{EuH썱?ݨaP`dK,ݣKG>,1A;8 }4MfI#ۤ{쒌E@꼿Nylb|u>ɶnU^Ĭ!(yeK\2dV=*N$~"qV!%a 2ll./K(Uŷ֏|["U}+7_Knp[?OjT$b\A0䁾N8j{WӞ+B?, +Yi`&%**̇?YBd -Ҵ}p߃^8~v^ zWrк](%&cY jXk<]hw؂ ,\PAё[/axͲ_ċ|kJX) +OsRFvfmPDB?!@QM $2/[;x_TgTO*ww5ׅn\wwн1+5?T3x. 2ǵLXz.6ju +3} 3`CݞH5꓈PRcװ|q3Jt&KDE9鵏vtcRzSe0REm;MA㛆]g33[e%am+xiڗQ bAsi#:8@Ye߶"xq㼛M1.HkSx0~os1k[m wj0]ϛD!pA(o0ٵKt!'ݲ( +MJw_fy1_[9INzRb*#j4yĒ742Cu y; +7IC9yשv;q2" ܤ&-3z؈7:G}{ADIm-8a?l9ib%pq*ySc$Mʝz#xM=ʺƇ2;@CTZ~^E!kS8L+N>joURm@x;?A<;EE! ɯLF)"6W?`r^/hbl^*iQ37 b7k-\1#" NdbC_럨=rBl:i`6SG XjsͺTt1Gehq,8P3o뛘1nA fנgΚT ަvG𥧟C&G=-r"12 nޡAٞٻGwD h1Úp=GODӜr񣤰qrhyJ +Ui@f%Ől>0ʽNtZIL|a\?bbOdNS'Y:pqGVԂcyOO*Jv(8`)ZwL&3ÖFv 1Uuq;'V Nqf/?q =-O=6^uA_G(D5Ey +mH;^/PJnˆ\GjxP! k̵SSr?˸9~YZH9IwS)mb) +wA(poNG+FLz jANJŦQI,d$lcS{fNXe~@v#hC=C=8{S?4bޢ ?`im$&C61$`z/l,9;5w/$.V#(V!aa|2{z*3{q̭496CP{x+SXW8Ub*7/Dh 2SZM;m=u:S39I_ْ#9 )A,-eq:cĹWAH{A`ioIY0 gb]@w?C|1 p~Vް N`*Cr;(|91!ۆ: lc<&s%YL%u d- z^֍Ffn63Į*#rs n#S9ƫ$u +,έun7Gй|QOv-Cu +MsX. /|[b>>y5A4ؿ1~Q$W@~j{3z@ S{+#3KH-r/v?k6)"@E֔i"Xy:OhAX5΁ c-linV( 9SQ5[QK,<`Ơo +6 ;T&#}L=/ξF}Ȳ̳7sTu!,}Qylm%a2YXD5bcnХUeYrhy۝\ +W-]X]rXX37-kF!lk۟MyhaNP;eQ_-s7b6m tp'f=vΒ[BT;Fٌ 4|ς%8+?AW@iضm۶m۶m۶m۶}c۽MoMv6tA vK>˰(>P 4IЏ+N"O%TL~3;.~&u3)y-7 k%J:c>8<ϭST|BQeLdɁA( +2@2i+CF';a[00hz54UBT0 zxF9~Ź#FJ4_[TA0gmqԭfw'{GF3X[ I< +X>|AB!AC_$:g +i{qX +:3=fM2RM^H[sL >O+@(ϗ|fOKoL>Dd:Ziڟ*4HMteՄO 0%U&P^1P쌬tGjytK28Z. M_Y#QQ\)`!n;첤וii |c;\ɵ 0U|]bo AVj%imb~xvy%'ǯ?p-Fimᙤ}e~?D+d4B?'MC|Ihqg9TTZlBOc`Creo8U +?iUthpĵ=[d† J0 =[͢m(y7Z'$uӢWŒ2\vMK̛cO6X ct_=t~+pz&euÀA(tʑQYe> $MuY+G;9pW_{N~}7@ԿhOg +̠+}z0\_^.N<e< ; Zd1Iӟ"pG_-jr ,u4eDGFXay!^Hk T֦y"F#wetno ^U KyB~_j#H9dfb&Pm=UÂb>DoC{H3rMLB$C ɔH05C:$aJ:3tVk XIkr ϣR5x^Xv'귂`̲19acLQ>6O-r3#r(<88\ EDr*?XUE$p8/xW. P`NtLr!>ݧc1A eV&Lͅ~_c(MU./ޚփ~9`WPsDjE'9bTBl:!0s0I;*s^l8\.>sw3"H^}N溷i)a`&.]WB +6t'<0KQ3 DKӼ>ͽ۶OFYL! S!rL}.=v5"\LzH85pZYYv0eG $І^kkdΜ9W`1fgZ_}Nb:0^īŐ!L7"5m('}5zy=̳aVnrSKt!Ujp{ÒΒ0ΖY5T<Qe59);5;÷+ +-gP݅DN𹥹68׭/ +uob5}I? UV):=|byU "RV tUESpIx "KrXFS&s9řbGH. );zA mP\F'Ͻu }XLan4_Pr֫  s湙 ! {-=7eZ_>,?VK`Q5RU=/%uw&С|d!rㆈ'v'- ~,2Hj2 ƭKSFH8zM`bF3H+>[0gP3Wŏ0XhY xMΰƇa$MT2\;C Rh}X[V`q`fRI궮#1d4D}pNnX9!\yQ:$n5-™ݻy.Ա!XL Q MbЦ/D9 0N"FRxCOmּ,BWV)t&տcFR[mTt9?d@'=A7z_؁-y08Tﻜ%7y;{(lG<L*ũr3ތ󥖿0P=0{Tc;0?u7, ΞSNn5k%%toWRp~ FU8Č[_odOM y3)5hilUrpNXv8fb{ rl[oMO~%ܶRYfBYps 9e^t&iDȿgFY@vNJϑk*Y׸Y*iG9eR뫟"SDŽCYeRbAN⦎@ 0_LmlޫwF6JַU>UH$ou-x( d!4g_"BP"Zau +h rgU_87kD oP +Ff dw9GϴXx^Ff7w:#U BzduD-;^ ߹ګ0X K + 7[Фl% Rv6KȜYXqӣqgӕӍ_c1A ה7ͻ/ &+%yAQӁc4Eg&}fKGO(;p w~af2q'r23+-0Cv9 )~gF8`LU-V_|5)6rǀhzdA~f2_Ҏ)pVL/NO&MN:F z D>by~Ѽt9jO܌ Y;:+¸>P'QL౻2s2ߵp\G?z((xB\տ帰ٴ,=-^c5 !k.S׍<.'ЗXF OC ,P:f<Aq Z;P"U Jihږ># Ufܨ +B%1KeCCiH웻w#E▪bˣ?0ݼ (Bv9Giwh\8-GN/%,3ﴄG3#MGDky>?_>#D$3M16L8^R~>TbĿ{$T̫{U;a}~㋉X) 86p1.)g\։3HRBٰړCeUuSkiն@F-TbV}^ +~; ڍG#lڪ$PS#z/'>Y0h3Ң?D\ec|6)z\DP1{SP*_tz8P7 3II+zΗeOuVVvt-onζkԦ\`\_q!(ejHeRr< ޜDEeGn,0re񳯺h|u#eg^ }'*1Xd.`[T}#6?{u 0q5\*yXd"i+oѼ {qz'RM':(uH[^н=}Pf]ؕMt\S&{ByơdgVB5,"˭=~nA<2@|Th<J<$\h}իo]$LXhh`MS"c1}.k.Bz9+b}yXiե~MmWsYB8y]Jin)(2Q\l$d+y0WETD@'pd;% +7x*QhBjifJW6ae_ܹv]^ 9X;!yUu+a:yo옲og퐷}b3%#a ap3;WXTqQo*$C$)yA +k *Q4wDTY">Td8|¿i:IZZ=z4j5$&PWe84د==F:%I~XZj9Ø .hSۀLF@S|_}CdRsu!9ve$ 3ωvƎ)G2H^NH{#+>,҉\yüW:n84s=Du4MLh;6 +X'|f4esGnŤcuspN}%qNS' 8Ǹ։d()JwV W;zѣH<]wi\zuceZE|1尻L !2D؟% L8ͭK3f\.gidҜ +8RD.0sцy +٩%^WPV͘U[=A?^4~@-e2F߫Xko{EM<0p/D~Ap3<-7]7H.\Z8:ﳺb~Hu#k ejbd&K"ZEFr Ϥ^ mO4YziUWM9e0`U!~~e ܺZ ~Z + ږAMLK'O;Y?X5%c]>]M)N7ێq#@> 4 BU]iMI  <=^HQxC/;" ll#=ˉKX~4u!nq{"n-KIJ &Zl~)[݉M) +Eh (Ԡ +0it>bH~Xc<8*CWp G=z,Yt6b0#(H;-jzq(GdÿW$ *q +]ytx/4F -]GL (2'Ye?h?>lgIj?ǃmYS;8~CϓpH 8gz. c}&/$qNpݢC;:-I,ULyDCYW_r'y1bj=Ii MC@jUgh:&6/j!\z^ T h Җ>y':aReuW>ϋ2$boV Y3 l Xi;cǗғrϵ譺ܯHYQ$vR{=FDžVӸ0u}n ىz=p^#4~PfY5a-,}ҹ$//LH ׋"l#NR郤^C= h"uL `Ǻ:#\x"ԆX +Lz'|_5F]B'k=呾M(7 Pf*$tA <|ߩCG$fH %ÔfݫxLYXQs]l9#kɴy4W+hHڦ-^+*`*Ip"t [$W gO]IOXbhas=E͠saY@{ГbvpZGA^=H)A|j_:*(Raہ:N:Hj#bۥ0T͐AUFD{l__=g'iޒ[%6HTSQA*K>z1O&s +4Jyd1$ +MvTa!3g ޒCMNʦ.kA2OtdȄ=?^i>(:jZ  tk0ҞT[ Y),h)/E>\3tK0;TĪ#csj;G<ɳ +WR7JvY2ȸlB8S:Qm5߀S,rܚ15wf@rB]:z ;+*v_ EU|(f9-^qPA]`wY qA"]9 1\EXxB_mƛALO_5RFu﬿tRoAJ } o&5PNBcrI;(&{Xj-/»{ @:N.H\;F;L,c]ɓ .2QqK-xCK߿PZt+Vb?O^-Ŭߜa +ED.3@0.3 +W!Hǫ~_G:I+C2՘mz2#1O:SM9F G2h ,כѨXMѦ&qKTڊgu}|)6e\ kR{vjTp -vӽ_#E)< 0 ~^jhSˇ?14PAuB3O (OuιqMzahp<ʫVT՘|`6B A~hmdbq3iS^NRh2(}b3̧!`6pRJd9o +׀ +RHR<· &Qv]&Ө{0J#{ yI2jI$W +{f.&A|g 0ʉF?Sxu񺊡RG#uv' ꎫH*Y>ȃyjڮlL-rN&ѩb嬨B`ԉɤ%nA/Qy脩fa;0M :=Ǣ^HdggCPlOAiL4.Gz@iD=OSW܀'ZF +&"_킟^|JSb/ [ A(吭$:8 . DqI|09JCOάpY3b(#AaW [DvAg*| R#)y +kO|ܒ4Aԁ sg7DŘؠJڂ@أk̵tmݖNo _?'NPܹ.!UBli;>ͳ<ݢ"mI:6%t+6D J5fiqGHI*;~tZ{zF[G]1u4"\1ժO.d%FQE7ЀAOsF(.WLHGGj I~Fp{9竢i);`+2_` 遂^-fL]4 bcz@K_0z!=PׂID z 6=` cUC'u) ˢ0EeT +,;IqH6;ԩEc3]got;@6ҙ5"JUI}Q؁ >Wt RNƺ.eTǺ^g2 +&M0⹅AYywtwt! +aM< 4 ,̋'21 l,.1 9mWm`BըY?<3e*Dt^̐Ym! 1DɅZCs&IUXT݋ ] [ԉ'Ï ̶LB,L:ٳ2h,߅4/dC,W X-G]- '4/ O. jPO n`= { 0i5.5B\Y| K綆HAУt"%-vיgY&{5ލ&H+2@k|寧IN;/o}s-/g, ,cKUbSƜ4 .yY~eAVCY.Bj8v(Q;>mӾδ;̛rv rȦo3L`ߒ Ne!Euo/a(|As,ه-fF( څބ/m\;~:؅|y֊ZOG`g \*=2eռFAfբ>S {CI Rz;/]a_3 +-.R L9Xl6Wii=1YRbdos?![ZH Rıtqu燯Ur=_k74[?m3&S*Sod!@r6o&n`Ϭ&,liC|6vr+7C.h'Dv$U\N:6B=#i믯Kfl}o0c-aKN+x^\dX ĔMt i_Ǽ\)$#zLBOdUϚ"irj4S{% sDP!eQ j EqC>RJj5ZF}p"iC4,nfiQPA<-CPq߷EܩLY)@'`f1q8f워JZ3T3# b׻!N p~}r"&XVĔ.7 t7a|?E\G7_.;uO +: eU;8-e,t7i\*Ly}g&onVδEJ lˢz$E:%*UdߟYY<$/fGܕ4h 2Vw1ؤ&'t?j +O(,g &tSkQo u)>7J)4FУ-Tc$'/ ++d?24fFfiG,k30o95U175@u lPJ7*B/,2mjS%UlC^9'n+,MW ռ$%B,8*rlJ#;/۰}OEY$FU+lQv2j;)aC8ջ6k/2[+}qg7!١3yz}dsrir5_ʽ?Aۯuk trƞ*h#uFG#;~|$FUK an4om\Yb(7[8:)ȑC\EȏNb +zFLU.gBUj>H73ǻo%4DA`aCTpLR>@ [,JA鷍kҶ1] Lw0Tpě'͒4fz 54 AiZF]7ʧ '0%"W{Qsm4U.$#FұT8T%H] ۻ| wCȕj W%{^KÓ|,1_u\?or ,T/pQYHYag,5>Cףa&uDhtJf߮MPUKIcEw)$x"p>k2xR`U ZHLoY>m-cT=Ϛ8ΒJu/;=8h7{}sw\^y'M8"|µhf7Lmٌ gQ&|k z<豱lgj߀ӄu-UI+"Nt(s~އ^N] E]cRҐցbgHk";$-ދJd]KRzRϪH,,ؤg7a!K8r)~XY@ +pPlu#'1L$l6+TAf&+5[b @N԰Ip9:T)MAfU=4m(")\~ +)C:Fz]jK Z 92HX|Z%<),>>?t\.a^kx#׽'>9.3Tc5aR$)FFQG@YD0IE>+Y+F8&c2@DedkKД +Wz#{*=%- ֨'Ŀ7`SWد!ݠS!B?i uY&Asgak(XJ䈃nŒʹ/:hWL$xfhUpE~h2 a@Lպ(y+xʲ cXj~]|ڵ NyVh6PrZO% +Fkg2?XݏcKh:f%#|%\v @~K^B El@>dHkZTxO3{2t"Pxܦ-n'36v\cT +UXrw?>(hNq.Kt~#M":}˔"XEB(S"AZ%Oy( #ҽ4tvH57 +'ck0f UUU{w\Lap7IԎHl_3^fv /jxU [CD83E?*lJ!+Mf=2 \[:RQԒe ±.$!cn^J2_@10mκ<ͦ"B"ݓj7}Pw:qޒ +1u1 +uO9ZPGK &\IBFP.!?Iwd$%"hshfksC쾺+X 75C>=T#)pIO\Ї+;LuduS&9iQ9N6b(IbGҐu $i}u`HVl\bMn:TƢHIfw]Kr 78~`O6d0\hGU0`Ժq}(^yI3l{FcYsĪ&q`LBϪ7R_4%Uw)?| +,CKO$y>P)SbwX\c7D0˔]33wkơ6!U#. #+Rkk $oi-ncr I#Ό{'pZw0m +1wSg}LO"gݑw[tdSxmqO&VLF b_ɒok2w_]c. +e}ڶmm۶mۧm۶m۶1$Jl<%n^ Cp-㮾>Qtg ˇ# V. Gc=rn\yfrjYudtNtxv5ǰ;݄g]~擟GvЗ0t.D<^콲bUV^='_[o`1fRɁ(?0&&:7 rdq +.gev!}YV%L9]hE|%@/!X(Ds*ڈg{#LƊɴQݬpb|Ⴁ8u.j^$N;v{c],OumqJ6n[Slrv>6&|k(gҙ"sIl֝l +^kF3^:. AЫ@zgӲCuerF|MbۏkY;vPtfG,2hx/ToWZ*V~R]~zH"eE`B_741]8*U +;/[.Bll\ 1cH掮@TKrD4K!V-&{*12j<xLuI STߟm!Ijg?UIo-;:5êF˯W-0M ]F2hPՏR25sfÚ{QFy.D݅d Aw5Oc 9\RJt#HҪڗ+2X}>1ɣqY50_@`,RF'7 ߽.he(g+w|r4քe1%`$Nwzr1pPjJ\ d;|M)"=L; +ZQ pJc6 ;35@b )ʂfHO\,Qh{\CJ$+NV|B\>G[cA 2X,'5VDze.\xۥGUg*a m'|ʖu7MV;zI?I7 +~a2ѡMi^F=+ JٲMJxt.6e($ -=SXje!k:Teߺ;"jP (kbH_ާ-I6s.lwiN#tҁa*dPv "p8E!NN ]M^0JD)ß1HvHw%^p3l:!H!X᏿ /=7C;Q Ӣ؝s5Ir_KnSWa*w,boQ%VlK򊅉\aHZ;mѾ)x~9cz zū)G,r R8&5.Tq0  Q`EJ7AH*w(7ctxQm`\1P˛kXCG^suyKr 'q\4$U,=?3VLq<-2e)nxjTj]a!O7@j.&K[xQ̢2/_pa}ٕ(}y\sRWQk9993@%ըP=HhZ=Ka)]Hh&K`1r +rhW)6"Ӛ ^Rdg<=k"Gk^ ևg/Ӿ,H"&bBTBF)CkⲀYvc?+N9<ě:Gsӊ'Ga!0% +|K6I&JihEZI:7%"EO{3C&{צ`I{,XZv, +~W.s AMd)u/AĒkdW_Lrࣼ^w#hdqA[Nƾx.n. ɋ9O勳;m=}t~R+n{Cdi>!o +oσJd3Z5–$9㬃;yCc@Ҭ%x[Ukp,eF!rބͤ{\%(\"Gy5rAoƨYdIhxB87-3~*ǯH, u[/]FوYT3?ϖc +wQgJWULZADco/k1Lv)PMY/xtNBu{\zMo,c;U$YadQX ut׼`g^olE#z+(mgݻ!6-㑽4x@7۠F 3-W)Ġwz/DQ}YȹDҴcz;B/H) U*~K:.7Yf"ypeѸʃ/IN@A PξDr:-$uxR<ߓew+u3'ґ!JU}=Jc&J}CSpCaPʡy{rZ)Ȗl_/?,׀c@ ?UǦhp·xbԓ vhXf܈{ t^fn W~Ր*2}-:`sNګ]9+W ~ OyZ_:Lh lqRuXзOADzO??-2d6x3xHB"? +6ѝnqݛ>w~P26`JP!"V5gt_eLh`T Az1ohw0C>EPY%(6o2oOg v=r܇ᐧ˜7G8.U@Liݼ@gTNSf+z|e3 ~OX25K#CduOO^͖[')H{0".qjQ'W_B g@JvSS@V,rE>Ɗ}.YG+Kbto)iu(wK6F'ޅ](Tl I/sHRFoǟꀼάhvJ OvRvQ sH[R罏OUۜ=u)q5io? M# qP=(ٵ.T`Dt;l)lbP0(?ސPvI$D{[A#Y{In@/f~u`Sڈ1ĸ2^pݣ`J7m{W 'C[/FMqFVH(Dx#_PH% t%1_J XW/.px5s4YǕu1J.#V9}w|F5^@"\Om("L,!ȸ1E>mβ:6&v T+͵ݿ˨8 O\UB?N&pP +$i˭Mtgݟ="ϑrudQӖ ˘Q}j{8 W,3^%4->qh&x`_z$f?Nd\ɝ(#"FU^8',")`,xOv)TI "\yl Jj!qY")E"WH#D 0O.M +Ԑ;h\6RIZAM?Aq_Pȴss j_Sj?_,yʂ«M*r- e_ٙȊF0(Jku\~5]d`Q +EN-8#$C5-VqMπQ הy/kr?mJϦ׿2Pvep$[J+|ِë VV٘iY azWo5^taS|3^Ve-"dK^rˎE'v}PhH他5vp {c?D+:׀-0JWH(>Er,q..<kCсxR5uXoY-!G)!D=sC;7xyVK$kw?Ul6nIp[ |w2Y_'@e}B2+x^%=" BA-H70LfZx{sA˵f2wP6뼳tBl։S˿NefcS|썉R]go`:!t}2 AۥHI"&awİ.ST˰x2H($!L[dPeQP@jp>A{Ɏ零jr-i(=F<|[@:KCԋ^+!W rxjw ߬^Pʾ ~_ۏ]1!sNjV^ #d;j5Ru Z[41}&DlRm3 h +u#N'gTY ս< !531V[~ 7Z.~x_g'"eJ'ܽ sˁ:a,3#HPЗsQ>On9ۋCuE7>1͔Cy(ahCyEc0' dZ$5Ne}˩e?_ E-iE(>a CwAC< 21Z"ǞQ/$/;g2tu,I6r$G@ y(Dک<-(VV7Gmkdy^= ' + +YfSQFs<}P`1v'f>m=7 C<OLzPVOp/']ֆf Y1|k/OZi#)6< kOY+jA[ZL;|X}GAϩѢk|^x0+ra T<ק%eIpWְCLf WK٨aAc|3FJP5 ‘y菝ȚP7X҈)fy; +ğp/צ5 Ϣ2bۥ3e3oK%u/ +7:߈@胧vf'aJ]ɩNf5%DVb9Yy,_4,hNpL9b-0pM"|=-jPnj0$7c,bGi^,ty/u _8Gya?Ib;1 +0:.Vd sbHF7JsyN 4Z{3CяFw,u)|>|Y7}Ӗyˠ_ǙmjJ +\F=|M#wںra:lDgD~@>:Y"b&x.Q"9c~<IҴ[ tܴoě\㨧-% $^ΆKWOm2x +jZlB #dPF_J;XSky6X.$2.|Փ`r{X#֜a ga#}$`o/@,ĖE3 `N4+id“%Z/3J$VA[ѩDH7OzqM۱ _NQp +Jc[ +hgۖ3IVe.K^8nfW Mۅ8641sO|Pjf]NE<(p{@3R$ȅ<|\S R,~X-eT%Y*' +1ڟ$besnTE{56A%wNuJ.KZq\6%yjr"l$! Az!q?uOu{\4'NόՒ,/V"!Gi<tZDm~Z{hc sw o;C3*"pZ<3h PtPi$!U[:Paե%O &bR~2d6Aմ?cR ´=T.{N,dҖhu% +v5`,l"6(T{42Ŗ.=:v8*7;ȥC_iEEdN[t$vZU@.ÿ-dqiVY|p~ o$]д^Y6S`gWh/Tk㧫ӪG^Q{}:flDrQ˱,NOhHw\켑%Y_v'uQbȭ5Rjs4Q?a +O.YLv3n\o=} =vGޞ=2A;ĩ yɰJ,z_Dj>`c);D̞tu"X ?OQ*{s{5(w5hC4nC<Y% K'|>Oj펒G,L`H:6[nFmxx]GMt$jO {@x̡.E#nK-Nx?MEorI!lڱjr`"Zy库"P̟so=r?n-{!ΧDA~47GB T\HAyXg{GP5+jRH6ɼ+7GK!tf)RmO9(yQ1Y +y7;$ 6k !:  \b+1?:ېWU\Puħa鷖tw>yIs/=N?#c /-: ïnS-}s(}9,BߧlrAٸ1,:ʢ4 q$<R cL?Tݍ35FUȩ8U}lAӅ>v \6 +,:zR}A +XV۝[>170R^Brd8qk:_kN[|)yg,PxcV$zF:tt@TMc*HAہk֏e?n3p;qoۗSُ۫~53B/l6w7EIOw3GղRdL|SFy4RHb#ۂ6qK-r_P-cs(1Ƭ:P)LM%Bv2=M <*`]hOY{Ѐr8Lq>EfS0l( f,HOvQgtcUXaB׈&Nv&#p!7 OAFw9Cm`]0CB+ +ˢ:7e˵ā/iDKE}.FITM[dxzq"`Q غɲu=?p*m16ujw+m)PG>e>k-NutC:wBQ3gj.ҕʵEp{M{:EcgMSl*޻V>!eC/yŘ~r &бfneս>wcjXL>+mE'U"o |)]K34RfqόPvLVLy =xj#@:[]B =u/O3*N ]vF~EuҞ#Cz+FoT,>}6=1_7K͞DYUukβNa5B2iBJJ8^>,|t^Z#(cPA`'Sv.b4nI%v"@Ws9ʽ-GDpvhny^|v{-I!cvQgEYOn 򅘳.S= +U)x a!$8ܲ{l턞-E'BO`ڈ?y%\G 5<^SO͜=%3X}HNޢ',n5ϤR0uG3>B```OI tTb=yGY8uj4'uJqL~?h +& +f[1}?j&eb%X>!Oȿl 3A +g_U(oW|K!'> ]> d)t$˨l^/Hn&(VEonLETKw4iDbkO}<,2WE>eI myEg`yqfYO폥Cwqgc Dɵ_M3LaTJvK{Yˈd][-s3k/װ#ӷ!DꨭIT]iB)f8E2|o\%X4)wE5lP*#=hǂ \^ ] ~ Zх˜9z7 ƒ~h K݆>TMgeo`8$p$$[GE"VDs~"D5RMN 4Mm`ak5 _rN!Iidꛈԁ 7#x O;$cSP$^/HFt3TvhfQd\݁7s"T[WJ ³*/-z^/ A/x1e)+h0~^+7@&&dҠƊ<r +endstream +endobj +308 0 obj +<< /D (subsection.2.1) /S /GoTo >> +endobj +309 0 obj +<< /A 524 0 R /Next 281 0 R /Parent 80 0 R /Prev 280 0 R /Title 525 0 R >> +endobj +310 0 obj + +endobj +311 0 obj +<< /D (subsection.2.3) /S /GoTo >> +endobj +312 0 obj + +endobj +313 0 obj +<< /D (section.3) /S /GoTo >> +endobj +314 0 obj +<< /A 526 0 R /Next 527 0 R /Parent 282 0 R /Title 528 0 R >> +endobj +315 0 obj +<< /A 529 0 R /Parent 282 0 R /Prev 530 0 R /Title 531 0 R >> +endobj +316 0 obj + +endobj +317 0 obj +<< /D (subsection.5.1) /S /GoTo >> +endobj +318 0 obj +<< /A 532 0 R /Next 321 0 R /Parent 83 0 R /Prev 285 0 R /Title 533 0 R >> +endobj +319 0 obj + +endobj +320 0 obj +<< /D (subsection.5.4) /S /GoTo >> +endobj +321 0 obj +<< /A 534 0 R /Next 286 0 R /Parent 83 0 R /Prev 318 0 R /Title 535 0 R >> +endobj +322 0 obj + +endobj +323 0 obj +<< /D (section.4) /S /GoTo >> +endobj +324 0 obj +<< /A 536 0 R /Next 325 0 R /Parent 287 0 R /Title 537 0 R >> +endobj +325 0 obj +<< /A 538 0 R /Parent 287 0 R /Prev 324 0 R /Title 539 0 R >> +endobj +326 0 obj + +endobj +327 0 obj +<< /Ascent 0 /CapHeight 0 /CharSet (/C/D/H/J/K/L/M/N/P/Q/R/W/X/Y/Z/a/asterisk/c/comma/d/dagger/e/eight/endash/f/five/g/h/hyphen/i/k/l/m/n/o/one/quoteright/r/s/t/three/two/u/v/w/x/y/zero) /Descent 0 /Flags 4 /FontBBox [ -1082 -268 6171 893 ] /FontFile 540 0 R /FontName /NIHBMK+LinBiolinumT /ItalicAngle 0 /StemV 80 /Type /FontDescriptor /XHeight 432 >> +endobj +328 0 obj +<< /Filter /FlateDecode /Length 881 >> +stream +xڕUMoH+z2`"RFMF6H6x笴`QuUݧϡƃ /ZpxZV_ ˓snW}g~'?ڹI{)GݿPp9ńa?tX_Qߡƌ~9ޏ)UUşnqxPvCWgd1+V7~&1Hu}Ȋ۳oΏq 6/F>oS~xUw<_/:Vu7>N>{ũֆ]c˾u~xuF46pCk&yn_bl MDSr!Ǿ{4\}!#:(P0vyT؁`h- 6hi*.Qg|'ȋ`mk`QDŽkıF'\S8 8: m-ב.y_T\/k3M̽P,c`xf} LS $suD> +endobj +331 0 obj +<< /Filter /FlateDecode /Length 684 >> +stream +xڕTMo0+JƘ@EHmT8]D|o=y`OvoV'ӷcW<{WWY['c{cjS_f;ص zͶn\VDZ6i#kcu;x>}0K$ynJ'\XEoZ{'[)#r[o{$1yhlMu=%+Ļ~0=j%fOnWƛ=t"?fIv|4'^JɈ٧z}~=hpU[\V+VRŪ(֞?sKVSh$sOW)zrR)rjI5j}#̽> +endobj +334 0 obj +<< /BM /Normal /ca 1 >> +endobj +335 0 obj +<< /BaseFont /AAAAAA+TimesNewRomanPS-BoldMT /DescendantFonts [ 541 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 542 0 R /Type /Font >> +endobj +336 0 obj +<< /BaseFont /BAAAAA+TimesNewRomanPSMT /DescendantFonts [ 543 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 544 0 R /Type /Font >> +endobj +337 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 148 /SMask 546 0 R /Subtype /Image /Type /XObject /Width 272 /Length 1171 >> +stream +xkuZiC&ݙb[1ZJ*BEar/"@(zQ]i=k{6i&s˼M̦KraPyͫ6VUݼJ_.Jq/Qwu,X:p5ᨗzEռ| Id^1EI@J,\[<n&l)o޼zк.l,ft0%G8˧:g> +stream +xyTǺ; 2aYYqè(.;FQDEED((A1AH1Dy$(k^yiga`gwx.2Aكq,dXfX EY 9%bC?޻eH>},l끅c?wiWcHMu]>H0sm|sow{ +UgD,S*@&^؟f,7}X0g؍17KJ7CnDEsv8O9!#䤱Sn`pHcGv;ס3nv,DSۋm5cjPg&鑺3΄QZ犠9/iu?Nㄎm'Ӟz犥R/߭[[["O[[[Muͭ윣ۏ9C]! ٘g9 [W-NmֿEoƍ7ܳ\VrńtTN ,(L"%y}F&,~N1a9gy. +%aӰkMtboZnK7o: ^ ?/]uVoߒ7wvJصnmZ߂%i~ݼu}d> +}gzփgs}-dqW;xZp@7~]͕[rwb9 xpvjsugO[[ܪ}{: |PWay'+/+[c24Ʒq4T,59YPlrhfn0{W*D_ ӔmY 믿$uɍii1Q/IJH~⅄>|VRBEc7?ny=UU!o[{i#I%Mb86w.UAR]WO~} +Zz 2ߚ0eTg(%tC 1c~E:;;_65 m'OIRV]]tawu( B ͐)ȶkܨUD,~TUu<5s;)L=K:;;єgƬ[+ŋ7KJ.Z*s$͘5\\hШC'&~ GYϽ=5>ȿdyq/\CbWϝFI <:;;׮Z-ZQWG8$w~r>G0d6G_v= Yv%^ٔKNѮ3*>ӣ3]LNɭCyG8u uCc0IcĖ#iuoJ3.-ё3N>,qID|TIKD;y +6m96r~~Ný`n"vUbfD[^˦õ1lr!$ ? eF8eX {5`n&-ns4 +f<}!ΝۼɈLpD,4 ^O+E%޿w*T #+nGܺ@_ц+x?yy69h{6HT|Ԅ"۸99C'. [d9NJ–Kģl/3}ՒKԡ]Q+J8 +mIiBhAJ(oˑa4Fo΍L}$,F6Җ2T4SO +9aΡJOVoJсMU񭭭ҖUt*BTVq xLTE~nEEQAkOݾۜ6YoK!QH68 S[4g-Nn[ o+u=.NMMMrkmA?r8ˆf8ҫ/` )hO7XHkk6 +/z p>پ]n[@(+l[}BO-uk i|#+:3~Rnhdk^:oyGURبurL΋喺5P[[ձjA[$z%(n]+ׯk +|OpcJ){ģ*-G0AN/xhq10F+LS!Օ*T^Vw~C7%iӥ]ognh\g͹0g0/nmVJmlf4eG +#2ôtbi?؍16-(,IAZ[['#EFܶ &ʹ3Aޞ׆M#4ʹ3MvpȈ]5&OppH#v+fS*X f8~oU3~y;^FEz)}2f_^y%c` +ixzJנ(fhxGK?u-=4'=~}UKAE/.(9ep5=ʶHSaC߱An0(IʭI5#,I<ɳH0?˴Dz&<.XsՂBؒo,F%rf~fTw섹G-녊~r0# $N +3{g|KJH?;Uo1 o9hoY.U4ˑwZQᙹڻMj%%D-jNq޽6pNaRIrbTY%D#ˑ`3&*Zc9v{+P#b1i` QǯrZZZ܄ȄE2'b(%@K?ȰPR9o8'g+fjw. _P$4A 2DnK2h>p\hT_>e,}IwQIB +0JFfȯKfo6y)/y80ړ>>ӱb˩UbѴBw^GU+go׽ZS]"WV|׿Z 6'3EswGŭ| zqxdW){zG]V=47$'=0n6F̐H>~nxjZ{{YN[[[c^H8rTc/"$I0Hh~W؎\KX&.Ŏu(( ð4C q㣢s>{Ͷr={`\[( P,M3 (?%Z̵҄08MVקz 0<n./ 2q,=(yX<-lJulP]fY{'P|=%Ϫ=G#I{{<aT)mltVwNN +zAYU=4׺M$^Y +Rɒ]n>~њ;z;n$Y +2ZyU Il$Q`㚖gúuH?~:;o_x-GnBޝ Ÿ^$V-i+Dc6"91=]x Κë=u!c5.7j,Ƿ_E%"|Ĵ2ZT-hf[VS~׾ +:o f꺡4w`ᄏ[qWܾg6iR9_ݷ-hfȀ[oDWZXȰW7G[יn1:* 7q^Ȱ_\D<:kukhhSvQrpk=%?zcy˦*T }[^~򼘚ؕp1E/^%QV*A<ڲ7łl R 'qXvy}}KssGUo>2&é uՍluuuhȔev[DQ +KINVo`jkkSFt@}4? q旼~+ڕ5lofTG߰o3g^> +stream +x]wSU_0tjcJGrsϽɹ7}X-jPMCy"XVGR7FZ ՖQDD"Yaqf[&M[yb}g}'%%)a/#bF¬rHDC*TDJ?DJ*#aGȟd"-B[)s ՁTVauʬ!cpbw)[cDĹG4X+5 +˜^FT9FnYXY-3G L1%D41!a ʓ;"΅$<1M7 +3 Ǵ8神nxAIڒ$.lݍY}ۑ7M8a %Bᐇ7L[=1&v䉵jࣽ&āE߈3HC2|-8O+..(hŬ@~uEEm`Aru5kڗV}iUCS(XSrʊڀbbY?tZ^q1ƹR4c%T3cF7tAi pty%r -zkݶN8qG/Wakhcڶy؅֎K{h;wv]l?ɞ/:.?v̋/5nܽn%+?9+՜N/\%Yys^;۷7Wo~ޝ6zꥥ:[0Qeګ .PRR%'ڔ@MԵ7_ز>Z_2 W R䱣LIqX-6%BBB^6Q-yzG?ٿ{dR{NvxseSnLIb$bYl\jzm[Z'p`Y?"a#ʦ:/S/uAS +>"iN" ewL[Zz5jx{N7% ޜ29\*+ 38%خ(0A'_{a-3vЭa2直ujb\Ւɲ@paP;B^quջOxSԕdoO>j<+5Zllk*HҬf` O:yՎ]WSW`O_9r{C5ȡk89AH o +M cy M7>=@}݁7XАF4̸ik_raq֔|hwSPPX޿*'ٓ& i5އqKr`\P;pv׈>/zvsfXY%}%6Gc4aˉ4 zeiy%$NG%敾[)0m)&<AE3/9ڨ{̋3>>I!RTnQ ϏR_)`y5vZq[t8mNepܚAuLO>oDpR3$v7`F` (;/7.H>FENjvW_6l8$;zb8'Rv縑\Xq%CKIC[Xv؁Iζ 1FA2H6帑qeqŝ2l#u?SqNi4H}`o~^B8m*FNO."O8WP~]3=sGiM)ܶ0w+kh + NCI<|N-_N]zQޯ{zT6c6W͒.Z7f$>;B]nQ%_^ll$I f4d=B]bQϣ[\l(DwKJƖ=rb%&R)nnG^4# _:|cerN?˦b+Ert37]q*aGAbcDIQ3T\ u,/3]w=-a%%硋sXc}F4+Q&8,!vS-u8z6R7NfqPqqʼyBkd8c](V̘Z˟@zO_. g1]E =b(@clqJT@cmT1L^SԁY9AWdShҜE_[ci@p& ;/4ge0Ę>fE^ ) scB `!.%ڀ18&)ԕC'oQD}j^\ +!ak-Rt# t%.T;9i3KFxjN0(u {hsf"Lآ)Xqc{̔ͦ.f!&ih@Hg8\} +(wR|.%ޗc3.bt- "2Lu +}髇E5׼Qfq>@]S윂 .1(0fUmk}dϒ)sJ/&uG%?wOh&F2$T&7Fb3uG/|9yNi46 8)H) ^i[XWZ!O]={tuBnuX0d$pW#𞰵E71cN&GغeC*#Lem 0 xǼzoErSxIu꓇99#8󼇋շo18vh3ލ u|y]X%jײk_ZuǷ 8"x7W xsbnN.,@2d>&9_|)1G,[v}H} #"c<(^*jo (ټXmeT;\BzFH/*W,>S_`Ell 9x'[3'Gl.Ove箷S_ E^ls:JJ8'Jm}s|ΚOqv>dv($ 9wDɨE /R_&`xJq`*UN0 +ښ8;!)2_2evT;70urܢŢzW7'gzGiޜ ݣWw쮹5Ѭ{ƈظQn LfusqFza#{Ҥue.;kukdT- 0 s`jŬg^L[X|#Z1SP0x*nrㅤ}KNX8S-6>0[?9@}݁q<ٿ $dSѧ FVe@>6 I+l[<߽Tbq3džcgc-{ayC؅I%릮$uYǦ ! G͚to@Li~Iq٘ ,ɞ2,|]˩sPͳƹ3 /ڰs͞Ϗ~ +~[]syB gLt< +w<*e%CŒ1 zJ2)O>=ՎǶyeđw[mYcLY9ʋ*C# $gخ(lԏZEeKٵvg~u=5x釞ޯmvZSU6<@RDzȼIC*Q^m(~Xg%5r'L>uƂE [C}vm鸴狶s?gWbt=_u\ޞW:?ن+g,(8}|AEẻ]dKY7fh*Y0EHE ҍsi%eًꊊPUUk6 M/ZӼ)l l^W]rɂPeEm_]1{,P^QP:-Ow{-@%"t"FA{ iKe=@z`(yTgFndQ2fD0<9i sn43Y#!(d`Aը9V!s ybA$k(ܰWy 3yHlNjŌFeِmFVaxCt\Fҏ4A$w}aMU+b#%zd&`ԑ:ωCf j(ad,}.49]E(JJDYE%MhcH^?ht +endstream +endobj +340 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 102 /SMask 549 0 R /Subtype /Image /Type /XObject /Width 40 /Length 329 >> +stream +x yr01m!BɺD.|S-?R!B}Hw?Y\p… .\p…ۉ1ߊ#ocͨ)?%"qMD{YΘ^3̪bq]+]ewCnF;@ۮ*BCakU>tXUmQ8zx:-HB[QV4Q+"W7`jN-3\W1őm[CNYkZkŨ 㦩&տʜsty$zpHWD<#]BSR;X +endstream +endobj +341 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 260 /SMask 550 0 R /Subtype /Image /Type /XObject /Width 484 /Length 1726 >> +stream +xOw}4(AV8gw&j6Uaz@DB\HĩRNrRj-s.zFJ%^B}=8W5 B\fi?>~G3ޞ &`U3rm p&-udw׫q,fd5-֡8u`*fq} A1yv\/O< ^B}uGHͮlvlu'7n޺{zuɍW/`vZg/O +O'|n[]-m1Eq<:jw7+/{?@eʵuծY.;j)ݍoO ֯OBeyU|>}Znqmrco@rkY3/޾^wOwˮZ wN~>:kR[ѻEh5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z OV[vY3uo߻!}-gM7.|Cwͷ6ME7>?7׽rzʵfݴ[ } a.lP\=~䇟|O y7vP\,Oo.W_?XB> +stream +x{oU hb۝9gsYv̶@)-@RP4M%@@Ukh\@ʭ F   cBR9;B3I<=3gl(Q- LG5b<]ٲ~({Trh rC3Cs)-<] PޛNxm ⮲jy9y05n +ʒeTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*R5u7>mn?s՟+]i]}#>ZWSy3Do+J&>voXnנ*Twx~=5 ES˳#>c*g74pK8?vf:{TiɼqඣY.Deon?]#أ1GXcز{DոVs[wpQդ'LWKG'Ψ^+KhVGd֔]_n:L*XL|s7{pdV-DjL03,QlYB9EEMͽp4~\R]W +nKHm +Y_ul?04l?qD"J46t V^eutosFP(G36ᨦTfLadrV[L5Oߜ<6dpHr LWS)hZU0ݲ-n<~ew,AS̐}9,;zjv\R9t;|>ae7 jw7scaSs 9;8,BvGf5RhaMr鋿^U{ۜəקZ#X9aA4ZR6f;JyPaL)/lg΍X&1[pSXR TN)?:l1 LdMhغ8m[k)U+/KcFJrǒy5SEz_~kٜq9抜paAՠY + w蔪WwėEɤ?&!o)n˓Bߞ>RM7W6L]O;ZOuHpS ><;,<%tzH238kE}ܻiDԈ'f畸-x<3O>fxW4sАΚ`VBӛ @({` +endstream +endobj +343 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 552 0 R /Subtype /Image /Type /XObject /Width 158 /Length 1733 >> +stream +x{oU hb۝9gsYv̶@)-@RP4M%@@Ukh\@ʭ F   cBR9;B3I<=3gl(Q- LG5b<]ٲ~({Trh rC3Cs)-<] PޛNxm ⮲jy9y05n +ʒeTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*R5u7>mn?s՟+]i]}#>ZWSy3Do+J&>voXnנ*Twx~=5 ES˳#>c*g74pK8?vf:{TiɼqඣY.Deon?]#أ1GXcز{DոVs[wpQդ'LWKG'Ψ^+KhVGd֔]_n:L*XL|s7{pdV-DjL03,QlYB9EEMͽp4~\R]W +nKHm +Y_ul?04l?qD"J46t V^eutosFP(G36ᨦTfLadrV[L5Oߜ<6dpHr LWS)hZU0ݲ-n<~ew,AS̐}9,;zjv\R9t;|>ae7 jw7scaSs 9;8,BvGf5RhaMr鋿^U{ۜəקZ#X9aA4ZR6f;JyPaL)/lg΍X&1[pSXR TN)?:l1 LdMhغ8m[k)U+/KcFJrǒy5SEz_~kٜq9抜paAՠY + w蔪WwėEɤ?&!o)n˓Bߞ>RM7W6L]O;ZOuHpS ><;,<%tzH238kE}ܻiDԈ'f畸-x<3O>fxW4sАΚ`VBӛ @({` +endstream +endobj +344 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 553 0 R /Subtype /Image /Type /XObject /Width 156 /Length 1650 >> +stream +xkOWPi3gvwݝ3R\(Xo^P, +TJb 1TRۘ&ZEMlbcDmڪJjӯV`A迿'ϋ f<3g302.}Ko~l^P+P7NrYk0wBl* fkXMhӊuW9KhźͣMEMW9a ]qM鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦.=}`]WnھihltyǵhrZNҞ.kf޹i<|Вn^Y3ӎRT,pv#4 ӝnvaB2! !uǸqɸ;Bxo YY[ߢ鬺ogrk&=?Y:̈́ʄy^wW +%K,ָ4RMk+ml=w n8jx+K'ԅ 3tZvR2&W4ޟ~#]>)Y]x1Kn-^|b%5* [4q;;U^=nJ1[Mx1qSjdJU<~\QrT4C2禧BiYR0f{TU⦖LRTʶmh:w~}+۲xn5 /9v㞖tj/>gpEͱhɬny:uuqS.yʎ"Ï"o771f LZSrG*wu 3-WF?®o*t,p3EQr1ӒR3T,J"afp.U:hksw7-71pT*̣ܝyÿ֏ʔ/سkzx|d)4_#Y̝ڂX>aS6^ɧx<)[Ngu)@3+z^1RyFkr3W,w߹UP~Gmgˊz^U%@21ݖ|U}_]_{>=,[9(8 +A,iᗝӰg 7n]> +stream +xOGA̽qB-1PJ*jJ,*BT Z 41mk֐6FFcifp+~=̽?wvGV^s;H"΢9\hTqeW%V\O`Ya#khS2p|t[*يiԸw S;.< {Eaɤ!(Ƙn4xr!GTUIc6dOosߛ.dcѹw4,,c }TUՂ-&/ +[Wt6~/7dM0Ε90hdlhdKk[jnSÏIrt.3\4x/%-'|o7ޭ 3 LZQOۺH 3 n5\/ZjMe֭ӈ$}s(J*`8ZXjf#]s`0eIv֜VYwox펆ϛKYlb0`"G"<~§XaUSg?Ppd\}Y-F.{kEP,K\UqW=[蔗>k=}x]U.ŲVhe~_EbY*i> +stream +xoUxjٙ3;slggf-EDIrIp Ri\ZiPi(FH( i JJ#4C%)VnKiew~~?=3g6㎧pa͌- jdj 5PS2L_`S[&?[j*^\Yߴ ً+7-Cv)1єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єh'N;ҹm֍;*_rkk6lܺm#'Ngoo}os͢+vwhg/YLm;YTtwi=sjCH =%pp4 q[v ~K8w+RU^>z՟zzP%C஗,`deQyFWz;̴Y"pG36ԯ =74~ow4ُO<*w b " +%DB̯|d7<hzv!VX4)'Sp#*ْnˆp[YǍ''jh߼sqO5tn.cL5 +Fx*&,ٰ#qSe<`&BM4{;L9pn*ozc,ߣ'˲Օٻ8m漊h:?l6<ђ9]%ʺ~ꪗ6^jkQ?J<ʝ̺:)w9 f z!mR=P1=H`Vb$5;Un" hz*MgKtxovYNL1 +9Z$)\%(zһ G5MUsh4)]1kڢ|ypkx}޴]jeM+=`"ɄfGW=NRw9VYs-iz3M2 u>YNy?R>7k{Vdm؂C2 *Ҹ5xMn=:vkc./^Z啬UPfGH-J=xA[П9+WW9-Otщ?$5n=* +YWvNgY]._K?/wj9tx3 of̀>I|牷-Z¥5_^bMM%S +;Pw0xAꎢ$m޽::jJ9@@Qո'Ml= +Ye`h[eUOxِe9+qf7ĝN\j[e"$UݽT˲QP`˪zQRt0s +endstream +endobj +347 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 120 /SMask 556 0 R /Subtype /Image /Type /XObject /Width 394 /Length 160 >> +stream +x  Om*. +endstream +endobj +348 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 120 /SMask 557 0 R /Subtype /Image /Type /XObject /Width 40 /Length 387 >> +stream +x݊0 rbɚ Ƞƺӓ=j ӆ8D!BUvWQ5w=.\p… .\p… .\p|\_-dʡ!'0 {3}xBti'.qUw+?C!yB"ڶ6K8y4wi*3_2f.z19Z^5xKfjy;3cJEhgRj粮RZ{J.9rwlYH Wk;kpHj^=9ɍ1ꦭuzs<39sNή39Wr\Q1VvOQ=+nk!B(/*|5 +endstream +endobj +349 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 136 /SMask 558 0 R /Subtype /Image /Type /XObject /Width 40 /Length 349 >> +stream +x yr01m!BɺD.|S-?!B]G=k_ .\p… .\p… .\p… .\p+c>ƍ1 5߸D$"5nY!"/Q"3s~32y>2{Xyvۮ\cvmvm !4DFVNíNUն.'BWT?~=4ߊ7pE1\^4iŃ|18m˲v<<`)km~Xkuc4WSs9Dyu銈;yB!| +endstream +endobj +350 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 544 /SMask 559 0 R /Subtype /Image /Type /XObject /Width 313 /Length 5596 >> +stream +xr7πB4fg"Ⓕ?g"0]׏N- /?ezr\N2TYk!t<4jrIU;iG0nq%[exeK)[<{5ϳljZ)f|9]~n.j%g<$GdV2eG;.)jѫ*?4g,މՁF͠v{z;aqAfa(؁I,]SJ};<qh6Oyy9RǣWuH~>Ίiꯜ{Q嗯TNx9RFb= cgaHUķlI饌R{ yF+T]"^ [r_?-&%CUp843fiBst:IQ(WOC9M)@|)%9/K"mRJt:| 7Z bYս1 ɾ~֎ub *#>Vmf C4;[dD.l6#=1 QrQbJ&=hF",v_v`Yjgwl6^FO=Lgv WK\C9vjBι:h[;!8ʱfQw)NIEMq>T~?Y$]}NNޑˌP{{.@/)Z Cu,zOt:})@2njjW=^VɌCӤ%//FraF{T^4 /P%U %)w~n}} 3O%CmAR*/SCwwh~$m^jy」Kux*] +ı,<␋lv<ՖޡEP_J8'MAENglv]ꕽv> 2JʯbC|'ށxl0iGX +4cz:&PJQ]TqRRJ6I;.dk4+@(id /}moz 22G*Sx<ک/5@@;3Ah5q HRv9:>JtBiĤIl.*X쓋RKGhR/C*4oZ04-YRJ< +D#O*iK7B $T% |y*-ВT +'^f~W#T@Xv˗TbJ)~-{ +@k@L8^y + 8< Ӝ3]h/ةo5RJ}[Rr͉* +DIli%!_ fHeT/wPZ͆h6M0 7U~iw8bghdqE"'Ue*2U9jR!(l k]yUJ9Ahپ4}VBG:Iy@ V)E(@@(Y"(Puv˞Ӳ9wDVh3 lR5*I3T4=k>H AVR p +J-4圽В-P@@;RhQ,TdSuG솒+'UꕽK>ϳwPZZ4}b-wD^R*W,T$uR$yZkU p}FVRy:% &w**SkǣJ1圏ǣ]@@0Pk/p.,T*м#¦LIU `NW91ٽ_(j?g80Pll41u] J15JU! 4BөTbj֪tUbjR% &; +dGyIU f:ϳwDVg?_?jHU &j ^nqY L`'*`. 0- J V3eTb"U@wT +R *p<%Ub4ܬ.Wa p(4M<@p8RX}n+*f+uwDVÃwDV4EK)XaGZ+e@LM}Rgk@0p}UFU v9Kln6TA1*j ZwDVP ܅ +Ԥ*;@L9 ʀd+nN'jvgt:gRJ;"+8MUvBh\@pyfh@p*A`j5RwAS +!4OUȚv4RВv9=h4 + ]ig<ϳwPZt*tNSM` _irŵ8 tҗXK)<{[/zllϥix6U52 CJɶ;(<g/RjYsu)% $krF~>D%dx +@ +gD3c0K@LK1u]gSU/Eʍ Lus ڭ0 qh{: .䜽t*4M9g*"rzDa{.;(+tޫa_?ieb;qr9nG^eblR;T7Á=a Rm{S4ubs ao(l%@dzI[ M_nZoRi@@RdSi@L]5AhGt` &-<"m܋sK9^wDV,kAh `oZvt>:0 ;4l]xwh^ͥv.kg@46Ue[L@4278Jw&m2 2y(KrV7_Z + +Pd#N q,x~/]Q'2wDVK[@d< 8@pT\._|I)Ͳ4B + ѼVcU@(֔cuxLUH)Q[[h'84bjRQKTHxG`Vu]%[!C-mRJ;./<>>6L2ԀPVd;:lT=ܛB=em/Fι)X&@r󸆭H/_.{izfyT4qhTiRi]N'8e@4!'("' e@@ˢ&);4tT*u0a")i+)%sc^΄%s^PBIXW Bm9r +"J˽&hdڔ #V@@ڞT#k>ZJ9NqN)}Ӂ NO_uCXW' 9f]Cq]3H{~p0 9gy;R?w}v<×EIJDp<sjSDq"`hW;F\.Y.ma v,`eO Vk[RQ,wx`y1ͭ:iu@MɄ)2p9+X6_zcݮU*bݏBT_Q_)/rjSJ_iD}JiɮX>,;v+$Ysͨf2~6(5U-;@˕iHwmfJ1^]܉aM׌H+6 MIisdnJɦm$%G/<8UYfȀm=Z Y>M2U@vuj$Ws)]ijꍞQwcY@nʌOl_lXѭMFm>AK,4뽿]{,M_[j]ҹp!߮*}8Q7YvhI.݌ƕ*gS4ls~͢e}*d;4O/2H9}B3Wom텾fKj&nsWjIU}v1 /vJ.sc×_. 9 /3~sPVP4Kv*kѷXoB.6UyoSY)=U_Y,2U~X);XPZf fwDx +endstream +endobj +351 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 918 /SMask 560 0 R /Subtype /Image /Type /XObject /Width 878 /Length 6215 >> +stream +x1 +&畅a/aa0L2')4Υ6X| +d{3/wc1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1s7?˯U_|_?Ï?廿~Wzg|vgn?;gzֳ7~JIO/ij}ٿٳ;NٳٳJҳy= >$=Y~ޗ_G|}?VeI> ˒L +EIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍v cweC*I%%t`m2G@LIQd$% @FIQd$JI29JI-`@[ˆTKK6鶍ݯ}ᛟ?퟿}_g=Y~:~ӷ\Ξg=[7ϟTJzOg=Yg1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c11dEq +endstream +endobj +352 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 148 /SMask 561 0 R /Subtype /Image /Type /XObject /Width 272 /Length 1178 >> +stream +x?w{Aq;;{;s /ȁ B +""J%UR I#BH.`#bq|Ia0fo5sëǼ[X033333333333333333333333333-OW˥Ae7ZUu*rqL++&|IX +;u2(Oḗ{Eռ/Id^1ݫE'I}_J,{][2o&Nl9pKWn^~Zw͍ YxgeiP5K;I>Ͽ _|utĬ|4aszw?\9֏^'#fOolP/2MoӀsel^VrTA[̒L~C_ɺ ^~qAg}4eXzQrz[Z?x&k鿻L{ݸ=,e^E;{i}_/qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ qz8@^ N/ Z T 2,{y5[]޺5o{y fzIWG_l ;XOWG7,M{<3oV&fzy7oK^_^q3K+̱gmi <^^*Ie-lvw?sO[?Oߟ=\q L-ST[;pK[^ 66/<]e.gkkMgްSƝ[ٛ_ZGY!v2鬴t(wUҟtn^eE5ghYQu*9nS|dq\i^䒡uu,ݼ , JlY7 +endstream +endobj +353 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 166 /SMask 562 0 R /Subtype /Image /Type /XObject /Width 190 /Length 5572 >> +stream +xyTǺ; 2aYYqè(.;FQDEED((A1AH1Dy$(k^yiga`gwx.2Aكq,dXfX EY 9%bC?޻eH>},l끅c?wiWcHMu]>H0sm|sow{ +UgD,S*@&^؟f,7}X0g؍17KJ7CnDEsv8O9!#䤱Sn`pHcGv;ס3nv,DSۋm5cjPg&鑺3΄QZ犠9/iu?Nㄎm'Ӟz犥R/߭[[["O[[[Muͭ윣ۏ9C]! ٘g9 [W-NmֿEoƍ7ܳ\VrńtTN ,(L"%y}F&,~N1a9gy. +%aӰkMtboZnK7o: ^ ?/]uVoߒ7wvJصnmZ߂%i~ݼu}d> +}gzփgs}-dqW;xZp@7~]͕[rwb9 xpvjsugO[[ܪ}{: |PWay'+/+[c24Ʒq4T,59YPlrhfn0{W*D_ ӔmY 믿$uɍii1Q/IJH~⅄>|VRBEc7?ny=UU!o[{i#I%Mb86w.UAR]WO~} +Zz 2ߚ0eTg(%tC 1c~E:;;_65 m'OIRV]]tawu( B ͐)ȶkܨUD,~TUu<5s;)L=K:;;єgƬ[+ŋ7KJ.Z*s$͘5\\hШC'&~ GYϽ=5>ȿdyq/\CbWϝFI <:;;׮Z-ZQWG8$w~r>G0d6G_v= Yv%^ٔKNѮ3*>ӣ3]LNɭCyG8u uCc0IcĖ#iuoJ3.-ё3N>,qID|TIKD;y +6m96r~~Ný`n"vUbfD[^˦õ1lr!$ ? eF8eX {5`n&-ns4 +f<}!ΝۼɈLpD,4 ^O+E%޿w*T #+nGܺ@_ц+x?yy69h{6HT|Ԅ"۸99C'. [d9NJ–Kģl/3}ՒKԡ]Q+J8 +mIiBhAJ(oˑa4Fo΍L}$,F6Җ2T4SO +9aΡJOVoJсMU񭭭ҖUt*BTVq xLTE~nEEQAkOݾۜ6YoK!QH68 S[4g-Nn[ o+u=.NMMMrkmA?r8ˆf8ҫ/` )hO7XHkk6 +/z p>پ]n[@(+l[}BO-uk i|#+:3~Rnhdk^:oyGURبurL΋喺5P[[ձjA[$z%(n]+ׯk +|OpcJ){ģ*-G0AN/xhq10F+LS!Օ*T^Vw~C7%iӥ]ognh\g͹0g0/nmVJmlf4eG +#2ôtbi?؍16-(,IAZ[['#EFܶ &ʹ3Aޞ׆M#4ʹ3MvpȈ]5&OppH#v+fS*X f8~oU3~y;^FEz)}2f_^y%c` +ixzJנ(fhxGK?u-=4'=~}UKAE/.(9ep5=ʶHSaC߱An0(IʭI5#,I<ɳH0?˴Dz&<.XsՂBؒo,F%rf~fTw섹G-녊~r0# $N +3{g|KJH?;Uo1 o9hoY.U4ˑwZQᙹڻMj%%D-jNq޽6pNaRIrbTY%D#ˑ`3&*Zc9v{+P#b1i` QǯrZZZ܄ȄE2'b(%@K?ȰPR9o8'g+fjw. _P$4A 2DnK2h>p\hT_>e,}IwQIB +0JFfȯKfo6y)/y80ړ>>ӱb˩UbѴBw^GU+go׽ZS]"WV|׿Z 6'3EswGŭ| zqxdW){zG]V=47$'=0n6F̐H>~nxjZ{{YN[[[c^H8rTc/"$I0Hh~W؎\KX&.Ŏu(( ð4C q㣢s>{Ͷr={`\[( P,M3 (?%Z̵҄08MVקz 0<n./ 2q,=(yX<-lJulP]fY{'P|=%Ϫ=G#I{{<aT)mltVwNN +zAYU=4׺M$^Y +Rɒ]n>~њ;z;n$Y +2ZyU Il$Q`㚖gúuH?~:;o_x-GnBޝ Ÿ^$V-i+Dc6"91=]x Κë=u!c5.7j,Ƿ_E%"|Ĵ2ZT-hf[VS~׾ +:o f꺡4w`ᄏ[qWܾg6iR9_ݷ-hfȀ[oDWZXȰW7G[יn1:* 7q^Ȱ_\D<:kukhhSvQrpk=%?zcy˦*T }[^~򼘚ؕp1E/^%QV*A<ڲ7łl R 'qXvy}}KssGUo>2&é uՍluuuhȔev[DQ +KINVo`jkkSFt@}4? q旼~+ڕ5lofTG߰o3g^> +stream +x]SW/xnf{"bPt *:0/Sw~C эDV#]f$UeJ^jf7W{g ~þO}*9{ι))>d!˙H/hAEUiHe"11/_QEq,@Hu"J:Me,Le֣тJK>F!Aa\DeVI%-GP7 Ia'f1DZfa4˙B5?NiiNEUW" j$?)/ǟ N+pEe U,*- V/_i-Upmcƺ{µ;B-UMW[VY*-,YR`3N ȗ3'j:%ftKIFzC++_h_=q9ӊgϫ,YbÛ;vxFS֏tRwo^Stnmt>~Wں%Kg+?;=@|n,3ְ-qj.W6n-+_6߻fs{{vvln>wߏ_TWdnq z3 _ >%ڀ)"GC?SG>>*q}\ +.+țwx4R(+SIk e3vaA̛1wnhJqL*~}։?ܼh,A#5SiX6AZ/FvbKp`Y?A#@AMm?lUZ7ﴵnQ,>iVĩ MLF2bA2-zG $x͛9wg(}׉Ո_>и#7cǛ=xܡ1"^&[exq+4#A#Ng!/>#d9#d;58|o =}~}CwU1u֠n4c5Z?$ScLU'G +r+Wu5bq%bWW̳yoIo>H IoF𔼩j;N\ |}y 5kXBHl +Mb=wڈ;|mwx݀F4d|gIk_ra31|w4q┝-'21xDˎPxļM|ЁQ1T]4RTeek b{?m|P^%S~u{+a3qD+xJv=a޻'\? %͹O\~6bJ\>Q#V6Ua=|'#ĥ%}iYĬtvtpScިpd;9?ikHGm~{)_Ā#]xݜWƉ%ikx}|s +qG$vgfhG#RLj +4jų}yv +~~aδٱh2@HݦHQJIJkj 6_QxNì`,pw̢:*q??(v iju-Acx.25O &>5nSeN$Ȼ*Vm[G1;k\D!o5w] 4@yׅq`f٭%c s/މ2־Xlj/uXMM%qwFAZ[#pRR(yS~eQ#_\:~JԄE׋ELQ!C +> fYWGy?^UAs4klтbCsZu9Y#1ab55oymk0%aY98c[@*-}a0@ݮ0z!Qz1PmA{8-'Ng*n|ㅤ}KNJ&n}a `@hm%:Ѳ36G&:H7IyH}ns0 RȏsS1u*[hocD~&;Cp"?-FnًS. >_8}G(|С{=+Ɉ罞W_5S ! G͚444Z8W ,7wkj.op>dk] }uiRFd`OpT[ YtT۶ɶo]%WÈ^zĶCx!nʼn0aw+݅}grfLU,?U׈Wx7l +~})|'ݐG% J9E6 o}ugvѣs~}{֭X4J!NDS# g +%Lԏ}7(X\T/oܹfs'H=;{}?ٸyCŊEs +^8̛tXr<(YvJ>Z2H:c,O,̟V:o+vߺao5nmLί.u[Stnmͱ Ƿn]S.(0W_PocQ9Iaҧ!RƣmkW0dpE7BAeY\Q!@/Dv 7P8pE,,YRUZVX_bU[B;B=ƺo +o +mZjݲʚ`EUiYeɒϟY4pznП@zr M=",j[ex=@z`(TgeidQ4fDyrI/zT\d + tHPQmpɌr؂p 3#QcaO?' 6gݣ9\ݮм:P(@R:B~ە |NA/EF.Ka0~<+jcQ1Y\*GFi> +stream +x yr01m!BɺD.|S-?R!B}Hw?Y\p… .\p…ۉ1ߊ#ocͨ)?%"qMD{YΘ^3̪bq]+]ewCnF;@ۮ*BCakU>tXUmQ8zx:-HB[QV4Q+"W7`jN-3\W1őm[CNYkZkŨ 㦩&տʜsty$zpHWD<#]BSR;X +endstream +endobj +356 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 260 /SMask 565 0 R /Subtype /Image /Type /XObject /Width 484 /Length 1726 >> +stream +xOw}4(AV8gw&j6Uaz@DB\HĩRNrRj-s.zFJ%^B}=8W5 B\fi?>~G3ޞ &`U3rm p&-udw׫q,fd5-֡8u`*fq} A1yv\/O< ^B}uGHͮlvlu'7n޺{zuɍW/`vZg/O +O'|n[]-m1Eq<:jw7+/{?@eʵuծY.;j)ݍoO ֯OBeyU|>}Znqmrco@rkY3/޾^wOwˮZ wN~>:kR[ѻEh5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z Oi5@>ȧ Vj|Z OV[vY3uo߻!}-gM7.|Cwͷ6ME7>?7׽rzʵfݴ[ } a.lP\=~䇟|O y7vP\,Oo.W_?XB> +stream +xkOWPi3gvwݝ3R\(Xo^P, +TJb 1TRۘ&ZEMlbcDmڪJjӯV`A迿'ϋ f<3g302.}Ko~l^P+P7NrYk0wBl* fkXMhӊuW9KhźͣMEMW9a ]qM鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦.=}`]WnھihltyǵhrZNҞ.kf޹i<|Вn^Y3ӎRT,pv#4 ӝnvaB2! !uǸqɸ;Bxo YY[ߢ鬺ogrk&=?Y:̈́ʄy^wW +%K,ָ4RMk+ml=w n8jx+K'ԅ 3tZvR2&W4ޟ~#]>)Y]x1Kn-^|b%5* [4q;;U^=nJ1[Mx1qSjdJU<~\QrT4C2禧BiYR0f{TU⦖LRTʶmh:w~}+۲xn5 /9v㞖tj/>gpEͱhɬny:uuqS.yʎ"Ï"o771f LZSrG*wu 3-WF?®o*t,p3EQr1ӒR3T,J"afp.U:hksw7-71pT*̣ܝyÿ֏ʔ/سkzx|d)4_#Y̝ڂX>aS6^ɧx<)[Ngu)@3+z^1RyFkr3W,w߹UP~Gmgˊz^U%@21ݖ|U}_]_{>=,[9(8 +A,iᗝӰg 7n]> +stream +xOGA̽qB-1PJ*jJ,*BT Z 41mk֐6FFcifp+~=̽?wvGV^s;H"΢9\hTqeW%V\O`Ya#khS2p|t[*يiԸw S;.< {Eaɤ!(Ƙn4xr!GTUIc6dOosߛ.dcѹw4,,c }TUՂ-&/ +[Wt6~/7dM0Ε90hdlhdKk[jnSÏIrt.3\4x/%-'|o7ޭ 3 LZQOۺH 3 n5\/ZjMe֭ӈ$}s(J*`8ZXjf#]s`0eIv֜VYwox펆ϛKYlb0`"G"<~§XaUSg?Ppd\}Y-F.{kEP,K\UqW=[蔗>k=}x]U.ŲVhe~_EbY*i> +stream +xoUxjٙ3;slggf-EDIrIp Ri\ZiPi(FH( i JJ#4C%)VnKiew~~?=3g6㎧pa͌- jdj 5PS2L_`S[&?[j*^\Yߴ ً+7-Cv)1єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єhJO4'M鉦DSz)=єh'N;ҹm֍;*_rkk6lܺm#'Ngoo}os͢+vwhg/YLm;YTtwi=sjCH =%pp4 q[v ~K8w+RU^>z՟zzP%C஗,`deQyFWz;̴Y"pG36ԯ =74~ow4ُO<*w b " +%DB̯|d7<hzv!VX4)'Sp#*ْnˆp[YǍ''jh߼sqO5tn.cL5 +Fx*&,ٰ#qSe<`&BM4{;L9pn*ozc,ߣ'˲Օٻ8m漊h:?l6<ђ9]%ʺ~ꪗ6^jkQ?J<ʝ̺:)w9 f z!mR=P1=H`Vb$5;Un" hz*MgKtxovYNL1 +9Z$)\%(zһ G5MUsh4)]1kڢ|ypkx}޴]jeM+=`"ɄfGW=NRw9VYs-iz3M2 u>YNy?R>7k{Vdm؂C2 *Ҹ5xMn=:vkc./^Z啬UPfGH-J=xA[П9+WW9-Otщ?$5n=* +YWvNgY]._K?/wj9tx3 of̀>I|牷-Z¥5_^bMM%S +;Pw0xAꎢ$m޽::jJ9@@Qո'Ml= +Ye`h[eUOxِe9+qf7ĝN\j[e"$UݽT˲QP`˪zQRt0s +endstream +endobj +360 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 120 /SMask 569 0 R /Subtype /Image /Type /XObject /Width 394 /Length 160 >> +stream +x  Om*. +endstream +endobj +361 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 120 /SMask 570 0 R /Subtype /Image /Type /XObject /Width 40 /Length 387 >> +stream +x݊0 rbɚ Ƞƺӓ=j ӆ8D!BUvWQ5w=.\p… .\p… .\p|\_-dʡ!'0 {3}xBti'.qUw+?C!yB"ڶ6K8y4wi*3_2f.z19Z^5xKfjy;3cJEhgRj粮RZ{J.9rwlYH Wk;kpHj^=9ɍ1ꦭuzs<39sNή39Wr\Q1VvOQ=+nk!B(/*|5 +endstream +endobj +362 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 136 /SMask 571 0 R /Subtype /Image /Type /XObject /Width 40 /Length 349 >> +stream +x yr01m!BɺD.|S-?!B]G=k_ .\p… .\p… .\p… .\p+c>ƍ1 5߸D$"5nY!"/Q"3s~32y>2{Xyvۮ\cvmvm !4DFVNíNUն.'BWT?~=4ߊ7pE1\^4iŃ|18m˲v<<`)km~Xkuc4WSs9Dyu銈;yB!| +endstream +endobj +363 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 544 /SMask 572 0 R /Subtype /Image /Type /XObject /Width 313 /Length 5596 >> +stream +xr7πB4fg"Ⓕ?g"0]׏N- /?ezr\N2TYk!t<4jrIU;iG0nq%[exeK)[<{5ϳljZ)f|9]~n.j%g<$GdV2eG;.)jѫ*?4g,މՁF͠v{z;aqAfa(؁I,]SJ};<qh6Oyy9RǣWuH~>Ίiꯜ{Q嗯TNx9RFb= cgaHUķlI饌R{ yF+T]"^ [r_?-&%CUp843fiBst:IQ(WOC9M)@|)%9/K"mRJt:| 7Z bYս1 ɾ~֎ub *#>Vmf C4;[dD.l6#=1 QrQbJ&=hF",v_v`Yjgwl6^FO=Lgv WK\C9vjBι:h[;!8ʱfQw)NIEMq>T~?Y$]}NNޑˌP{{.@/)Z Cu,zOt:})@2njjW=^VɌCӤ%//FraF{T^4 /P%U %)w~n}} 3O%CmAR*/SCwwh~$m^jy」Kux*] +ı,<␋lv<ՖޡEP_J8'MAENglv]ꕽv> 2JʯbC|'ށxl0iGX +4cz:&PJQ]TqRRJ6I;.dk4+@(id /}moz 22G*Sx<ک/5@@;3Ah5q HRv9:>JtBiĤIl.*X쓋RKGhR/C*4oZ04-YRJ< +D#O*iK7B $T% |y*-ВT +'^f~W#T@Xv˗TbJ)~-{ +@k@L8^y + 8< Ӝ3]h/ةo5RJ}[Rr͉* +DIli%!_ fHeT/wPZ͆h6M0 7U~iw8bghdqE"'Ue*2U9jR!(l k]yUJ9Ahپ4}VBG:Iy@ V)E(@@(Y"(Puv˞Ӳ9wDVh3 lR5*I3T4=k>H AVR p +J-4圽В-P@@;RhQ,TdSuG솒+'UꕽK>ϳwPZZ4}b-wD^R*W,T$uR$yZkU p}FVRy:% &w**SkǣJ1圏ǣ]@@0Pk/p.,T*м#¦LIU `NW91ٽ_(j?g80Pll41u] J15JU! 4BөTbj֪tUbjR% &; +dGyIU f:ϳwDVg?_?jHU &j ^nqY L`'*`. 0- J V3eTb"U@wT +R *p<%Ub4ܬ.Wa p(4M<@p8RX}n+*f+uwDVÃwDV4EK)XaGZ+e@LM}Rgk@0p}UFU v9Kln6TA1*j ZwDVP ܅ +Ԥ*;@L9 ʀd+nN'jvgt:gRJ;"+8MUvBh\@pyfh@p*A`j5RwAS +!4OUȚv4RВv9=h4 + ]ig<ϳwPZt*tNSM` _irŵ8 tҗXK)<{[/zllϥix6U52 CJɶ;(<g/RjYsu)% $krF~>D%dx +@ +gD3c0K@LK1u]gSU/Eʍ Lus ڭ0 qh{: .䜽t*4M9g*"rzDa{.;(+tޫa_?ieb;qr9nG^eblR;T7Á=a Rm{S4ubs ao(l%@dzI[ M_nZoRi@@RdSi@L]5AhGt` &-<"m܋sK9^wDV,kAh `oZvt>:0 ;4l]xwh^ͥv.kg@46Ue[L@4278Jw&m2 2y(KrV7_Z + +Pd#N q,x~/]Q'2wDVK[@d< 8@pT\._|I)Ͳ4B + ѼVcU@(֔cuxLUH)Q[[h'84bjRQKTHxG`Vu]%[!C-mRJ;./<>>6L2ԀPVd;:lT=ܛB=em/Fι)X&@r󸆭H/_.{izfyT4qhTiRi]N'8e@4!'("' e@@ˢ&);4tT*u0a")i+)%sc^΄%s^PBIXW Bm9r +"J˽&hdڔ #V@@ڞT#k>ZJ9NqN)}Ӂ NO_uCXW' 9f]Cq]3H{~p0 9gy;R?w}v<×EIJDp<sjSDq"`hW;F\.Y.ma v,`eO Vk[RQ,wx`y1ͭ:iu@MɄ)2p9+X6_zcݮU*bݏBT_Q_)/rjSJ_iD}JiɮX>,;v+$Ysͨf2~6(5U-;@˕iHwmfJ1^]܉aM׌H+6 MIisdnJɦm$%G/<8UYfȀm=Z Y>M2U@vuj$Ws)]ijꍞQwcY@nʌOl_lXѭMFm>AK,4뽿]{,M_[j]ҹp!߮*}8Q7YvhI.݌ƕ*gS4ls~͢e}*d;4O/2H9}B3Wom텾fKj&nsWjIU}v1 /vJ.sc×_. 9 /3~sPVP4Kv*kѷXoB.6UyoSY)=U_Y,2U~X);XPZf fwDx +endstream +endobj +364 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 705 /SMask 573 0 R /Subtype /Image /Type /XObject /Width 374 /Length 7610 >> +stream +xnGP=BpE$,w=ڐp>HTP=[j9? UTzxMr3O7&}8tS.ieYN4MJ]/uzL9$e/L=&p"ӏ_|:Mn׮Cȗ4`۶.c:Ny/_{{xi"t]$300t]0I)LʻX;Nw:{*.5>&K !Hgv!ƅiqӋ άZI|@lbn{=﵁zx7=|R{SIkS{ 0M q_^^)pN)e4m֎؈i4M2 PJ)?u9sR7m OW,^n!E{S;@KIi*M'<,K/"=>>@؈㏼2#\~eaHߔaco~+/aڶvc[n۶mt%nk耍8mW^^^jlpcIdRxiaRL[V;vFgǻA1k_|i zPDyXDډS;:Ez mێ[`I .y P9taR"DzI&kl~c`M~W/_ GةPPTJoDZvPvhaqKM)%ZǮLE2&*3e Ð_ab ίeLivt{8ZeE٦ (.+~e4% +؈eYfZ(m; C p;~26M@)[ JvPF(VTS;(`;faS;4` 7y5PPu4MPP*/0@)0@Ais(% K@Ymn˲w}w]۶6".J] +؈%< <ϵ#tJ0 #6eriC#J0/_jGlG*Ȥ626"ޥs$sMAۑOq4PV %ZPJ4)\.UmQ|iQ +v~h S;`SoVddEzII&&. +؈Ε(% NOd~+A!7՛Rw.%((GK##^8fibR툀M<<<qdbs%Ք% +VbT;(`#R+Xx ˗yxm%EzaR8NF +kw_e叕2^툀툍RۥA՘˷jlDtڌLܾaah6eoE{%tÕo8y_W躮iZJix<,pxzz)q%FO*W"vL@AK۱:X ((\2#iX FMeYRb6@A+2@)qRZ +؎|| uԎزqWV2@Yq4@q)x7MSalq>< +@A}2&Ε r% R*0@Yq.?TRJyǔao+&0@Au$[abԵJ@A}(ȸ}dRX (%0e~? fkl9S;`;VNdq*77ؼ\.mjlD-jlGzgL툀wIV/u]7R +~|uG@Yڦ (%2L~d2 PP[Gn1|(+݊[3c&ҋj Pu _Rb ARJ40ı ܂Q_rġ0 c#?Kaۑ?vDvT;(`#fo+-"Ni_K@Aӛځ[6cY} qPPF)^Ԏ؎|@Yyz1J (+&}׎ؔM@-K {iۑpySXx 7(n5D[;`Lv_~ɳZe^RAaHo!-c.ߒaz紒˲DF;j@AڃWJS GI`1g K8F+HiC\F (h= 4MS~+F +m-Qm0cy[8 v8vij Pꝵ ߊ(kaҝ 5 CT2 p d@iҭڱc1j W PPA,bۉᏩ5PJ/pw]PPm/Pܪ1p1 P\4Opj p .^,ۑa۹|K +\.˲ p:{e0ԎQ(eNx J/2 PPLa[yIyeYabc,`eϊeLR;`;'۱ʏTcRRz pjlj#{ejljJ@Am@q2 p;rt]cjGlG4_u]W;C[HvDvٲ^ܻ +Jw.KLab%3 8## 0 #ځ7ݘ:NvDv/vDvKwjd4MSuP4M1X֣$}?4M &-&X0@AIE +꺮iH2qT;"`;x<^1#Yqwi<~ +J=~[ և +((oJ PVJ PJjZ]K +i o{wat +:>vDvWb"Ԏ؎c pG\nd^t +˼eN'*vI-ި<<$ؔi ávDv0C툀vՀO}L0q(n8a'.ߒa"̯쒀7q%v8|>ym +I&Վ؎ZUQ +-Rj e^ 8v}Xy~?cljGl~OGHө CLbT;(`#s<+E)S;?q<ϵ##=G0@A'2 P4M)D1 C۶r,"m{<q_oJ((uˌ#$((?6(+6\(.i 8ѴAEQ15P\zLqQ{IGHAc ߄R툀"0 ݮ{ n+׀R,nt:صi]툀vQ\.A¤S$(%=rԱ(.5LCLjGlMTz S;`#7&CJ}jGlG*0@q#$H@Ay ##)=kGlJ9So.vy((E?^mߑgjlDtI˲xҶׯ_w]^wI@Aׯ/// wi^v8v<>>ϑkGl~//pѢa8Vʚi 7"}߶m4#6ySi̍JDZtd25P8vߑ[>ucaψ[.$w4n-8#W<׎cq]׹2X&nV,rt:|X" ݦ<0c1s$o}8yU.U4M2 aMt]ibW<ǦibC7 'Dt#8c, at>H ^XRnT]ѽ3L[R;@.} /Q/)\=Qvݦ>/"^uĆ(FhY48 1jnܦ93BŤWCT^w 彤~U2O2L{Ƽz^|^Z\7N֎K)I&.mӛ#_%maqYwaڶ#pVjܥEjd7DiQJ/v]ZMnjv;έ0YwiII&]Q.w_e.EK?==[rLRz |j2LG{ |xkƽ_92pCh+7oo-Z4M/G ڤEW=>>ԑ>quO}~hXOrќ9Pi*VK(s|J.`!>7S)DmUe 1R~gSyzIۜH ^h_}_;p%ʳZ%jyfˢ$'˷R8{g`ݓU~5.I~6Զ0 Ç +?麗u{nU4iy-aBqǣ -BWJ/!@ij&C9oM>O}ju<==ŃRg qLr$<ǨӦ+ܪmE 4>~CZ_Hk{CoE>_WuiJnY/_CiJL҇2LIՆ(%տOk,^FKa~$o[p)H2.ʶ~/iU\.{{EVmTz]IV һUQ)/QFza4}/\DiOK\ղza|q7 p8Dl/tUXuN?۱t>%'I#}|>cái%Uλ5wj;_gʑEFm6[9τūo_`i> +stream +x  Om +endstream +endobj +366 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 575 0 R /Subtype /Image /Type /XObject /Width 158 /Length 1733 >> +stream +x{oU hb۝9gsYv̶@)-@RP4M%@@Ukh\@ʭ F   cBR9;B3I<=3gl(Q- LG5b<]ٲ~({Trh rC3Cs)-<] PޛNxm ⮲jy9y05n +ʒeTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*R5u7>mn?s՟+]i]}#>ZWSy3Do+J&>voXnנ*Twx~=5 ES˳#>c*g74pK8?vf:{TiɼqඣY.Deon?]#أ1GXcز{DոVs[wpQդ'LWKG'Ψ^+KhVGd֔]_n:L*XL|s7{pdV-DjL03,QlYB9EEMͽp4~\R]W +nKHm +Y_ul?04l?qD"J46t V^eutosFP(G36ᨦTfLadrV[L5Oߜ<6dpHr LWS)hZU0ݲ-n<~ew,AS̐}9,;zjv\R9t;|>ae7 jw7scaSs 9;8,BvGf5RhaMr鋿^U{ۜəקZ#X9aA4ZR6f;JyPaL)/lg΍X&1[pSXR TN)?:l1 LdMhغ8m[k)U+/KcFJrǒy5SEz_~kٜq9抜paAՠY + w蔪WwėEɤ?&!o)n˓Bߞ>RM7W6L]O;ZOuHpS ><;,<%tzH238kE}ܻiDԈ'f畸-x<3O>fxW4sАΚ`VBӛ @({` +endstream +endobj +367 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 918 /SMask 576 0 R /Subtype /Image /Type /XObject /Width 706 /Length 5191 >> +stream +x1YE4K.`!@t'I V" qUGF->^O?|Ͽ/_[[[~?O?~_[[[W;_Oooo?*+*hT|W*h&AE%@r+X˵rrߩqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEJE8V*J8@"FE4*hV"~Pp|EMOШqdJAE qhTЬT 8`"ܮ 4*hTШQ@"f *nWNq'@6ܮT8+FEVEo}?V|'|}˿o?3F^ʟoo_o'+?'?һ߾o?xqۿ}K3 ٽoooETcὋ +endstream +endobj +368 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 577 0 R /Subtype /Image /Type /XObject /Width 158 /Length 1733 >> +stream +x{oU hb۝9gsYv̶@)-@RP4M%@@Ukh\@ʭ F   cBR9;B3I<=3gl(Q- LG5b<]ٲ~({Trh rC3Cs)-<] PޛNxm ⮲jy9y05n +ʒeTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*R5u7>mn?s՟+]i]}#>ZWSy3Do+J&>voXnנ*Twx~=5 ES˳#>c*g74pK8?vf:{TiɼqඣY.Deon?]#أ1GXcز{DոVs[wpQդ'LWKG'Ψ^+KhVGd֔]_n:L*XL|s7{pdV-DjL03,QlYB9EEMͽp4~\R]W +nKHm +Y_ul?04l?qD"J46t V^eutosFP(G36ᨦTfLadrV[L5Oߜ<6dpHr LWS)hZU0ݲ-n<~ew,AS̐}9,;zjv\R9t;|>ae7 jw7scaSs 9;8,BvGf5RhaMr鋿^U{ۜəקZ#X9aA4ZR6f;JyPaL)/lg΍X&1[pSXR TN)?:l1 LdMhغ8m[k)U+/KcFJrǒy5SEz_~kٜq9抜paAՠY + w蔪WwėEɤ?&!o)n˓Bߞ>RM7W6L]O;ZOuHpS ><;,<%tzH238kE}ܻiDԈ'f畸-x<3O>fxW4sАΚ`VBӛ @({` +endstream +endobj +369 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 578 0 R /Subtype /Image /Type /XObject /Width 158 /Length 1733 >> +stream +x{oU hb۝9gsYv̶@)-@RP4M%@@Ukh\@ʭ F   cBR9;B3I<=3gl(Q- LG5b<]ٲ~({Trh rC3Cs)-<] PޛNxm ⮲jy9y05n +ʒeTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*R5u7>mn?s՟+]i]}#>ZWSy3Do+J&>voXnנ*Twx~=5 ES˳#>c*g74pK8?vf:{TiɼqඣY.Deon?]#أ1GXcز{DոVs[wpQդ'LWKG'Ψ^+KhVGd֔]_n:L*XL|s7{pdV-DjL03,QlYB9EEMͽp4~\R]W +nKHm +Y_ul?04l?qD"J46t V^eutosFP(G36ᨦTfLadrV[L5Oߜ<6dpHr LWS)hZU0ݲ-n<~ew,AS̐}9,;zjv\R9t;|>ae7 jw7scaSs 9;8,BvGf5RhaMr鋿^U{ۜəקZ#X9aA4ZR6f;JyPaL)/lg΍X&1[pSXR TN)?:l1 LdMhغ8m[k)U+/KcFJrǒy5SEz_~kٜq9抜paAՠY + w蔪WwėEɤ?&!o)n˓Bߞ>RM7W6L]O;ZOuHpS ><;,<%tzH238kE}ܻiDԈ'f畸-x<3O>fxW4sАΚ`VBӛ @({` +endstream +endobj +370 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 579 0 R /Subtype /Image /Type /XObject /Width 158 /Length 1733 >> +stream +x{oU hb۝9gsYv̶@)-@RP4M%@@Ukh\@ʭ F   cBR9;B3I<=3gl(Q- LG5b<]ٲ~({Trh rC3Cs)-<] PޛNxm ⮲jy9y05n +ʒeTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*ReTEY,UQ(KU*R5u7>mn?s՟+]i]}#>ZWSy3Do+J&>voXnנ*Twx~=5 ES˳#>c*g74pK8?vf:{TiɼqඣY.Deon?]#أ1GXcز{DոVs[wpQդ'LWKG'Ψ^+KhVGd֔]_n:L*XL|s7{pdV-DjL03,QlYB9EEMͽp4~\R]W +nKHm +Y_ul?04l?qD"J46t V^eutosFP(G36ᨦTfLadrV[L5Oߜ<6dpHr LWS)hZU0ݲ-n<~ew,AS̐}9,;zjv\R9t;|>ae7 jw7scaSs 9;8,BvGf5RhaMr鋿^U{ۜəקZ#X9aA4ZR6f;JyPaL)/lg΍X&1[pSXR TN)?:l1 LdMhغ8m[k)U+/KcFJrǒy5SEz_~kٜq9抜paAՠY + w蔪WwėEɤ?&!o)n˓Bߞ>RM7W6L]O;ZOuHpS ><;,<%tzH238kE}ܻiDԈ'f畸-x<3O>fxW4sАΚ`VBӛ @({` +endstream +endobj +371 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 580 0 R /Subtype /Image /Type /XObject /Width 40 /Length 362 >> +stream +x0y , +gb`@Bܺ.&.CGQ<"DDDDC=Vg}ŽI\1ߊ+wc~J1;7?7QY{ӵ~Y/̼`[ !=f_%hcBEd5rOvuZuE$lA@a[jtr 7ݳ*\U=!iڹzZi0sy9i%xnjrk\9WJ{kUU5cOiT +endstream +endobj +372 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 162 /SMask 581 0 R /Subtype /Image /Type /XObject /Width 40 /Length 362 >> +stream +x0y , +gb`@Bܺ.&.CGQ<"DDDDC=Vg}ŽI\1ߊ+wc~J1;7?7QY{ӵ~Y/̼`[ !=f_%hcBEd5rOvuZuE$lA@a[jtr 7ݳ*\U=!iڹzZi0sy9i%xnjrk\9WJ{kUU5cOiT +endstream +endobj +373 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 122 /SMask 582 0 R /Subtype /Image /Type /XObject /Width 206 /Length 448 >> +stream +xi"a!DA'7,)D olfZM,y9<|//ffffffffffffffffffff0noR:vb f?mX K2_᧴t}ΗOi ]o]oJ#4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J4J1{iz/9J;+vlJ[0df.iiz\6e6Vۨ~/M;Ӕffffffffffffffffffff 4R +endstream +endobj +374 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 98 /SMask 583 0 R /Subtype /Image /Type /XObject /Width 98 /Length 1069 >> +stream +x;oALR +Z(iq+N! (P 'At: +B!A"("xPَ|;s|9~~͞ 0 0 0 0 0 ә9 |$xUdU#s,)1ڱz +U:ү?Hmk5 eEZ!jf # +'Sӣh~5ϸ_WWa3op@1$Y fR4&1Stt;u2\&*ʛQTdEU"IK9uUK.ͮ$]1fzďK2XiR=AOGmDU)ʴZc'2On[8%(ʧ wґn#7ҼrndE<(}k{c c]{_~݄S-!%ptu ֓EIM:D>ň%[<{)^ '甤+ X4aaaaaq$ +endstream +endobj +375 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 98 /SMask 584 0 R /Subtype /Image /Type /XObject /Width 98 /Length 1069 >> +stream +x;oALR +Z(iq+N! (P 'At: +B!A"("xPَ|;s|9~~͞ 0 0 0 0 0 ә9 |$xUdU#s,)1ڱz +U:ү?Hmk5 eEZ!jf # +'Sӣh~5ϸ_WWa3op@1$Y fR4&1Stt;u2\&*ʛQTdEU"IK9uUK.ͮ$]1fzďK2XiR=AOGmDU)ʴZc'2On[8%(ʧ wґn#7ҼrndE<(}k{c c]{_~݄S-!%ptu ֓EIM:D>ň%[<{)^ '甤+ X4aaaaaq$ +endstream +endobj +376 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 102 /SMask 585 0 R /Subtype /Image /Type /XObject /Width 108 /Length 55 >> +stream +xàS_U| +endstream +endobj +377 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 120 /SMask 586 0 R /Subtype /Image /Type /XObject /Width 240 /Length 106 >> +stream +x  OmQ +endstream +endobj +378 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 222 /SMask 587 0 R /Subtype /Image /Type /XObject /Width 1556 /Length 2335 >> +stream +x?{P8ݝyg`D\K [\a#RtICg&,9R^B + i;/>tOɌ>~壙nmI$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Iɶ=YLٲ)Z:Ԍꦨt=YW-v{V{hn8|= ɪqݎ}.HO<]Lfَv0k(Ώ楣_~ K'oz)ێɲ(o^|4?_mv4]'c7/^8ޅWN٪th}Gv8ӫvUqWW|]~޾yj;*fk7~ͿugQk;xގ:+vv@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; ؎HlG$#v@b; i;jlGQv4߽~vߺ}fT/ߎ.N.Sg;ێ6߬]tCL.]>pvvW>_~Zh;ώ棣__|]okhXwv4];?~ڋ揟ݽwA3w۟_p8j^,wz86[_Xtz4Z>0> +stream +x  OmQ +endstream +endobj +380 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 545 0 R ] /Filter /FlateDecode /Height 126 /SMask 589 0 R /Subtype /Image /Type /XObject /Width 393 /Length 1186 >> +stream +x۽u 4 Yٙewg7>p !>TQDDF1Dh*6b , \$+>.-I$I$I$I$I$I$I$I$I$Ivb|emӴfe/̀"۔VuVgk<ϻޤA5t4 xK]':l_ItK/zpsA5X|ifIdAQK{KKKY5+%/gʺ_Mܥh7wCO3\(og_,܄~5MVorø&ݺ;֯7o^co;mK$[o>v?:? k5;:m8iN.CG/oOD4MG6v~@44u,F-LSVrϼF48ڬh+1Ml.zOs/m+f+I  :? |Yů[yI.4p4eiB0M@@ 4& dLi2M@@ 4& dLi2M@@ 4& dLi2M@@ 4& dLi2M@@ 4& dLi2M@@ 4& dLi2M@@ 4& dLi2M@@ 4& dLi2M@@ iMpowOS4n.zÏ:? 'Yq Ӕb\7lmVb(iw[o.qOD :+'+E4axso~9\4-ޚ񠩩n?n:r7:? + k9~փtJ48|mOwݺǟ8KKvmmZ8U =+Ξ?s{Ow܄[]ˋ%xLe-O}Zoo:[r`^#?Ť_Խ?geWuO`h$I}kڥz4)i9]Si`6e0I7I$I$I$I$I$I$I$I$I$IAa* +endstream +endobj +381 0 obj +<< /Author (Administrator) /Comments () /Company () /CreationDate (D:20250831165616+08'00') /Creator /Keywords () /ModDate (D:20250831165654+08'00') /Producer () /SourceModified (D:20250831165616+08'00') /Subject () /Title () /Trapped /False >> +endobj +382 0 obj +<< /AIS false /BM /Normal /CA 1 /Type /ExtGState /ca 1 >> +endobj +383 0 obj +<< /BaseFont /AUWCNV+TimesNewRomanPSMT /DescendantFonts [ 590 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 591 0 R /Type /Font >> +endobj +384 0 obj +<< /BaseFont /ESHXQI+TimesNewRomanPS-BoldMT /DescendantFonts [ 592 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 593 0 R /Type /Font >> +endobj +385 0 obj +<< /Ascent 437 /CapHeight 437 /CharSet (/comma/period/u1D435/u1D436/u1D438/u1D43B/u1D43C/u1D43E/u1D43F/u1D440/u1D443/u1D444/u1D445/u1D446/u1D44D/u1D44E/u1D44F/u1D450/u1D452/u1D453/u1D454/u1D458/u1D459/u1D45A/u1D45B/u1D45C/u1D45D/u1D45F/u1D460/u1D461/u1D462/u1D465/u1D466.alt/u1D6FD/u1D70F/u1D719) /Descent -11 /Flags 4 /FontBBox [ -342 -238 1011 786 ] /FontFile 594 0 R /FontName /XTPBXO+LibertineMathMI /ItalicAngle -12 /StemV 82 /Type /FontDescriptor /XHeight 400 >> +endobj +386 0 obj +<< /Filter /FlateDecode /Length 592 >> +stream +x}Tn@+fH@B/ iIV{5@,mqkmr.Ӟzyu3뙳V}YMkc2Ȳ{2xھvcpmʢ{"}.GE< (6>١/8Ew&BFS#&9DeVL[l>},ʼh&ˋE]*$m'/X%a5W1krM40nV,GYu'ȯ>)灲Sq:7`pm 8V|# } +gz5@0uZvq}zֳC\>h7!uoZ3b;MC룖T-րuU#K`!zKaL +endstream +endobj +387 0 obj +[ 557 616 667 526 457 664 673 280 315 637 519 804 666 668 499 668 555 454 544 634 597 858 628 552 578 486 478 389 489 401 314 499 519 276 259 486 266 783 518 447 489 491 357 353 307 521 391 688 475 503 436 636 668 621 648 691 594 530 670 687 693 478 522 472 458 450 369 402 490 447 258 506 494 451 440 391 525 482 410 521 410 455 546 469 584 630 424 421 507 507 537 454 642 494 539 519 494 494 276 259 381 631 470 511 540 338 462 448 481 425 473 438 469 469 516 465 465 465 465 465 465 465 465 465 465 220 220 ] +endobj +388 0 obj +<< /Ascent 435 /CapHeight 435 /CharSet (/comma/u1D436/u1D440/u1D441/u1D444/u1D456/u1D45D/u1D45F/u1D467) /Descent -11 /Flags 4 /FontBBox [ -342 -238 1088 786 ] /FontFile 595 0 R /FontName /WTHKGI+LibertineMathMI7 /ItalicAngle -12 /StemV 82 /Type /FontDescriptor /XHeight 400 >> +endobj +389 0 obj +<< /Filter /FlateDecode /Length 590 >> +stream +xڅTj@+fxFoc^`<ͲW[;[z5lA}Y}P)79ve&LFc.#ۮ'&9U P˪hoj a[B򲩎\;v}{Uk[ȶNlH"m9KbXX!t7H;{wm$T.Fy]ȮW'i,9_e Y_΄3&.񰩎z<)O"J@X`b'Gn.Db Lj0r`yfJξA5.* f# @Uhk2&}{HYѝ[*\q<1Do8bSĖt +w;z=H0ωu^N y}3|ѳpS|$mS9 BBu?0sq|s^9o[w_|h[jӊܷTZ_kf5_ +endstream +endobj +390 0 obj +[ 669 720 566 492 715 719 304 270 678 553 848 714 732 540 732 601 506 578 684 637 923 681 594 618 535 524 431 538 443 336 501 563 307 288 519 292 863 574 494 527 543 388 400 351 570 438 741 515 545 468 680 732 662 703 738 638 568 725 736 761 680 570 515 499 501 406 446 537 492 276 549 531 487 478 433 569 522 447 571 444 493 602 504 631 695 509 457 562 565 588 498 708 515 539 519 0 575 289 286 414 680 507 550 540 338 462 448 481 425 473 438 469 469 553 515 505 506 512 505 506 514 495 510 514 244 238 ] +endobj +391 0 obj +<< /Ascent 695 /CapHeight 660 /CharSet (/A/C/D/E/G/H/I/L/M/P/R/S/a/c/d/e/f/f_f/five/four/i/k/l/m/n/o/one/p/period/r/s/t/three/two/u/v/y) /Descent -237 /Flags 4 /FontBBox [ -1033 -247 6300 896 ] /FontFile 596 0 R /FontName /MYJMNU+LinBiolinumTI /ItalicAngle -12 /StemV 80 /Type /FontDescriptor /XHeight 429 >> +endobj +392 0 obj +<< /Filter /FlateDecode /Length 881 >> +stream +xڕUMH+z#M`"RFMh[dC{/t<=$[Տz +hn<0~W7ua<} ?Cُ~]r> av ߣdiB!rԧOE=dt^[չpqjGY>^hmG;vnZ7lުMl7t\31Owsu"`cl"* + /D=B )M)EK3<655`ȄCni0AKSq-M>0ID^kC\ø8&\#&6:z 2f9׉_poluz \s֘ibEݰ0g#3ckxfuԴ5N'#d#kT3L:)S#0yz1k;`Ԑ> +endobj +395 0 obj +<< /Filter /FlateDecode /Length 844 >> +stream +xmUn:+Et=%@m&֑\d2ps,E61O?7vxE<\ooStscrַWcG?9$ۥ׬Jk +'k3w6ϗmDS7EşxP_9TJq#.RZ]3O(~|?Ox߿ ݝ i|'_tz!dۉֿaG/qyz?yY1fhthx_}t'N9|3.y~=W*! ' +{r%⚀R@2%eByl@W(# ICl@F<~$ <&VVc&jmX@) +04P#3GfN7&|3r1)YH W@X2^DL+Q+X!F!uE*KLuHN W'NY' ؍902aWL9&9(%@{Z誹#{Vص8k8FHj'n!h!CgW)lIE.3z]lcgwEژ> +endobj +398 0 obj +<< /Filter /FlateDecode /Length 732 >> +stream +xڕUn@+Ha!$ABJ( +!62FJsT]$ sϜreOzc}s/ū=֧&~}}nn:?m>Y[آ?=>Η"[Te{ċ*ߝ +۫/ڷHGܮOˍmڲ+mUY۴կھ[_"ʪlwk©'ՂͱUEZ17mYMךؠQOiQyYnp^~[_T惺Maz,O΢>!XvR?V 6`&[q'y]af]Yo$Xg⟳=6^90`4|!v2a@@mLĎ0s&F!ܣ {Q@R }Eu.MLq!f a<2ʖ!c0& SQ.eP58rr4Kgx:"'1jix43ԩRF94ZD3q 8e8̸t&&q̹ %7ʘ>b}8Fs%0t?b9\lċ>ËMy;㋍%Nv;.Yzg]sg~Qg(g[Kx&}{꾩dx5jMxZ!{=zݗF_vvy䧦q"-;PE?Zr +endstream +endobj +399 0 obj +[ 465 465 465 465 465 465 465 465 465 465 236 236 288 550 435 435 895 ] +endobj +400 0 obj +<< /CreationDate (D:20250908055835+00'00') /Creator (Chromium) /ModDate (D:20250908135922+08'00') /Producer (Skia/PDF m125) >> +endobj +401 0 obj +<< /BM /Normal /ca 1 >> +endobj +402 0 obj +<< /BaseFont /AAAAAA+TimesNewRomanPS-BoldMT /DescendantFonts [ 598 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 599 0 R /Type /Font >> +endobj +403 0 obj +<< /BaseFont /BAAAAA+MicrosoftYaHei-Bold /DescendantFonts [ 600 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 601 0 R /Type /Font >> +endobj +404 0 obj +<< /BaseFont /CAAAAA+TimesNewRomanPSMT /DescendantFonts [ 602 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 603 0 R /Type /Font >> +endobj +405 0 obj +<< /BaseFont /DAAAAA+KaTeX_Math-Italic /DescendantFonts [ 604 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 605 0 R /Type /Font >> +endobj +406 0 obj +<< /BaseFont /EAAAAA+KaTeX_Main-Bold /DescendantFonts [ 606 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 607 0 R /Type /Font >> +endobj +407 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 220 /SMask 609 0 R /Subtype /Image /Type /XObject /Width 468 /Length 3395 >> +stream +xݎṂ2LŒc-ei n]9ڄsd- ׯCfLKdg!FCt3ˋ^y׸f,<6-ҿ ]wT8!YOlcҿ u4K5,gWeT$(J 84>˰j4MZ8?*I>FW ^ Pp2mV#`/2\dν~4TRn_}#qJMdi7W @bjȕ?c/lau7+ci4M9B|C3=}_WrE hVQ {2]&-,jVgvhdH-tO۶]cT/FUUcVѴnIH|β ^NWB 1Qpxx\IG.^y{{+}F8Z8ft%Bf  y}}MsVJ1Zu~u:4'gh2x:オkY֣J2R5GHL'M]ץO G%8Jeu]ˊ4u5N 69Wl}r-BRaP&sXoZ+.lu9 2Fr ]i`teO**z@86k202{vva1WYɈﳢW_Iy*k].a1Ќ2.tK9}Q2ZҴd 72f5N$tx{뜓aO +Gd+_.^lPpI 4B{KGXIm5car%v}ƶm%̮uuY"~ʪŲb8j5oyf0 {y{{k&M>,4MM軳1 [fRe.hu8{` k^t2' [@ iWVH|_&n,n)zL8r+4ay]&vUUcfc۶^Ҟ_&戗?>#<Bxrb/lg\.Wޠ^64c h12Sm@Z$y{K+x[V$Sń~V"K4O %ub#XK*z^8+VJ`/2xayL)}X8 Q7h麎ݏ?-W[$\.ƘgǏ  6Z^.4;#ty#i,]moƭyUV/!k|ј.KVOj^.҇'a3j@e^N_YkucM322]b\rI]b5f^gԕo]ZQ7^u]>2ރ.Mc0KڶMJPG\~5+UƘqΥ\JW4]V+|\x +{܂~;MieGl +`WUU]֓]n`2t:eu 1F<>2X2FݤM3ԁT&.1: Gm!b/l$ic2jssNo37u!2?'{Ol7 yӒ0)ӄC]גa |)^hasZ4]؋ Si6 20ck۶oͲ} ۱۶]ۍ^^{- \px45Ms X>YG]28 (ѓFtS0 ՃI&!@]󬉅lEN1{*`/%M1^n1E &UIx5[ 4JJa9quk<4؋ab{-ڶuIYj0v&۶]mh,[plW" r]r @ -rmオ6cd]"_k Cu.҂l\9)/!-}"x!!i1Ccx+=AKn]9b/lGK+Mdo.z^80XX^HJ&u56GȢ҇ëiM^/l7isRKF ۷ou]ޗBpΕ>&<˶H06;M ,W_gPE +endstream +endobj +408 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 84 /SMask 610 0 R /Subtype /Image /Type /XObject /Width 188 /Length 1054 >> +stream +xn ER )Tu6)ra;6dkVOq`/""""""""""""""""""q8BB&wε.zR)RxUJ9R.rέK94.)S9qfCP䜽'3SJ(}1,O55c;vXA,sJSc2u{/qR1!RUSU%13{3y0/ͥӓn&<PN#lVM$)udi0ڠx%G>r{/X䜑n1@Ɉ%Hxn8UHBz^KE>_M/ZAOg..sک1-\9+G\1x9זMWrC1Ft1B!t)̹Y]ׅ:YN-Q cblP02I%ScĴ96$5 yJ):fU;fCC/l[zbΡ̹Tqz|&8c\rJv53H53)tĽGrqkmQWAzo!$8:<ڵM>z;3xRōn?oQxTt1ʊ-=0|||V%߅K?vg_VWUanWZLOs\x :ficTŇÁs$ayS.]'5!=^g1#Jjڮ*rXjK^> +stream +xàSU S +endstream +endobj +410 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 137 /SMask 612 0 R /Subtype /Image /Type /XObject /Width 155 /Length 4179 >> +stream +xWG" + + -hFF\%5Hx;#AHT~sLmh`s颼ߩoݺկ`0 `0 `0 `0 `0 `0 P  +p.h(ssEmyk/4wU"Rz!Ѿf9;MY3oVm(VH8`0HXYHrG5y4LO?=re,]&G3t '5>V%zU`؜$Mw腬Doh(j]|Ey;Fi{L?Ҝj<=0hilM=˓'/w%;ǥ%%~uxߞ{w%'%[:~k׷^-f14*"$!Ux.o|Pv&˗?ܼi?8uiӡ>n_hok˽FwҾ+(ǩ/}Vq{*nc;R^mt1l[LG M^˂3$LF߻{OoY6kβ +nަKA +e46Vol ?1{ڭ#LPCP:tHo'X4"j1yuw!d40@o~wsAk;uuuq) {ޖK9 VǸv*xGƼhgEaܿ6d alzw!J&Uӱ?SCpyFDN7 T0{Vf566^t97'@޽#B]xpv֡Cu?z@g0 6H.^i 6~MccfL[jٝW^)o14i 9[Ξ>=-hIԀ?S[[lJz,vt!9{Sh\jv4%<,$5%>5%ޝ*՚#aƍ-+-VFoߜ#Xޜ<~BC۷k)8g=Db)S &ڕ&MwP35% $Sy6dj1`Zmŵ24^4x ~m^AT=&*JZ>5%>KRzZqp^4j()>*#VCkgBN)(Assr41Ow7KC1|0FGDJLӺ3?khd*.*1[m~SV>ׯ+g[=y]p;cG*ѼGZVɒEoݪZ84܂٥#mIINVVt2m$9F wV ܙO477kd2mXyq[DA4d2ٵ а;wh˗7$^,;@_:sF"$ h[mf/^TX/O~Q=E + +kY +VW{j؈I|řE =@Sb   xuFoۛ|̧! 7"-Us]BՁ˗.'e:L-Hw/>͙+E%N h^DGmW8 =TpjB)n[9v2ۜGnOZ#N#hzU7޽ +JZ]q, ΡQQzU7rS%Z, p4ۮ2n9 +%dB3B޹szVɁr@cOtxxsԤ JJXT9efv%'%m`MY&B3 ` Rs|uPMMMoTr֭_'4M Tt%_Zsh4rwʕ1Q}}}ǩzpzEw s{FpH@XsPUYnO`VP%lTר-Kr3g\{Ue=tBP?8q t3wv Fv>"a1–lߜثDN~j- {2:c?$܀+t7K‚mmm㼬fFwo[gc _ݤ%r}tN ~]DINrf!TU\dr@)Ns ޾b۟|4DpJ}aA9(fAΓ0IXA}[[}t4޻vGP\씁ਇ2c Tކ-ޜDCc :#hӄ$'u.͡S&tY?=f{蜹]mjiI"rbA Q kWvJ9Cn]9rú2~f|ߖ&ugHEdǨ3Obw|QTUY4ej_SJ8^UYձCmyC'5AJ8Bvf<ɐ 2ooFDy'6k:uHP]}UT?x;QR8k"]^є6 l/uUGw$S%B\yKEpc=AW:V6L3m/ Wwe3vUK^{I;`0 y\I#=r&\zlr ۯjN =nkϯ8>:gɪ)rQꡕ6 +ܭ 漢uvM:ƅ+NSc+ /M_癒~J\Q :[j=6߽ttƶء̅'>{7AP`PAO-.8)m YZeoNBe7oZgjjjz= ۄ2{V={׬Zlk,Br~u 'm>csʦ[<ܿw-煪NN +lUS54ns_ 7nK֪lLL-ٳg2[]Q0wToc8CIYEDE'M|߾*kjj:srG:}4=pE|zN}݄)'Rrr-7¨sǼsZwFQ>G&8\gvya[Ia;yB jK#i=C$Xu$F,ptN &F4Y1 `0 `0 `0 `0 `0 `0Y +endstream +endobj +411 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 196 /SMask 613 0 R /Subtype /Image /Type /XObject /Width 253 /Length 1246 >> +stream +xQkUE.̙vw̘3ۈ҂RF Q,Vh (4H"`O ❈ + l㷑IV\ρg'gg'&aVua3kfT{b:`ϗ g=ye:M:?'r\?uM]V auqYvgQEm +g3[[k|I?fyonl~,pnn]yc8ߛɫ%;Cw|?_O^ T#{h {"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{"{ +#{5}]Ͼ(GmM;wo +(qx~E/\j t8d_;~m:NGQeYK_z 2>}镫u\xj~89{v%;:m\z8~ko7Tb-Q׊٬c[w>~{3x,e*ɟ)a|u&wI*ƘKnU0"cxy\ dXz+YiL3G٤n?Ŝ.~=Fp +endstream +endobj +412 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 578 /SMask 614 0 R /Subtype /Image /Type /XObject /Width 440 /Length 2003 >> +stream +x=nQF,dlOĞخX +a B%D +p39i_s]\q;l73;Mqonf!7Ǜqxifa ð\.O'_yW3NX,f/~lݫNy'gzI$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@IM'N4h: t$@Iy'̝|?^{u9qS'h0,:܏kfv6<jb~5>f䡓Ӵn>{H<o +endstream +endobj +413 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 615 0 R /Subtype /Image /Type /XObject /Width 192 /Length 993 >> +stream +xϋTJW6$4$C%Qo".EP A]mY*KWx ^x`xЛ'詗Eœ% "6_xo|>| y@G>``̨ԍ.Kt^u5 eUV~\i7t?(7jҏáJTQ\;eߏIBe/&?Wij{&I;v/M´^beFwıs^wp=WۗPVё~V'ʪ0u6}s߯xoڵYʞ~hr\r>.뵧 5'X'6Q4o/|~$.ncj5,g瀝\ՏK5kcsn8kG[·T*[Gs8vޙ3IV?'KA%JH?(~P"DA%JH?(~P"DA%JH?(~P"DA%JH?(~P"DA%JH?(~P"DA%Jt7 Mi[<_v>G5_QRNn:v7v!? >#·}aSӑ~n>>v>W/ kxG'w> ++;_K kPyGgׯ_>5;9W*]ܢقfxnx \@Ξ>u3]WMJ*gyeeQ?4CJB$tl> +stream +xkU/H#̼ΛuLkhBZhPZ4XJiH +EX XK/(yғ'=. {}|Y~cery@K:g"m."΋8 ]WSɫ.*f^1q2zeTypdPK<@ƺ%',,qΧm-fGi0$aѕӧz7.|cOKH~t D *r]q_?op=ќ]ˮ.:Jٗ=睯N:9KfR? Ow> l/}r>l﷿0V<Ϗ+S't> +l /@y> +stream +xn: 4Mց]45M#?h^!8ke,aCJ69iG::5DH7cJnnY{:iSGv:OM7MY圻\.]m+'G?x-аjg`5Wi^&ZUo}x>7Q(m|}߯=p [_MrB*+*"'50R(S*~qV,}ͼ+,KU]S^*7PZM&A-dkb:9we+RzHWrK^:(p8$pY[ld4"?b ʒquF˶.nYUisqvR8.߲dVzad=d~S|qۿ.r*z5u8xNyf?aUSM]='#[#[GY(u,x:Eʚo^YKU8-ִmuuhX<˞9w>)[T3>Gl%K=%25Ibɕ8)~P0p>TkimdCrVaB-5:ƵZnBZXUp8;f |P~W$[EqOB';Z]JX0<|:.lY^B{OJ&_a{ux<8b+V: mI"\,>_}MEiV]smfWGXhngHCi=aۿˬ1UՋ|aD +endstream +endobj +416 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 154 /SMask 618 0 R /Subtype /Image /Type /XObject /Width 139 /Length 1217 >> +stream +xn: aE3h:Ȗ62eU !Yhx-bƔCRhȕ8 G~eQGܒ+p?.bMXJ/Q4]rD41ƼXZM8cY!cq \d^j-B@oooA}Qm馣y!m֒]\Y~DvɛR7)*|ÛBNY_@7ŧ%h}tH[i}Gh}tGn>6MS׵utn>u}<CU( WHn YǂD!Tz#ѧXouP'G)^{8Y=` J'=bo!9Tt2ov~ȁxѲE!''zgoQ8bQf룾֭lY>#:4\ѡCh꺖oI#SveivAh%%/: +endstream +endobj +417 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 619 0 R /Subtype /Image /Type /XObject /Width 167 /Length 937 >> +stream +xMkQhEKbܙIۙ{' +XĈ`-Xbu#"P)]Xw ugvWڅ+qQ^&ޒ>< %34v˙ _ lyz<ՍUN'KvYxݍMOt#0TU| +xf- ;";N5M3 P\n@X^ĜPzuckGf`?4rҕG^JH=Otlv{?[!63rwow㒊pD󘟯\[U>"ovg7E9cqқwï?Ip#lܼ\P>jn-ލ 9э@;w7kʇ@VvnxcɮxlBJgźyFB-kvх9 ŞlŮhɉ % + Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":AUD_C_{} hOs9~Z96 *5?lM='z+̮[*5./&iNtfsu}||x{,'')z;?us㗥cpÕ##KoW>Ƌ0MG2j bk>[}Sã'/m'f'W:]vn6t;ہP~c*sd;$u; +uY +O͋%.mvXwE.^-gNo(O.^t9QzPmc6LayN7@ +endstream +endobj +418 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 620 0 R /Subtype /Image /Type /XObject /Width 167 /Length 936 >> +stream +xKkP barLsor)Z +R +RRZꍱڍnHDQpku!+u-nLfҋjzN}x)$YSTRy ly<ՉdN1%MTaZGDwx͏ U|g2=6n^hm(>MǦ2;,wͭelcCw kѽXLtݏ!PpbDdם5%}{?,Ҍ.ѳEggOg/u%SLѷQNh]NPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( *#:>u!vy}i hBD,bv;='ǤO+?fŅ=Ud8<$}|l+cWGv;]wSs u/1\A٬>:>_}vm;^~qڮz[w.7lAj|Ï̍NNT^L^Z?t9 ۹cvMUfX÷ OtkVꢌM/}6/}Y!s"͉Ua1{O d:mqIlTRyzmA0cӭEwZw +endstream +endobj +419 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 621 0 R /Subtype /Image /Type /XObject /Width 167 /Length 937 >> +stream +xMkQhEKbܙIۙ{' +XĈ`-Xbu#"P)]Xw ugvWڅ+qQ^&ޒ>< %34v˙ _ lyz<ՍUN'KvYxݍMOt#0TU| +xf- ;";N5M3 P\n@X^ĜPzuckGf`?4rҕG^JH=Otlv{?[!63rwow㒊pD󘟯\[U>"ovg7E9cqқwï?Ip#lܼ\P>jn-ލ 9э@;w7kʇ@VvnxcɮxlBJgźyFB-kvх9 ŞlŮhɉ % + Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":AUD_C_{} hOs9~Z96 *5?lM='z+̮[*5./&iNtfsu}||x{,'')z;?us㗥cpÕ##KoW>Ƌ0MG2j bk>[}Sã'/m'f'W:]vn6t;ہP~c*sd;$u; +uY +O͋%.mvXwE.^-gNo(O.^t9QzPmc6LayN7@ +endstream +endobj +420 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 622 0 R /Subtype /Image /Type /XObject /Width 167 /Length 936 >> +stream +xKkP barLsor)Z +R +RRZꍱڍnHDQpku!+u-nLfҋjzN}x)$YSTRy ly<ՉdN1%MTaZGDwx͏ U|g2=6n^hm(>MǦ2;,wͭelcCw kѽXLtݏ!PpbDdם5%}{?,Ҍ.ѳEggOg/u%SLѷQNh]NPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( *#:>u!vy}i hBD,bv;='ǤO+?fŅ=Ud8<$}|l+cWGv;]wSs u/1\A٬>:>_}vm;^~qڮz[w.7lAj|Ï̍NNT^L^Z?t9 ۹cvMUfX÷ OtkVꢌM/}6/}Y!s"͉Ua1{O d:mqIlTRyzmA0cӭEwZw +endstream +endobj +421 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 152 /SMask 623 0 R /Subtype /Image /Type /XObject /Width 150 /Length 1210 >> +stream +xn8a.Tj*ɒ8pRrm!]횓}_3?g`hB}h۶v- \mwծqYki +!H4\.eQy9g]V ό1ݮvu qǦivj׈?r!$Ǯئ8 MbLf1JI{_^d +87,{/krܥ9_Ya53it#q&ijWOaSn|ķA0o緷_~ծ d!3BƘk׋\E_7Kve٘OB&Ad3t8NS{0-5r)scd>SOrέjcǣSa 9,kzFTak?e|~`Q}\cux ]v'ē%t">=sS.<{y,v9[ziBT'Ky#Rd] B(.1Q2ANñH|xPiڥa2D ꒆ(gM9NL0 |Q6H {<0NPCp1A^rQʳ׈Y|Zϸ3S3uYLH`$Od3\)MaB0Ɛ.<[km[yipC}ړ U>$TuZ$b_~/Ekmۖ@m17٨Q'gӫ|~'wf*%zx^ك,) }]sR.!r::D +endstream +endobj +422 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 80 /SMask 624 0 R /Subtype /Image /Type /XObject /Width 171 /Length 855 >> +stream +xKQP ^t9sf w望f +i-@ED%0$t# +i-67D)~hѿ-۴)hc{Gm= ILP +-'d"Bڑ +k%w#* eyPUb%떣;ڀbl(rW2'42Vh@<uj Ŝ0[\2_IRS7&*n%͊TZOuvO Ýpr`t[ۑcdsPYbo>9*O \i5> xI&^ A=T7 +M X1d,HA+3?Z'4P +_56>ʎ_m'ym(0:^ @o{]d|]I7u}%n@!j"""""""""""""""""""" `h*V=8d|07;q]82 ^C싩)079Oú0>޹v@t<D3sjgH&4c>a|(hxb2dP[C7φ}q}me~tL0!rjm{?;k٫XZ[ݝ'77Bmm7: +-Gw\ 7َ]32p^5=1pi謶R%o2 [} G^'`Ut0  .c +endstream +endobj +423 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 480 /SMask 625 0 R /Subtype /Image /Type /XObject /Width 540 /Length 22916 >> +stream +xw|W7~}>faMlI3cKSdifdW4tL!tj͡@LG25Tɤo,/~#; +A񺬘bĹ?{e'_jL4Ŷl~ :i*}Ѵ՚T*U.}v1YbCrD|,Enx1f%-k԰ '+" #|^2y-3WY1~nzxu6K6G%b)z]͊I[u,2.5b̑ v#^b8AU /2D !ʹN0f c n'AףsV-6iѢݑ~LhlYzґVYzlvYVrisrp∢{5hL ;~wվd}횚HBBhZ{K cDI7 j߾o߂s_fzt+NPstdluWo7n؄s=]j#ͱ{=7օed|/W>~QF.Ԣ73Dqb~n.4/ɤAĿ2`pntnبE} wŠbR :EKE~WG:mfmk&gܢMj%ZP +JL %AėbݫZ1gـ;U:|_=s*&_2kqvcj8m/;ՑܮE 2#H tj߽c~@PH7< Ocf 3r~RF%G=x]Ljw䔆iuٗ{i۪gZr7Q<\A5զczw^LTN`1N/X eE'Zc5FFy4ݫw>3#23[Nͨ_?kUNcE 7[ي4 +K +ŀ< y3SS322rr[w2jj?~wc&jM9ٹ5/:Z{[Q,:H Aq=t-|`ɵdf;t|?bk637jVV2ZDA+ guͮ:-ri 3d??&>{ }pZT8Ij + >D񊄄03nb8xNvKg-Dg;q떝TeK>ʂ &##啹洴ƵSydµNɅ[ćn/dS>h0dC8ZMcX䪁Oi"͉jfe-5ve%` +F7+>ZRTTkied䄄-t̐qS3<;e5mn[/NЅx݋ [J*1M}"T Fh9QL{Ŋj$Z5JU]u FX#KWL4ȞUU*{ ^=R4MSJ9Pj~?3Dmd^PhiN9ᔚly 'N71P Q֎~i8Nʹ'']Y-o OG?8Ǔwr|Z) ݩ[#U*K 3%`1xEz*bTM+HSE_y`x]SdFdTeE0r$`0̅ZtjDe-obXujAg10Ĩe'z)X)%B}Bo$`6-;o,']bɣsbܰ Gc_r½:ؠSx bQ,U=cVoR)I>C)D Nq&J):&-IbݧrҤ4/azEja92ͧMk 5Yqɮ ꍖCX^ 7B#/rI9C + 1j8e<<"˔ SzoyIȁҲ3iA?kӕaӭc&}96V:Lď)Vy*T*K4{x6kj)10JIQ80&HQ5ӛ;(+s|᫇yp6Yx֥&N=N㸤:}{|aeVqМyEMrӓV'+լ,֡_^Z6vxѶuQ#==xlNciWTQ j< {ݠ8Q3iɴ hk8F'iu"͚YS^ X+4HŲbDhXLÆ͊ +'9>w`(oxٹrAQ~8=LE۵[^_a{|E|y + ++å ~!|]LXxVd)s{pE%>++y"É8)"#V/ܰ9A]bN͚/‰IÙEeeȒ'X=?\|DJ%eCq(+R1g=*ZVyF=CX1 GE%i8cvvK&j~$>D9c9Xvq]r-oH0>yC m6>)%:>/m\/0f8Q3a5^RA*ih5`CڶܷǠRTS;-kGwۺkgS 7ƚ瑶;Oo?tЬ6EJAܢ]:myZVju=v<D&4]`,RFcX92U&嶲^]7jhB&SO+H2)VYVmxם'׋/8m9EsfI܆/ֶi9_5rИ.tJՂ(B\=zW7o)Gy d7k[sEN竾PvM*EVM֤֛+mXbfYS2>8mC,isl%#QRo%A<_UCq&w^śIAXKׄ?8vm;{ {?.OMeOMɨףޝiA,rYjY@OYWaƧL9is _pAx _LKZuDʚ9a&+Pҽs6n4hnXT +x ,5LC\]Nep^j3͜8is,`R䑃Ƽ/(TО;JJ5Q EƧ5j׮ˀC6{ZB^nge2&R7t9n?f_{#Q@[X<3,Ze+ʑ39nRi_S aQRx}IbxqMI飧__A)|Ŀڑ?񲖓^ueLBjvrd='Zkc^ + a ('d3&H-1iN}5Ŀ>^G,f-3qnLBϖ;muJ^$crn{=8kzF F'=*Ym$[ 7y^-PM*\1nظ{$Y +Nu:ݨf-ީEtjc۲/\!2ƼIsI1JMx>+/V\z Wo'Ue 1)hMʉ.PѬ銿繸vʭNu'dg.rM;iux기KMhsTOG{N?F/. +12T&Gޤ՛ԌuNMHmύ2=leL 1E%:mI#L3OvdH7_6Yk1G$#Hdu"]:-7m°`GTnsXkkծO灊ۺӞ5;zwoH-qxNjփ.kv o8msԎ5j~а '_}0 SWʊ e4̙\8uiYɝ}'1b5;q/]}G -o8v䔆 >--[돉|='/ڣc?.'#H:qƦEB>v/A專$( +RN7i ˵qCߜytnکDң0xAZ4uW <~9.1ɫrDB\R*o =6 [GePH(A%E[S X׵M|?[G R5vW OB?a A)+VOHjZ9mI}&{UR}T{pA8Bu:3YIRU׆0TE0VLx̤.k -AL5^cpa9%* Rcݻ?W +^4K4'*SJFnvO9(hڬ`Jdh8F" ! ܴE?uGāl7n0U`:PQ5#rvoD=ϊKJMHIKk8z(׽o_ܵje\a㴕]>tnG:}z~Vڏo90nD[ؔC?/7RY֋/jy33a4ah=p^{k?_ۺSJZ~B аT7YnmZvMLeC#-q>ݼdc^͛֫P΂6Ǣ -5bRjMMk4u4ͱsԴFIF6G%zc0VJfmv5bMm['.?>i˷8o}O ‹G$er3jyY@Iϫ9Q)/EsnYW^G>[:kIZ̛-ea h%n|@wzVm?k둒>x/߿z蛯7߼x 96,\jIHi"Q% >5~VLB2-HCPfiyY#xVZ^ 5Gx^ÙK]_M׷/ou1âxFkԪ7glݙ`'_k7u6ɛ6fځarl\b<)N,Z^V _-[l=k_xsgwݼxg$/N9%Y`Nʡo:ֺE>=:rS*OZ MqLJlLo;U++\oovwZ]:R֡ JAëT5T7r磪=GպE>ӿֿ_9~fz& fOjJsd#TmHtʯj{Sk?u~+o7 J^t:ҶuwxHՀ aM10@ztDbC^w\5_n_3 V# ʱ0-kF*QT жN ӵCX;31dڿ 'vܳUn>crD1}v>8k%Hdnrc90A, A1fȸW왍{ +J!%ң&hYeiuo_l 3q游 G1P['j5yI(NҲg +O;sis|>yf;x)2IVg{0/LhAYޮUۉr^erDBVh^f-^5b}`^93i񩧠(7pa0̰e%is\:pƽXHң) +={E"!xsCV[娄O3DJ!.|rw3SJgdPVE˔ bs6 `/eq;Vla99'ca)ԑR9m}kc>֕#FT6:\= p|\dydܛVNztrHOr 1&' +FtXlBʔQS *JrJ>9h^R A|hU#=~4ȉ!F޵;*PԌk睥r\ Jzrua{yp;FS3ho9*"VjFĻ}:nM RNXy47Q1+##-U RkX\@^2`Je9a/x_\iO9EÊ C\֟w7s]6N ԼkК4hЌxwo?|Ӫ,S&-7`d k~vK5cz`VΈ&)A63#$,*)*>xvQ a|DMV8p y/O) -5<7ɣL=`x)5__ٴh}˿xB>s[- +NQzx|`)}f3IeBz`H +&Lխ? (Y>րȑ 褌 Ë) GM{jN(ք.ILR?i%V"l>9xHajgx̪$ԬrmĻ[n0|K4/1Kwx|R\/@OHSj`_0fr+.p| /y0<&V.Ux`xr{1ٕw_LcV-dМ> #Ȥ;޸z<-H)o)"Uc@E+A_=y䔦MZJLBJ|:O J"ޏsʁ;)A[IQ'!ܢIiK0ۍ'P)o}G$H_ƍ[D/1yԔgf7mJMPV({|崒>/Tn3B )o7?+vɏ>qĕhZQS0]r 4&ރΣ f;]4:ϣ7SۿNۄxxƧvWיiZuxT 2#II }^eьCٵhϫ r|Ļ|!k +/:xBMymr)<+ 1-ܮc R)W4jJxx/~^%{ȲFNzz^^VrxxC?9d`@MyMA*q.1K9hE.k޴`$=r{o> Xc!?pxx}kwia- /jX1 D$=x{i4MBÜ6Gbt}81QrQ͢%_ޯdiZ4X ^W_;|jrJ6;Vl R55Uf>p54qDQQ̦- сgVAv~b{g6թy5P$LWUns<:e>qDьq36ǁ r{OJdC g_XS~ڟYb9cwkjբ=t4޲dc^Ung7l߭vͱd}{dµ2خITCUϬ;men:VeRdi;&#ءGRN<+-S +{Σv|-/`W#_];*'%2-*eqv W[٨Qs}P]9tn#dXiVPʊ~ĕg}{ $>)A8~:in=Ghꍖ +s֚i^Dc\~gi(w/Z9wy8sbj6J_]z睟]~>ٷv'1uf.PR^ritNux-dA_].Ӧ Ëzuwt#VY|R- ̑5wBq\.\fgmwN^ A rhx4Zòjފ}o^zx3)MfG|p}9ٹ<޵cO45o]w]'urz&$ކ’Wrm=v tKu܇$)t\gyvjs6 /l_9i^ɣZvӈ]xV[ѼYNcXo?˳d}r֞5;~x|<92d/y|xTZ2PMPT8`I%Qs޸is'D\5Wי:)IiZNڲdc=_?f3c3LBRz81KկunqiuLn.5҇g7TԌV"bjLrrfaߝA5L`CxT,899'\U;5xK|A!$-k"]:^w{ F CnK z,!pb̖N#5&SHdYo|Vٹ"ba15d%No |XxsyTD'o<dJ4hFO _ؤI꒮/O=A޷v烳/o.x'HKL%]=^:~leNcso.x sT"K9tyIԬ\]g޳zHKo\46i^9tyI4r L5tdYyݢ/2#ȶ#%>[ޯ`(A6S՛x)islYě ^cm$G$'Ҝ, V71v&)H Ɑ+:zkF+A1t4zr}M +FP) %]Czc"6E3$VP r'ם($E;m֝A5N GG1'R9F56-Zj!]@^h^1pW[x[ 7B-H;Wn%J&PLqxRS8駫m=8h.㥨hMFN.L?b(mqx^F0 #hy#m00\#*!}CA&]@^7jXrW&\-r(^d{YH"NU&\Rdm0PwO+]S'?xxsMYW +7x-iA&P}S7 rw(\%9W>`Lz< LᅟvJUB1 /17vRUbRnBJ^- /="]=^rޗfMLu2FZ*J塕g(z5[u؋"(7{5Xfxj?OWo4x}ќlhD,vѰa37D#HjA +&o4O_.ޠ YɛkV/jY~#k +'ް Q/soy-lټ {7/_ꃆ su<(3ݝ 7/i A-h^ڶK 6/@aRNrrr}7/ 6=9`ƍ[^LyJViAf83L޿ng&7/_yz񮲡^BE]ky[<5j\9X_D+˧wINiHyJ=i^M *jAˋ''HtK0طn'l}`C'' -HIы[*SGGw=y2{쑃oaѴI+ZP +J6q ++HI˸t,FWmWVV;5 A>qOoߗˌLTd*,G{YK72ذ`]pK6zɣ<~x#kYUi0Hok牷3?x G)*Jo0bRW~k" `M|gE&F r᧣8ěթHTp(AIbxyYMq${wpneC=g @G̪j 2nE[[NG>XSjVS)2)xbངbMѢߵdb jծ\AŠD36kچ"MG>M5$GKI5Ӄs_lx:OV0>tF7XS8xFG~U NAKJ7yfQu0%ȼEs39_09ayvސ~#fO]T8xc[Rw_˯^mW]N|ó6FiA|zJEBѱzcxz̓ۋ?O}@92`QVr|ܥS*/>Dck3~VS>kS;=a6.ۖ~PO579';s~^V#6Tӑ?hh^IH_{XBvu:]7(~M"bjF%&Ԏ`AYEsÆ $6v|RZ9ye%wH@oK=RL1uW+XJM y)*uW]VGxLͽk"#WjzcZ(e3hl˜vbxכJj ҂_J2]F;u(5GyxTNc-*oN +6i5^Cn~6͋I5秽N(|S;&;KAV GtȄ'A:q/T /QzIĹg,fyHw@eܢJ3)#k +J07 (pWnߤI'n&Jv QS3J3=mgԔ2˕޸aluA)/:\|rmJY-iԔrXva9Ym"bk21?MjӲyq&<~g~ÕG~I xzǑme&wfxNyn+[2keAs0*'+T6m?u/%Q{{Ϣm{\.Wf 'މ^K4'm\.ך5vEӿعj bj_7\ܺuGKtc&Yv|ʻuiu7(?waiԔw%:)4,F$pFxqiYFYKw_=+2J][hw9ǷzԽSi^DMyP՛(AX0r +@ւ)],Dq;GJi^y\C2#+.JOHٷnr}0S!L QSw(Ac.(2͋Sw_࿾:sʻ%FyTw_&w n@&9ʹNdp6;D+jaM= ozhIˋZ^YECș5~s-Z^sRg/%ޡL\w ʼnZe^2j3 +@%* gHc(HL 9x$XP`ӊu_tZ(j_()ugSxZ-iQS)4'QzcVFOHqu5rGլS~׽)?]}LлCzTQQP~m] dzڲtr(1yq<˔QS^fCqK O5)7n +RL s +n ""&.b`Q9xr,'Z7{CWS_׾ҧ{sݴgf Ld_H@nYkh^RHѼDq2Z +4: 5BBg9_`vm& +Z.-C@ML"&+*^T+5Zy  +5d/-+9fQS*0Tp@?:]`ðOE©j`p\CP8jaاq?)~V_7QI 'S,}Ud4z:DDX1#`%Ԕ:@q&Zh^v/WQS*84'zzv4=!+WqqXU9bMʅK63ѬI"}0k8ɣ[o ^BqhtGoWy9!^`뛡"1伉Td-gye/i)k4\h _9.pb|RftRo#=~hN{qH ^]ѼfXí_}Hc^7]P81;~МH&(N*'sx2jʇRP8*aZp#5:~T<+?(>1-PS>`&@sr᧣6<(].WvpP?E-+t>@Y>͉f%=](e磲r<@wx1p}kb^&IxwIu^bP㫟FY^LAt@zpӸk>@喟ەO4&NGzp߸}I G/:>5TTj&=unĔQƐT%篦bXS5\R*{?_RCHz@E<˞{걨* A + +6U 1P*ɳx?oh^ +4xK(H L^LdҜ PS% (*l?$oj#Maq4NЂD%Խc7ѿ`91$`IÙ)VlוGRDLM +{ye)3 >qU~`mc'0p"-H)ײDנSVH+Vdk?6ik^ 5 +љ^u-sYVLJ1Vu7`U:u׆,KzD4Ui8#K6%ֱ7K 1&ŋ`kpxc[ѼBap%Pr]Wt| +XBǰTPrX18i'gɩuWY~ +ӷOU8E2qyЂ \|`+uc8)ynU#=""*U]X;- ءh^- Nx +%؀^COdD`ŧYm mZ' |ޥg}Nj70YJټeZM hYZMeG|ܢ9/%H9G>}EVki N4ZI֟dޤexiYK8a*PAf~@s(~-/i9Q8lZ +`jNYD4fC)A)^f0d6ɦuMӤ9CGI'RMΛO +}p /iX)7U3Hm(3իEsO飦kv? G3_P5,v#*V9_o +Ju6o7a*iAgbB(=e/#q9֪] Af /bu$Ԭ[й/Ŋ4'iYFg$=!^53zspeт15EK\A~{"7l /8kJSmq'/9bE +PE)(z3͚t?z#k7 y=> .l֦UZtb{1{t1(Wz܄ItbHpF7"ͳ$KJ +)^vZq*N%Si_Nv鯎pb-4/ tD rZ&o>V9}9 +qQ;AM\Im\hP=Z`-Hzcxz7oh;Vliڤ5++sV A+hAˮ<|VkʭCC-4/M'2n*2]A:e=c0?{gr{Z|RZLbc[s_)ykHneɥ=&&q@8Iؽ $O?NS0Z6s?om{/$hfkNNęXhI }ʆD`eظѩYѱ51ѲH#3 ګF~UJ@Qщcꯘ8_j~yr%dN{]GsWCF/L ܝP _zhX2"IfL^(/dMn=_$AIFJ-- ~1V/]:&+=m|RRFjj#, cF]=DUpYɬ]f[t4 nYVd]m}VnoTλmW/k_U?gM6'0`%3%MfՉxXrz UF+/\YqJ0}ݍNNxqZe?23rXN x7}mki+!HZNh9fY$ݶl`҂2zIKAN4Oh0&M4$]8}B\p@zx^@2uu~z67>4,L$I%$,/ +vpt뉽 = EWC@g;99$JhCM"%I+?e$t 댨[8Ӭ)!0uݓUGH{7}pO4^[g 8-r^1C q\.-:,2^K'} Jnq_mWsUȠ%0Aݣ{8iU 4B1.e.uuo_3hDaoPDݛ{[h{_O ?nݍS-_#O-#wtRFaAi(qˀ;:hQ {0 d,ªC Ҋ.<=ˆ~\kD~M8#S#+4{sI3rIᔯ}BeM)ea@/@ qFPL)H~'itFS`. 2rV?$gXZ ,Gx@{A6Y79o|ɕďJ)Dž*_;8A3g!у=ww)۴+prO`8,1~tC}ݶ:7h+s!#ojUͷ}{r?pB)'{ph@:|^ZJ:e9MNMIɢ~_Dž mFG;9Y$3_rNivlQ6wќd@3G^JIɚ:if /\{Lsxp̌ g/Gx,? H%H >`K +.Ȟiۧ7tl1lnpV=v #yV3M_:$qUlԴ j9zᕗ"Fy[g$:}M >~A>Q1 :>}1%[SJSsJP煖m. -ǥd*ђB˱/Ok5B_#6.x >I0 +mJ9H\B=B[>§\$,6!!䭭uouk9t6faQc ŠSKfTͯ{nPxJ)[j~̲JN;%ݛe~P&t!ec3ށOtX~-=_gWrA +9ɶȘȑƮ? =YwLGǜi/RsMDG<3",rbZSd%(-Oxqlg}bbz[-1>[۳T}j R2Cɴ ֠gsn\?ٜd]^vfdl)Dߩ]}rQE`H4#&&wPdqTw)HM@P`RY`HH9rmu _tcՒU~A9YQbQ]F>!(W/. #ykyN{YB.GΚRٰ~Ϟ=8#S>NrE9᧳_YI O4/GtO +>2o|a_^eojDMDe]88]D,:0V,e]7> +stream +xKQP ^t9sfΌw望i(> B$+#)fMAТBB]lѦh1ΝQ[9{x#yp<0'orZS% iG*܍WB4= +B1Wʵ[Voj9TeN]ɜhXyYԩ-s|I6s`gC.pOVߘ\l6lȻ,+G. 2y nco׵~춐$ *l,FW?^<;p'AkZ}Lη_ AM %/B2{󷍏~޵3} E)JOuten%(mIeGg}'~n Ƈ@ٙ4[h + >鍟{ur(tB[č(@M@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\#plMzCw}'+G~5Ʉb,",$SGAӉG# H]sK_WGٖ.2 ֮o,~{a|>?^=[8}S#K .~k9ݐrtEpO)b(5s_(/\dݓVɏjԊV#bSšgLW?z:Tl +endstream +endobj +425 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 83 /SMask 627 0 R /Subtype /Image /Type /XObject /Width 141 /Length 878 >> +stream +x휻kA?{3w{;n|!"HQE m  P>Q NZK%&>0b1s}?|t{>ٽfJ%XMQ r.tApQkȪv[wOK*~B„pYm٪2c(W*^?rYfZ$,WJ 7DeK%$ cZw~1q1zȱɩ>wᑭ#Ģ<L'K]1عח^_]99,~ݧiSҚ>=y}&=wO TQ&_}=L="SS(_6^^z5ajXP}V;>fp ȶ|Inʾ .:=9}Fx`lOU97>;ːGE&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#D&#ԑ z5%Skgj5orEPd=w ƙH*vRۻgbL)VD- l>n +(RHeq3S! Ǐg}}iU˼> +stream +xϏSE6iӴ3mMyeeL$!HyX!tM*$Q‰ &D=h ԛWk.7Bg.O>kd6"g*MSm87ũ2j2|,HC7SY=IJˍ:yZSmcmdҕZ# Kh u1_Ǵ) ;3FQZ3(Λ> tl9Jo|3 [g/yﯿ%aEYHҚd~~i?o=氮$OK&nʦ/\r Nӟ-_LLMu<0BtNjN_ּ5%Fwr9#W֎WY<%Z!#?6Ti'?9|/}Uxݵ:7w^JEp}]u‰J]NBwRНԅ.t'u; I]NBwRНԅ.t'u; I]NBwRНԅ.t'u;o_7zkćVy=|UצxݕcruuaI=dơC@G:%~?БoKqS]Ֆʎs?[ ׮f<}g̏h~nid=/Mu5^}teo~*L]]=(E\/nerTzw۽A:ٿ핝z[OC잚 +e8$hS:8*SVP6V*p`1 |qT(*^FԦn6'MM#ua$-;L)v+9sTVTs?)mb +endstream +endobj +427 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 80 /SMask 629 0 R /Subtype /Image /Type /XObject /Width 171 /Length 855 >> +stream +xKQP ^t9sf w望f +i-@ED%0$t# +i-67D)~hѿ-۴)hc{Gm= ILP +-'d"Bڑ +k%w#* eyPUb%떣;ڀbl(rW2'42Vh@<uj Ŝ0[\2_IRS7&*n%͊TZOuvO Ýpr`t[ۑcdsPYbo>9*O \i5> xI&^ A=T7 +M X1d,HA+3?Z'4P +_56>ʎ_m'ym(0:^ @o{]d|]I7u}%n@!j"""""""""""""""""""" `h*V=8d|07;q]82 ^C싩)079Oú0>޹v@t<D3sjgH&4c>a|(hxb2dP[C7φ}q}me~tL0!rjm{?;k٫XZ[ݝ'77Bmm7: +-Gw\ 7َ]32p^5=1pi謶R%o2 [} G^'`Ut0  .c +endstream +endobj +428 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 80 /SMask 630 0 R /Subtype /Image /Type /XObject /Width 171 /Length 856 >> +stream +xKQP ^t9sfΌw望i(> B$+#)fMAТBB]lѦh1ΝQ[9{x#yp<0'orZS% iG*܍WB4= +B1Wʵ[Voj9TeN]ɜhXyYԩ-s|I6s`gC.pOVߘ\l6lȻ,+G. 2y nco׵~춐$ *l,FW?^<;p'AkZ}Lη_ AM %/B2{󷍏~޵3} E)JOuten%(mIeGg}'~n Ƈ@ٙ4[h + >鍟{ur(tB[č(@M@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\#plMzCw}'+G~5Ʉb,",$SGAӉG# H]sK_WGٖ.2 ֮o,~{a|>?^=[8}S#K .~k9ݐrtEpO)b(5s_(/\dݓVɏjԊV#bSšgLW?z:Tl +endstream +endobj +429 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 83 /SMask 631 0 R /Subtype /Image /Type /XObject /Width 212 /Length 948 >> +stream +xOE@lpgz;3;뺉Ȳ4H"Y IpU +Ƌgbf{~`KϿxiUPVZ{.̜N2nplnkbkTa(]ub+eVZG߄|2.10 Þm#KHwRQ~eW8yd#2?ZAe7ʜ6"S?3N?yҫ;Wqܾxi󹍢Zk[nk)[EnF׾~;{p?v?3ٯlC-OᩁI~+>&ǏOf?oSSj0u*>v>M}멽be w[tAp߃=ROmmS nqa壽}g5@)_~n^)3GwnJycs2O{w{ Gi=PJC)i=PJC)i=PJC)i=PJC)i=PJC)i=PJؗo߻/d?!>(fO{97/lJynlem}{-G>9݇*׶=e0=!>(nN: fqqkdo?$./߈nG' )c㱽^ S_=М> +stream +xMkAz($$]23fv֔jmA,} *m-Rl z]KETXD77ɶxs2JdA$eAʎ}{6ڜKBeX +R>&66L\hӛΐbwN!2镩T]GL"%(RzOoE4)=<ٜo\?ٞY8=~lJ4~j QIn'o\{m]K}C( Trv~uULtjdh(l&, aq/_$Kڊiه{;A< zԄ fs>{h"̈́v-g!IU? ќoj ^u)ye;Wi]}&x1y_+ _C]/B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B"B_\FЕ'\_.C;}&x\ORYRmښ&Oi NϞ +AJ"WlT i j( Ut>"WhY>>팧NQw)m<> ]oZtZatEcՏE/6^1x̖(pS6}cGt[yK%^\V"> +stream +xke꡺fwwٝMv 5TDAEA$=hbC(5H*xAR؃P"Z-zIfgm*S>/f `1c1c1c1c1c[3.[ݼ473Iun>EYE͸$U~bI$] +۷ Q. +)u~a5 LV،(;y|.0;|8{kؕN.Z|ǞWtniIJ7?ݏoy|zqInS/p`nr=}|0Mo8?J~yG Lo`znn㷏Bo߁M<}̙_xE~ w +ٹ~ !@ۭXӭ~kC/OX`AQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@h +t]YZ.m}SeNR}2+ +| ZxZ~Z?qzRww^$?HܼrCOt (ѣ?67ǿ.3 + G֭7g?S&ޗ~[U~"lyǖVϮm`W:z[ᄎƃOSg5wQ4bO3k_0MoMԧlV= mVk()z/Ʃnj MZ$A'JdMǟrin3] +*?gIgLc1c1c1c1c1ݻ + +endstream +endobj +432 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 120 /SMask 634 0 R /Subtype /Image /Type /XObject /Width 174 /Length 83 >> +stream +x  Om +endstream +endobj +433 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 120 /SMask 635 0 R /Subtype /Image /Type /XObject /Width 174 /Length 83 >> +stream +x  Om +endstream +endobj +434 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 169 /SMask 636 0 R /Subtype /Image /Type /XObject /Width 695 /Length 1499 >> +stream +x͋uZp4$7L-z"ER,ъ(B EBS-="(jՋ +'[SzknvO3+޼ |63;$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Iջ |(G;Ӣ󍩹N Z]hsÅзlh\7C/p!\;QK;zdV/olv"M_&Z$} ֡b"Ml .\&2s[ݾ>z(ӡ^۳ћzNhf(s$A3]H0>:ҟ/:y-ᄇb2+C5ߞ.?#l^Ӆ 0'G$Lt[D9qʏX7/][i)-"[y{fa۶W~Z'^xCBHvV>wc+?`w?ry$ o4&X7^ idy9#lmff|Թ_*?`.|ۮuKBmfgw@G'VH(݅FBɊnp8Otu偄Ԡ](m$ Bo/V=> +stream +xkj0`#"P [Cb9/Rw]:ߏboeړ\c6sNߴBgD'ER +B@sau@i?.!юqo`J)ZcL9w<Z\J{_kE%Bi*ko1Rzɶ<~Џjgb]ޫ?:. +AHEBw/}u{@ \r@_tBY1:^%J)8H)~qC$\!w-V +]8 +/>-f}6֪=w$s!4M;| lvkL,B]XkaО)@ړ"M-_vs!""""""""""ڣ/ +endstream +endobj +436 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 103 /SMask 638 0 R /Subtype /Image /Type /XObject /Width 176 /Length 75 >> +stream +x  Omp +endstream +endobj +437 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 117 /SMask 639 0 R /Subtype /Image /Type /XObject /Width 89 /Length 824 >> +stream +x]kP}Bmr$59'q* ȼ A~/ ވ(MQWܦQI>SaŤ?B$MZұ1c1c16RQ;}ޝҪ4TRR oH*ء* eBӆMl7u!47({ ʶ}cUB*eϐh)1{>O}GZ#oKëPk0B*p F|pp60@xj4`{kO>{My4 +sH`j08 F=>0PuWۮ_{MHhSC /,̡MGu5B8<|`?!zwP-UmV!8pd˅s\\Z9}.&7h15'd=L+))n߹&עQRܠUa{RRܸy? + Rs3Q԰\-k'J,̙-S=|"""""""""""""""""""""""""""""""""""""""""""""""r!ͿJ3^>NU׈,{dBOᆶʞD>H0y,눗W'##e^c1c1=~v +endstream +endobj +438 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 640 0 R /Subtype /Image /Type /XObject /Width 162 /Length 902 >> +stream +xKa/(!eqyg3R^ $~YP!<^:x SPFECС.]3ꥃ[<߹֍__ Ϗ.^پ +IYI/ڃŅZh=ʪljK?^S>t{rLۑXEQ}!>y8' +>ZP>i~upKpȨ\y;>~^(XlLB} WgFOݽs2~O/dPYU)*2De)De +2QLAT *S)De +2QLAT *S)De +2QLAT *S)De +2QLAT *S)TQ}̤a8~蕉`GWOry+HEΜP>V-0dTxNrrr_^CHo^ݮH;Sw۪߿|vbbDZql!sH-xmYec8"_W?r|`Ņھ;B.rj514x<5uk}c{/psr7`Y+sZ׾$vDΊ.y4 zU{> +stream +xKa/ eٙ}s~̢xNB $<CPD*,E+,*4*P0J+Cu}sy>I3V1cˬ#MO2/!܂ ӑ]0/ +/40m 7V2N1tڴJp,s<}ԥSC#"3OpK +ͭ:Μ[tSUOt —*[^hJr_? AKdmfɡnn=n}CLvouRbUkEk/L>"~@u +4Ĥ!/IhCFk!ZC,ZtDk:5њhMG#ZtDk:5њhMG#ZtDk:5њhMG#ZtDk:5њhMG#ZQSk|ߌPkckoT*/p{PlPY^>땿)}]8={[}]~c*KjU(?Ù>\ړY{g9x.HuMUfW&G٩.=_9XY½92Z WrQag[xngn]6xzjO(D[@$sαMk ;B(r +ܾЙL/䶺Žp=)5FZҀR8BC3&]cd G +7X[˓V5\ +X=Q +endstream +endobj +440 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 170 /SMask 642 0 R /Subtype /Image /Type /XObject /Width 263 /Length 1155 >> +stream +x_TeyFsάsg̖AIetS-# (I0͊B(n{nbvvםE,cٻH2И~$+[٠>ߛ֝HXlV6_z" +E*g`fti>HAEln/wQ1uOfS4V_SӍF#)if}tlqkk>czw_y؉T3=4~WYl9ݼy/ɕ?yxf:hrm뾽\^#+QL3(A_K ^ XqƟ]oE0H6e Ͽw^q;)dG{ `©gV(bnܗ^}5 _^\}oq{ɺ&տ}_i2*DF& +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @Q@ +D( @E) +6(ʊv>?׽0+W/wUQtige8t5 gϝ~ؼ,}p߁&Q |PMh4Vvb˯^eWfg;hlky♺V|ɧ+DQNeٶ[Y~sGwmxg{ů}`%lzSYVY$yE>uxΘjoΞ;KG^#vVYEkE. +c6äOQ˿޿տ涟k~Wئ-\MFnc* EʹL~5՝Q۝-z6uartvXs:o~e +endstream +endobj +441 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 127 /SMask 643 0 R /Subtype /Image /Type /XObject /Width 41 /Length 472 >> +stream +xa 36沚`x=p0O\VSumc9-u$^<ѿ\CᱡD\CEm.h(O*UA 44A[ 3 ЃBMUH5|L x\-'D=fU\e4U;FݱuD@/ýι.i㖖~8hy !(K4s{e4Gcl8(hKGti8"*h:*5L&y>. +endstream +endobj +442 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 101 /SMask 644 0 R /Subtype /Image /Type /XObject /Width 167 /Length 72 >> +stream +xàSUũ +endstream +endobj +443 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 645 0 R /Subtype /Image /Type /XObject /Width 167 /Length 937 >> +stream +xMkQhEKbܙIۙ{' +XĈ`-Xbu#"P)]Xw ugvWڅ+qQ^&ޒ>< %34v˙ _ lyz<ՍUN'KvYxݍMOt#0TU| +xf- ;";N5M3 P\n@X^ĜPzuckGf`?4rҕG^JH=Otlv{?[!63rwow㒊pD󘟯\[U>"ovg7E9cqқwï?Ip#lܼ\P>jn-ލ 9э@;w7kʇ@VvnxcɮxlBJgźyFB-kvх9 ŞlŮhɉ % + Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":AUD_C_{} hOs9~Z96 *5?lM='z+̮[*5./&iNtfsu}||x{,'')z;?us㗥cpÕ##KoW>Ƌ0MG2j bk>[}Sã'/m'f'W:]vn6t;ہP~c*sd;$u; +uY +O͋%.mvXwE.^-gNo(O.^t9QzPmc6LayN7@ +endstream +endobj +444 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 104 /SMask 646 0 R /Subtype /Image /Type /XObject /Width 167 /Length 936 >> +stream +xKkP barLsor)Z +R +RRZꍱڍnHDQpku!+u-nLfҋjzN}x)$YSTRy ly<ՉdN1%MTaZGDwx͏ U|g2=6n^hm(>MǦ2;,wͭelcCw kѽXLtݏ!PpbDdם5%}{?,Ҍ.ѳEggOg/u%SLѷQNh]NPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( Et":ANPD'( *#:>u!vy}i hBD,bv;='ǤO+?fŅ=Ud8<$}|l+cWGv;]wSs u/1\A٬>:>_}vm;^~qڮz[w.7lAj|Ï̍NNT^L^Z?t9 ۹cvMUfX÷ OtkVꢌM/}6/}Y!s"͉Ua1{O d:mqIlTRyzmA0cӭEwZw +endstream +endobj +445 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 139 /SMask 647 0 R /Subtype /Image /Type /XObject /Width 40 /Length 336 >> +stream +x aߢݕe3n҉.h_>jvC!B?ݺv,BPUٶգ|䆊Hι"U9-{93k+ fvuu;'jd-mtqzOj +wZJµaC<%ĝ>0jfxچPG!Y zK)D4R}ϛOoҳ1.rJ)Bӵ'^6+!\p… .\p… .\p… .\pwI?s׫1FWD9"B}yYX +endstream +endobj +446 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 120 /SMask 648 0 R /Subtype /Image /Type /XObject /Width 167 /Length 80 >> +stream +x  O7 +endstream +endobj +447 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 335 /SMask 649 0 R /Subtype /Image /Type /XObject /Width 361 /Length 3642 >> +stream +xn\W=)C +g&fzS,$Z(~{RJzzaYt 4z]uYFA t:ײ,oեn<;(}5@*՞k @l!mЇy5;]=tZLS_+({TyC2ޡ@J)hͶ<H)R:fH7۶Rގǣy mۦXL @C=;}rS{mQw\Ns k|+]l;.6sJ)RRbRJޡصp3ئT&ldt 笻RJtC)Tkc @~akwD:SUpT:A)fk d۶s;4i4 ZhkHZgq}hzN WB D/@tg˱Q@ c +ЇQ}eJ:l +?|;V,{yvȊĮ+@}M\>{Rva*Ǽk۶Mc^?N qz-B\)ӭw\`uGpN0ADc\.J+_/gt l/OOO-l˶m~:N6$;4{/FШ TJN(Bޡصzi @bW-mp┻Sn,{0@3^eO0_1KN_.K;4}1=z:l+%<<c śEսЁzuRV\Z_!a#D-:{#xG KRVAY}x`lݩw\ uhd%2 p 7)%ryzz"{hzuxwD:P===;4q !Rr)qC-ӽxT:{C)iB9g=ڪt]WZ`妌(Ю.7E@#KFc?~B)v:(7*kE/zCЇz*2/Yޠ@ȡ1 UkPRn +[Ȣ4, @ {df]=.w\PJiЇRluAq5[b_.;4f' @f]VpѶ @;Pn +|evQn +!4 CA)%f#h6[+#uhSDz,!Ӎ՝z`rSj|W:hH7brq5{B8nF-v7;^.9sJ)x`sVRG1FB85ʭc$uh=P9sINK)ޡ5 +q׫v4x`+)kJu "xzirS2 f ÍwDp8n*S5z`tNk;.nMc [txtҟ9g*Uh[bwRJrSr>Ӎb:v"eY2ƺ1;.{g ~CxH'eY4XLw\:-RJYec)%c RVR*x6V]+)}XK]Su;f}hBMcЇq5_=pu-rx1Fk'uh~l]K*`F+bBeYq5},ry{{1Dv9e uht8T*vzR'- +=4<ԫhQw((g;}ew\vMlyvxC{^Їݳe]`vzdAByN (Gv VnJn8BRJJ;4fsԭC?D:Fwÿ+uP}.~C7rS-u9J)J! 7)%K|W%7 w8`\a1Zi;46&XJYUx*S M4Ȑo8~MYݵoC_.'|!X)!LӤޡ;*64MhsAÍwD:`> +stream +xM۸B()zhآlo0Q!Y ٝsAG?vka47e !^/0?ݟSJBjl#ŵH]yĉ!RCyzE@VYEJҀ~^"t^qan5OLF)"7ZW~ jN (37Yxj"_/mzi@D +P/}>tЗI"Xm34"8$RD +Po{́H!RCy^?x6Rei4_a"(K.,GJzjҀ~9l)qat{yH]"8AC"$= B),Wk) "?!R+^FO"8Ao6`vnF[/ jt@vatezh4_O^ЯcFP&RD +POU"RBE +Pc)Fph)g8~JB8'7D +ph)NH/qaHI)@0 !)D +pH]"8A^"RUx'!Rz"xW)Ia9ΊSJBz-FJ|X4zi@D +Po^H)@4M锥tHʦiZzE@R (#%IF#e~j"_4ǽ )qat{R6I8Z/ W)+0:={,P Rz"xW)RZ/ W)>A   +q)qL H7"() "II7SD +P#euVe"|z^ЩHq

>E +P6 CxTHj B)@)˓Hԋ{-B)@)ʆǽJ"L2Ryn4_4ݿlH +yTO)@H%RD +Po)]eHKK9)"dD +PGJl^Яmxl(ۋB)@=+)SJ?#v RzNH +R#D +pH4:=HI3Ҁ~*RR Rz"xW)"8GZH[}XbFzPSڞwFg),*RCyo^R9DFb.|}}7My^eF|<'%srxNI11("IgVlr !IwG{~y\H^JɇF)Js*۟ 9>Fjˑ.zğ[²,jnl/zz@cAyro݆Zh)ލMLݖ Z]Mwfۻ/^vޥtK%UY]/T%qz@/J`qJyKc>$Q%xlm6<>U +endstream +endobj +449 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 109 /SMask 651 0 R /Subtype /Image /Type /XObject /Width 105 /Length 2319 >> +stream +x\o$G?!Z{vQU]H@ !D% +=!+ +D&\VAzf<3^5cLwWuO{zlOZ~꽪q:0ʀL!!u_]6Cf! q{"dS"ڤF(G!Bu?ZF"#Hd0 +)iB I`3,o11.'03'1 1a70U##P+H\P5@b8!u.FA$d4XyX*= <Ɠ}0:{gUN]yo H̞y&gO1 +iZ1v'(rWhi !~󬺚9KGН.{w6ѳP/S{^>1 +CPEnJ`[0=lAbK>gY/#JW"i4o ="6{.T0EˣNJ]4nw"Yס~RdKY R&ͦ2^K_,(]@'^6 +.mGom/tLTLjPvJ6E/tzO1!ͽ;i}Y#Tw<`,ҿ"<|:輐 0_n,~Ibq$eW'6##|jN[>l|XF c_yhzޤ)HWTv69{"o;ݗJʩc$z*MZN( F4x;D=|]Mƍsϭ:S:kIeӋXm-vq;+Gwu#tbt5F_[VJV +zc:א΋XNdYrLn=کyժ:_=W_wMyMgQJ<)`FwӪ:qgⷵ]E쿛:tV&kUSPl*EJHU<ךּW#ԚǾW0/_S\h.7g*(;tG.oj:FM?ou;߶ST"7u:bVA{Vh xx:Gm*Gni&3֪G۪'ݵ*DSz/X1>- +@^\U+3 +xՍbޓT;_WL~l+M wO>2[82}Bc{ +鿫LY2xQA]?X2dZ17DWF{jQ<X"]7^%8o9E.{['M,2'͟u!rIdcl:x*y;I d8v͖NGW9 {-!wƒlY(~UEm?$iOX>=q} + .BoEl>~pkZUSˊ\O!}+Kz/{c:L>h޳߲L Pۥ֪E }rhh!>q!ˑign>okjXcgogxԈ>i4y +L>#~)|YޕA6 FTpӯ"ɦAu? z2F89&|w1WJ0M岨 {r;.s[]G+ +endstream +endobj +450 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 169 /SMask 652 0 R /Subtype /Image /Type /XObject /Width 695 /Length 1499 >> +stream +x͋uZp4$7L-z"ER,ъ(B EBS-="(jՋ +'[SzknvO3+޼ |63;$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Iջ |(G;Ӣ󍩹N Z]hsÅзlh\7C/p!\;QK;zdV/olv"M_&Z$} ֡b"Ml .\&2s[ݾ>z(ӡ^۳ћzNhf(s$A3]H0>:ҟ/:y-ᄇb2+C5ߞ.?#l^Ӆ 0'G$Lt[D9qʏX7/][i)-"[y{fa۶W~Z'^xCBHvV>wc+?`w?ry$ o4&X7^ idy9#lmff|Թ_*?`.|ۮuKBmfgw@G'VH(݅FBɊnp8Otu偄Ԡ](m$ Bo/V=> +stream +xn6EQ߉ 1hT͢ 7kSx&?=I$I$I$I$I$I$IYi[oMoڵi缛󛼏ֵZ믴|!ܶuK;,|Yz@z^;w}h)6y~sROLpNNT|\]|y #~jȉg3r9xFN<#'ȉg3r9xFN<#'ȉg3r9xFN<#'ȉg3r9xFN<#'ȉg3r9xFN<#'ȉg3r9xFN<#'ȉg3r9xFN<#'ȉg3r9xFNG^G3`vΙlYr[ߎuq z[1 ٺs/>75r.}?z~+ڭGY.nI$I$I$I$I$I$I)\d +endstream +endobj +452 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 169 /SMask 654 0 R /Subtype /Image /Type /XObject /Width 695 /Length 1499 >> +stream +x͋uZp4$7L-z"ER,ъ(B EBS-="(jՋ +'[SzknvO3+޼ |63;$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Iջ |(G;Ӣ󍩹N Z]hsÅзlh\7C/p!\;QK;zdV/olv"M_&Z$} ֡b"Ml .\&2s[ݾ>z(ӡ^۳ћzNhf(s$A3]H0>:ҟ/:y-ᄇb2+C5ߞ.?#l^Ӆ 0'G$Lt[D9qʏX7/][i)-"[y{fa۶W~Z'^xCBHvV>wc+?`w?ry$ o4&X7^ idy9#lmff|Թ_*?`.|ۮuKBmfgw@G'VH(݅FBɊnp8Otu偄Ԡ](m$ Bo/V=> +stream +x nH@wϘ +endstream +endobj +454 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 135 /SMask 656 0 R /Subtype /Image /Type /XObject /Width 293 /Length 137 >> +stream +x nH@wϘ +endstream +endobj +455 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 135 /SMask 657 0 R /Subtype /Image /Type /XObject /Width 293 /Length 137 >> +stream +x nH@wϘ +endstream +endobj +456 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 658 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +457 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 659 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +458 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 660 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +459 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 661 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +460 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 662 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +461 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 663 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +462 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 1148 /SMask 664 0 R /Subtype /Image /Type /XObject /Width 1450 /Length 60521 >> +stream +xw=^p ݝ2EevFҲ %!%HHHn 궶ئ!B't0fL `fSl04|yGS4y>zJGl|G +XlRh^-Tb+bϕ{޼]lOEQ(rCpy{ryS1Ҫ* Yk@ڴjŪ +XdE} !/7Zz%*Z&(+-;ם U-5o:Xk8(oPbj\P誺Z˕D׫Õ(OT-1Ƥپ +cز%gU}ҧ]ͨڥG5AYAse<|:xe\풁LװzQFj'[.[DPߢ0\/2VK' T:QWUUԻr'4-fG+oRoVcVP.k>MEں5YTW7zDXB"PUHu8iU@)i['},C_ID&j &ҏj*8uq?\T=+Jldfb\ELR7\F5QYvl,TNm8eͺFҧssR Dj#QIKVSS}LxzyF`ᘊAԑ^B朁hVa*7ћ @_Vo'W9F1M9A=.^UoڈBE H9qT{eKDGVzEy1`T5:w5ߙS#oT1/%誺Ct0JϪȲ8S?+p?n$ +[4H|`F<ɓ@3,uWf!<Ѣ0JK#PWC*qx}"(SYHQT0JWE]T*=voG)/OJ_N9ڞM<_Om&-Z[**5O;If GFԐ 7fq$K U(yE^t{{zWDB|_%'WnYj-gZ#FtM25bDwg +jfՊ[tEqoQ=1W,N=ȢSpS遺%'}#8H-~& +pjZMVQĬ/ȚYS-Zߥg.+JF˭dOu *'JôjxTUuuW7QAhդ~W4xrg\㋶Dsٯ!D*r(dЧ*Q}e3 *EHC0-?WzVqeBܴմottt۵:6dymЖL ޷]oI* ҍozWX~&&w"g֣˿ѭ;P>Z3IoYW(RtGbW-<}v#t8 éE E=EիFq[j*U/Q"!ѐl̗jj1!7nA`{E'>Ēk-8qT"j~Wt5C+&5ȏ#j%Sn)k_q_n>&^X]7j֪8R)uȼK=;ZEQEI4ҙ(#5{4OYNIYQ?Թ.锤vMVDTfM党Rܨ_k` 7l񫛩o$`nR ?asQrgN%[wuy[vGUu Bβﺢ:na2Wt=Jqk"-EfwqLZLL5Emdt"cѼljrSU.[xQFi1Eq7aw"$9sP/Z]>;%Qqc$ߨ6`-'P-'GrU2`3>B;HkvC%ѫȋ!qA%(w,&*k&R_G*2NۋZ1j1w%Z1%xXP#*8jU&i9zr^h]xR=.?X;otH_D+3iȍE74Z7fՋޢ٫WzEZ5U\r9W%GtJ*_Zȑ#M(+k_ 6F-#QrY;+U܋˾useX.W^U3iEMY+\el`Ԙ\/7-vӒHz;(V$aP7 'I[7j:n8DE[jCoUMe2}بxnqB 3GXq#ۨ:i6Z%㛢wF/n5/z5SKqט!Tx/IQJԲJ.oj7VoA4V#oaP[\7 gĈtwo14bĈ7]Ʀ0\ΛfY͗kJo\w ɉEU/*-j'|-EB4-AդA4ELhՠXUY}K<*^+FGk:L\ 6P51ԝ #Fb.w9"բK*L+~q+}FV*m&b.!]f׍;-CtfdE7iZ}TԢ'bjrFGUyawCE$<(j#$NQm +Ն?&iUTǨJgN*"Qۤa$;q# +Hyg4?Qqä9qCPc^2ʠjf)N? Qَ֒hw*nEv+ ҐRFU6#߲f9"Z7D?+}]]^gH#ec7-%: -$sK2oHFFrM>ǿDy_Kn"+/qܺ5͗ +iz^4k=egDWa7SѳvѬj%;[bnjx֛krz*#=+ QWReJ986 ED2hlА !B|)7 7ёRWk,3kcIK ˬ& +gb=ӬRS.ZfOz[wF]WXWރK6JԙZZ*nʑ~FG +zXˍ--/i 4_d&jsĈͻJ#o>DSu:KDU?o54.{pEˋqSʯ( q/$"z)&cґd'S%}X-Wsm`qLij-HȍAk!3dl!)|d鱭 K>釦˩>nߜCfwWZUMjf~G]UU{Ԩ܈pQư  ?X&I#l9WchJE38^q!PlMũ,Emx~mfcH8i@Q#Ɖ2q[M ehVj}늛Y)JŒ,&ɖlMA]7xzB񡞊+fh(zf\PNW$'j(a}T!OaxZW+*׻<ƯVh*nUˋKY5gXoҁ90uYt٭^v9ՆщcɹލUDS԰{BjIꞫ44;ijVPkYBI1}fԊ*FJE\e`qAs'KFtO&HuRİ 5LF +%Ѱ_|D{߼& /ddvѪzA6Ѽf9^t bsE"Ό YmNjQgڪ7n1͒Ua +z#&/S]/7߭u~tttrǙ,K{˽RhXb{caZ˕jW–hmzv4}>]uR7N~⍨!PK4kۨLܼKϑ @ovsah{rCDSoKӴ*JIUS${7к#;/>*/: Bџ˴DEC<"1!e]Qxn&&uIB76~LZ#;TF#U_V=^s6Q3EѫX ꚖU*Ews_>gĴ{1RA]\AEł,HuTw[8zv;ن4뢙AUtV#CqOG`< NPK\fG3X(zI-|adoњKrbErwkADoF C.vq,95LFZe7Rk&^Ve˙~5ȑ=YܶGV*[v[(5tJ Ym)؍E Ɔ\TMG24(gŤoѭo6[t:E-UO0 $ogv-RE7iAe5Ҿj$@DLhtf^uQ 9bͩ4!hn#=;kjF5'B +U?=jԭƾmh4Q%}uDzjhf5/nӌtٻ?#v5O]" mJHFG/<fˌjh!ݎ |+r`SKRw'4ϴ9Ƣؙշ3mne^w֛cE!OWlt/]49:lۨ4EnԝK.=f*ѫQxhhu_md$ej ΒQ!ۇ7uhT#;Rw&r t1۠YqԀQUv<^-oC-y JsfQ+5u-ϔ[jgvNzfSUA0YJ^A28֋%ѡXt5?_ +%gD666ldgNt{{ Dٝ՝3yCsgZffV\17݈>]he'U^#bLêW*9fZ"Ps,HP %8JA϶ǨtI{B.経c +u&Qsk&i* +\T{kDG4gK7T]!g42UFO4t5%*Fl^lJZ2*{2``K9*ۦ  ݭhYfp+Z*n-l[?FΜߙ覹\g6R{dSR1hZCwZ>Tc0mx[bd#DX TWe'ڬ*%ĴE#QY (g_5-_c"\<1:DxBGm&J( ~23sH&wC'WumPt #Gj~~"mݢ%€'.xiAmnVǃǃکW>ݩY!D \L饊]^9w@bMWeNfv+ZBL(mt(3T=Zv(L-@ h%#uz+k ? gX#>-:Y*ՆVu EĀZg(?8+N0Vq]TEj ߛ8M?r+w*^MFQ4Z̵ 1lƂr|c*C6?~KE|jo9JU5SGzW4t}ziȟGMEj(u>._H}⑚9+.k#s%_: euZ?5zfCS򆼐4G"ϞxG7CV)=7i^?݅vU,ލZM3بGnM"hȜw4PFNmhd1樢1d'Ӭw! (nuvXf"PS{)!Vob+YH. jv鴜.U4!+j4M/8bF5iTU-b(K-ާ0Zf<9tŵx9Y`\V( Kr.QSSPiozV'jΦolyu4H`'N͈;Xr֊U٥%ƺպ:B܈c<.ԇ6jN:)5K亚ݫ-MyOȈjW] 'H ,f~b-*ݍ\$6n^1C߫<'Iolqq}w.N24V>dBmMmhq +|ԛʗͫD*ӛ)Uv^eV+X7i:䀢v=5l![AXH|STIԪz< |mTe(5>s6Gjp:=cDQ##Jj^TTE'6jO!џ,SK. a +R4s%\9Obh5@dT;::̸Z$o3.T$H"tΰ'2lKOʊ{UO:y%Mwdgdx6;/ҋҏdByy5WGA&9ۣ+G2dbǴDSC"F3p͆ѝJf$b.F7-EJUFN5dKz܀hh֢ZUy +Qw挡w QIיGvXJPʦj]+H_j=jf{9m棲>%,RrCjz5d[A4-΁H|El$o'zdTSWCY?C9w}^oAW4JvPůI{q>cA !CѪif|P哢->u+G NZGB;θ)zE7nT 8),_:'HO&-w6Eΰ͢JEP)n|卛8Uʚ*tuy'NsSz5u5^ID$oΪB|iԄ,t&d1 c`҉YMmTVȉEj,[SEY^# +|N]&ɢS)  C bOQ">Zjz|E*bt>1[8Yw]QWJ20iJ7~B&j8a܀ 81ɄOXO(ژ"dn8z򔡏3n} ܸ28eȞ7 cC1m']60nGnQCQr(7_loIju +H~r{dDޛũӅvFqNNH"*?aܴhYw]'ǧNjי-EgZmrYH?rJd' Sj4yY䘪7#+ݚ~+W\J\).O::=~b*˒:1 :ԽB00iYb<9^\McrӯcFq#{c+qG 0y/XU0iɓ28';0O>S8紳;{qp_twѥ_4}ɒaz?.K/r_~K}?fsյ7\} pӲa8no[o;#{;nj{ޥKas9fͼf==c<ģ~'?Z: ^|9O?99<}gaʿ;K^zyڵa+k_?ͷ^ƛ xͷ~w0 .]/^xɒ%K+WZ+>Õ+WR?OO?>/0\n_|n:O/R_Zi͚Y6YSO>OU}rdQ϶|Ŋe,_>P5;UZd%K_X+0|ŋ}oѻウw-R΢Eo[o[o[ +ss(*w>ǟ9{f?㞙>X3yݍ7_} W\}?f q_:<:;駟>9O>}`p O?ƛo[v]w{ıc9?vԁq7[վkz0<~vɞ;wK/pގ[mo|goﴳ/=ַZ/-˵k׎0r;f`/ʭV`vw>.v~8Pss?[fO?z+W`%K^վ7/X^WTFt'/ +ok-#i7{㳟r63L?ԸdgVjlҏ#GvF,|[S3Ad~˽b)7Hoh}f[tg~J^o8w 6~}o‰䌷*x`7+[N0'ocɒ*nUrU|Y`w߬Mls-Cz9ڵk߽2]1׾֕M1rdowq_[D_zGٚ5W)gY1jԖj܏O?t?7-|ͱ'|ٙ׬av_yMw=C+54a8>ܟl.2ffG +֭O6~g{o[e~˅B+ΪmoM50qp?:J]3Y3a?|{%R~ _ s_xqԓ<ҏU,ÏlefGle?vt߷v +nDlg~t4F 7A[maҏ{>v”+a{;i)Ÿ [ҏb1ͺd၇9 /~Ef^+5vcv,ʽZAv~ݷeҏ[,d;Ï{d~t-tpdamO~e~tX?o@yY]Y{ 1d;,+E3]Y-_{MjNdT>qh2矗j}jCs9_@#!#W6h%KOYԊVc`t_|ٕWg~z5d,(Z=_@ۭ\λot'x9~x 'f~CY>ݎC?ҏbفAo=3t%P3x q=; ӏf53-hyN=k,mO=z,=#YN0l,|㞙{߬7Òm?\vy{C:qi 8g?v̟;:#_~7iʯG{f~ʽ[.3 3z7x?nbG7o@]v5'~^xo,ϐpzOf~o:|m䜧Ï>~}K?l_w<_wrǜpd~x3mϽ󰣎8fǎ,ЪhwÏ9G7&X @gS~Ï9أ7~ҏYiq/s/[n,m_d'd~x.h|ŗ=,0lO ;rG7v̄s/8tH?@ۭ^qo8v̘ LY æ h/[;M:qܤ3K?4QzuF=m=긁SzFfGѮkvcýFŋN<4ףҳ,}N0lV\59/ss=6ۓӾ~z9O9+,8If>?rĉ>3K?t7 mϙxl~;sϹx\p9d~gN+uEgwaGC֙؉'^9Og_xŗ_]au108uw]4|3Ͻ;f=HvNôi+f~g XsϿ0M|{sŗ]ywe~؞a{&h_g?WML7]^?oyz˯W\ea`#OzWվW^}u7ܔ]x1-h?|;ޝ4Oe~莯Ό C,20`f__>{ٲ}/#0-y10<^20]_ hndg?>]|!t5׍4% oO=ēsK?l_i >K/rpکa~{;>9g{!Ciý[o0_!|I)`c‹/}0 {ࡵkf~oýlϽsaֶf˗/,0Tn{=wދa}"XdIfn{bN;+ll˲T<vmoah[-+>,Ѻ3v:}< Ã?棏>8QO{͌ ;k׮&d|GG'|a{߬/lҏ7a2)h3oO>$ Roڵy6Gts~áhr_|EdcQzgf_|QRn_~MIg֐ Imm0\n]֧6f~|k]=6ܻl>Z>L~ژMU<  S٤9]3\|mٚ5Ͽ0OϸgZ&(؞QN`87na]ژMQEt|>N5k-/#ogy/ޢ gVjc6GNr~1)3s{o|۷ژMѭ_y/?=jc6Gw=L S٤y+gG;jΗg,߰C讻Î +pUVUJ?8y?ůzfn/M֬YkyP>?{ֳI?tۓ~Rrs엿UVXk Õ۪9U٤s8,Ce˟~9xɒyLg~n]ߴ}<࠿}|3K?L'_H?@{̸gA9R?cAj=Î%mr]wrıjVoT>7vǎQ?ֳJ?u㞙HU0fMgs^gރ=Jg~vð<W_{MaWl3-_2Ya82h+V\J/z}Ng~z +h+V98Ya:M Lmw8l3\_ hcL7[밣W٥VwőǍcNP뙥\nS8GSO=\3J?Ĩfd-aF>Ŋ,zj=&jƛo;qj=t QO@[fy ~M3zZ&,Op<9_@{j?>)~M3z@g~\W/~'<֯ǎֳJ?<,f23,|C?>]wMa8X2)TV԰mcO\J[Ͽ0Og~If]kadYah]v`ZN4mt]!m;ͷVWOTYA +;-ZM3K?4la `5ēQOznUg~b܏ oÌ S٤bS0hoc͚j}s罨3K? 7-fRr?DOISNV٥O򾑴׫&M=Eg~oڥT[nZ+3K?4+uu7ӺuJ䟗\v䓆!,o `矧cY>|۝3z6QO+n5o77]_gk֬_zEg~N`T}o7+.O?鴳zv9_oe?ϿhIg~ؾEa~6xlC.V뙥WI?w-$ךKh?u}͵/4L;`Ț8ysC҅#Ӱ(ġ9g<귇^rYyǨ/m~$ȗ/@~7ሹ./_њKh?vO㰖FqQopؼ_҅WڏQخ^aM4} Y-|qy.~5~I::ɢ5~*4Hab~wX*-yd[s)GR͢Z +߁_<엿kZs)Gռ'?5ߘm%],7j?bp<|cTOcdJ547h?[}8ko%=㺪YH:qҔ0d]xт+gCKZs GX͢0I8 }#8 kn=r׬^&K~[s GdA{h?|cu}yZs9G%ɴ@qݻ<\[J%i(ʗ?X[B&cZ-6׾}qMڏ $ nM(yϯ9N=}#ڏ=iXj=w QGFq#zZ 0.hƍXFdayi%δ@+Kx27z[k.涗I} Y/=ysGz 0d}_xz<}o\BUw {Qu\{gV;mw՚Ki?J5 Yg'Mm1Cigx܉3.{5~D,5Z#iw?R~N iCyoJ?Ц[Zs)G\z~b y~|w2ڏ8 ,y(ʻ?7[;5~$0N#Ph}?{[s)G_iXWi /o5~ ISw u_x'-{\JEqi[Ta-| +s«^<޻CRڏ4H@{~o}leq%i` P8}k۷gڏō(=wر#_>?Xb=nJ=ot︿oyぇ2ڏqaw#ɂjj-Ph'X5~47&jim:m#|s{?A|r~Doz`/I_CKiQ- FgaM W_}6}o-y\BQ[o}qi1CֶmO>o>c1+0v0^޶o>K[s9GW5TAl-0m{3_'ӎz͚\B6&izx*hz4; j{?A׿ug>^|qkk.k0nh?"߰_?O>=tYk.(׿#du'ZsG5 @a֮{O+Kx5~TJѽQ5lۚ>Ǟ//{\BTRM+q:q60dmذ81ǟ#lQkTzJ[TeqS;_B}c/dѵo}\F4j(Šs|S_\Nֲ0(W|_d^\B$i| +S?/|Ǟ[rUk.$ѽaOj?ɧܗ>螟{ZM?5#nV⁇|1?kJm?jE}Vm:`Ⱥ}>{Y5~DqVA5 ISt tp'Ӧغ[OkYT˚/~4$m1oSe͝/Zod۶|3,jѫښKj?Z$`y>y߿}r[siGTkDI#L|w''__bgQҨM YyOßXaCk.6žF6Ru)0n|'߰ack.Fؓ=ISt u L4eKmWWa͗/@Jj?jY(+v\{M۷h%a7|Oƽ߶Bk.F↛n񾏼‹#GXK@w'{?[[s9GaVz5H{뇶n-oKdAؠT&Nڦc{?O |r!ꫭ#eaQ$(Pڏ,(Ė^xzOn;JxQu(U>I/wGdQ(ƕ_|R_z\B$id]6Xxբ۶ڏJ6_JRh?v~q?cǿ۶WZs9GWOo:R;a60d~'z'xk֚Kh?¤&QiЛzx*j1q㦝6|mZs)G]}-q8}k۷rڏ0ɂ/׿0I5};Zs)Ghh?"̿_~'=wQja- M Y믷rڏ{lh-0nϧ~'ʽRGq_f-PRڏA7m~;ڏZE688y1ckk.ld8P9?h?F+ޏ8FG0gC?r(˗8 9x ~s9Lj 6mUNu'~\Fѿ񶳳3WJUJѼcx_ 5[_zi-;6/ }yGut_y,k/_8`}#);v詿;%Asm# àֈlg;i1yܾ}G5-$ٰaâjxl;mc9?1#L4M Y-s;~/XB2lذjû@j?_(ׯY.o<~Dƨщ(fO;vF_{8<~ ,H2PfOV%i7%?XYg=<ꫵqc nAQm:`کxW{cn:A5 t.^׶oXtOۏh\GJG6lcosL/Md-0< /^ǭ[_:y,Uw鋒,Qm:mUK[{?"{~ ꫯ^<~q}ab PgqcGaO#eʟ;fg o?zAox 樣Ϛ}vWYۏѣG@A[T o?4jK &Nds[xբ<‹GO?!E]վJO=J͏_jiTPۏJEvYsk֮۶]<~q=fv9 㙕>Xtx[KC~٩Xr%AO$uPۏgVy,Nð֚ _`۩X♏}y,tA/N+q6a ;לnذ񨣧#I;z8 {Ҋ/_r~+O(+ViMޖǢۏ i~x )}yy|z y,ꑝ/@avj?zz'><~QKEcɧ%cJUK[ןFzvj?xXtQVz߄St u7ޮYSXtѿƽqsn1oSq%ͥn= +z 7q۶Woqh?4i?tDz's_cGVa-~٩Xē{<~t&Q~EعX^jn?:j}Zԣ/t)0vj?_S_ZKh?:_88i&G3O}e ~?qRn?~$~ JUBn= +q_ܚm^ypy,ȴ@qvǗ}&iXKä +SK?ů5 Q@!vj?y~y,JƎJJOg]FPs)0~c+q_VldoAv9_q5y\M8"Ea6nJR}#hvzϯߐm^yXtig(N~,yѽ<~$YTKo~<_-4J{?@vj?Z<~T٨4k^RK8m1CV{ۏSԃ&St 5gEݐ'ZކqGm:m#n=b5kmۖ>dn?7KP汄C퇍@q~I[8mn?FP͚q=(No=IoUE͝/F$$З/@n?¸yix eճ xK/?<~Dj?ufǻ]A o?,a[(n?aY%(Dۏ9R(B{ۏ iTi_=!.슫tǢۏ0ɂ䍍͡h1oS%Q- bEy~M><~x iǞv9yλ#7^~8zZrQuP):3cGW5fAl?zަc6qV땸4҉!댳νy|eӎǢۏ iFkK k|JU|\&n=~Æ6q--y$Eol(̴cg#?UKh?/jUmn=ǵ#=ͿJwZ !K{y|h#9/,e]4faOZ Pۏ~Dq#z +b-7n?В<~DI#(̩g5Ok2fi?̚}QGOUXtV_~8s'O;&W.vڏJo$( oAvYNVq엿cG$8[`]<'ܷxiXt&0iTFмd\X Prڏ07m^v[qeO>ۏLs?iy\xբy,x 9spW.AAl?Z +q7\v|Hn?*qY::w4:zT!GטFטFgwSt u 7{y펻N9+EfjI#6t T9GTk(Ⱥ?|E7m޲♕y,i?BE8)._>%QҨ +2wſpD\~/<~I#5*մ3I@.!<<\W=4EZحwTvUAͭ =<~$Yg|3N+q6a]Aqmwyyx5ȢJUz\eFW{?"]O=-/5y,i?bͿ_~b/tA1Yi?̿_~BKn?$mqZ~E;X^$i%=0\~/<~t}Ϳ N'NҦc㍷NcGXZctq%_%+Ȣjĩ{?"lܸ+y˖k#i|68 45t uWp!y=<~DI%l?_!+#o?*I3 W0m:`Ⱥk~#xT<{?vk?y,7ގ&x ;g1o>X|IW땤d%a͍a10Լk֮[V=\n?hDqD Eӟ*^Ԇ/Iݻ@AƟy`%<~Yd]ң +q 7h8wſpD o?iؓvttgQWt u7p6Y t`wMo3cGFTY%n5_*5>KO/_ 6\l o?joպ(­ݱyy,HfR4s.aIiGqw<Ι;ÏcGFq:wduU60d~{<<~#J50[`=):.x9]ۏ]Fujj-Prڏ n /.y<^籴#(=-wsοpB~7axs~cGj:XʷۏFg1lذ1AoTm:`Ⱥ}X#:G7FI:q [#[o‹#J_W45t TG1~(֭/wy|fG{<7ڏzԿM Y-y_v9 ޺|G6;Q- _lÏo9s'NǢۏ0I$fYPKX }s_fϚ3rۏ6]V!Ǘ~_XxQFvku lk'bճxw_q5y,hz:zJO#(YG#Ƥ]=_|+CKt.~ eI6{ܨj]ɧ_QG籔Qv}O=i[cT@xc|o?86lػ*QЛx ϬcXr$nѻUFFI:q60d]ǒ7ކIֲz{G#it(;zzyxGcG-ToB^+92o=5ۏ0lݙ(š>Ǟy}#)ŷiXk=#@=}?u7|тX|EI1~Ԙ4fQTq<ę{?A?tY[fgXZ 60d‹}$yΑӎc)G[ooGO=L[O[~8%a.#F60dm}=cG6l;vU60dw0elѻtΰSpĔ60dk?yk̝ۏ$ lXojoT*6*ð_o<>~s䱴#N@^}ڸ{?@^۾=NߓY>y,o~RcǎxgMqy,hT 6lDgUKdFc?sd߈JMۏ.aҨ$Τ^(W GTkFԽ@n[lܸ)IZ/5ŧ^ޚ˼c`Ѭ>j{~˞|5~9#NԚ!ku'ZsǮ:lذ(zݧ ~EZsowcvy0k_&NھcM8b3+[E .?‹ +>v٥Z0 PBۏvaKhQ~oÏ˭yWYThQTl?CKj'r1ǟTho,|~7Y 3g;Bۏ]w߿6mi?b|yZgM?iaÆzͿj=0d};߿ŭN=ęOQQAA,@&NҾciXSyW\rBۏv$Y7Fx;*Y%Y +6oҚWz +m?jY?@v]ޏS +m?8JQqZӨ`}G[s}]YZ4JޏZsu۞4fAUl +}qM2ۏFk}tXMZ(YZHk믺BۏƈiֻQSTʸѣǿyi0< ZKxeOGhI +߁_药]ʼC«2ۏwkt/_p!]quk.x;vonMƵڏ,@w aW71f1/m +q~ݭϷ?~6ކ/_7ކ5_*7IO=[Ï>G +m?aK,~8䏇_0ܶqV|ϻp~kn,fak0dzskmx[KGqZ P spxW&i׃8(gEE]yOVweaFq_{(UjuT~q 6l yU+YTh1*Il?C㨣Ϛ}vk.P):3jevt~ę(δcg5emh^|~E83s|k.mW-z;`Ț5죎ޚl?GvujiXM88iJ]{/ ?~TzGjY4F7 PBۏ7o=m +5Okܳ*G՚^*=/:[OGv綾_^ēOwT(7iǟtq'<}5~6:}fÏo2ۏ nn{w 0~}c]F3mgw_WO}Sw |3pPk3w޹s?~tua#xTk_8m1oG= d\4/w<ּq 7?~ _}h?Bxf{ݚ۵Vgg?ۚ۲6j +s?O7Fqawj6o5اZs|;wvZGNd-06mӟ5Ͽy_Thq&͍Q-jػ@ +m?:J=5ۏ IZݽ@oq[5k[&RIh?jY%NiT~}EQ s-$F3UF3U%D29M2lM0L09M0`0$9 D`r FA RӸ>_WSzkV;Wrպsߏ Ț[K+Ԯ@'|0ĕh]Y?^:Y5J{J=S?NX`aj8}?  +4Ho}8t- +.\a[czǎM8FIo4@d+>خ8Io Nֆpq(;rܹF򫮽kmK-5kRaۑ`h('Xt~7?~FG= k(+rz?dQј{;10U?D蜍zS4f[mrƍ~L:g_rO\?Zj֬=$z +tls]]]|olPO{F*G_C[~\|^pq{+ 0ƾ|r[a~朹o,X8znD#tv;v}G~IoQZ~ޢGÕT:dλ*V0>u舝ww_?Q-386rN:┿3SuN׏ J8 ?۟T}+ $A~/F㣏?ԏqA[#kh̳O:5}?Z{Kj#!p]h4N?Ok{ڬYWWw^&'}?ïFqYO8zGV1]fRo8#/G7G~pQ:.{{h\p%uN{#RK/_Q?fZgFO}'׏8OY~q1:oFiw~Di]]`OP[3:Oɿ~QFiз{' @w)gM>QOdL4Z@@GGtg0>cx t oeFc<6y ]O\S? XWG~/ˍF㣏?ՏJ_ZO'3iqKYOOOoh@G~9'"|I84?':39N-~ f]]]~Խt^x 'P?j/L֒#>1u]\quFceWs׏( lؤMr GATP:7<‹FczGt@LÎ:{}nѸΟ2<6]j``l-~^U.#ՖE}?x+Fw}szn>T 7N{h4>cN8KH L @Lun{h49£_4ӱɸ0b_q;w}F1)GR{=Qjx2 tw޽n{6u܉ҽKo|6t:aGh4fqW]^th1vpdА/_Q?t@2n ۟-ۍFf?s׏𸁉a~3vFq1}?FG?=0V z +tƣ=7j(R?y'_R?V8)t @i=1Mܶ,ӷtO߷[3^y;ͷ^thz?\ +U.#MdC>dOj4Ͻǟlw~Dԏj~˯͚~\x=<%J{,s^}mZlh4Λ2c^x'AEZ~37OS!֓G'FicJkܹaѸK8zG?;81,=еnlw~AVGtn ɡ~Q'c{'tg~=Og=L{#O{zzخ@G&fѸK?zYO4RA8d+h4L?^tFiea}200`C>裑Vi֏K.?c띮g/==ߊ!(O>Y4JFK/?y֏2WogoR9։]yu{ymەЉ@raև=54@\;F17r?Y?])}C꫋/?^th߫6cH[S'}9׏j= 4^tT-Yn20qdW_9Q⧟~wz޾:Fˮ,t@>[4o( YX21_9t,Z[E?1PZ7vş~ǟ^|HGR~*k/;Khqq~V[y +z۟QjsV^ko{xbw`b:V]g7Ϳ~To9?Λ#3i׏h$|KhnD}?z'>_yGgsߜ}ׅ( 6_ʫ843_,'hꫯ +PZ?lg}?H0z#/ PZlYO?sUN׏0JoR0_>1+no/P?Z LE^Q 2;{Ǟxks<6N8Գj-Fi5 ׏ѯ]ZV]O?쳢(f'^z'ކQ{Tz駟:~|kڋyԏ8ԓoG-Z1펻{W];msq˗&`>D:W;{9bGIw7#J/1{~=Lai/v~DI>R K./t@s?:sO^t$׏hRO( ob#0NG߆Q:ki7|U??1^}^^^̣~iPOH-B~~]wb#ʪH%E:s'JGoB6hx (ϮA>#E?4PZ7v޺+j=~Å(oQ%ag/_羢({po}G;]?^sKgw}o3lvkpJرIqt/ˮz 6nt~|F|ݏ0JoBgَ1+ZJǷKԓ8蜝wK._35f|ujݏzr9N:s/{:Jct˛s:`ɒSg[fQC5g^ymm^ɭ~qzm:sλpWȺ7'(t@Κ|OfdžQ: +Pfz?Ξں?jS?QM^xevgΆG5J_Yz|e\KvIChSF˯Z %KN#N+Pg7NJ9s_=+nS?(zG8 PfǟtIm5kT?tl[tڍ Pf'rډ>ms}?qL}$kev_~ɧںyKeZ0+ԏoѷgs^37笶8[+9ԏj4H5G==}k7W_Vr(鍓8~r=~ݕX²_~ek%Q/;?N>%h~ldSNO5G?~h/_}y1G{TZOzYE(>xxt>Ohz JO;]:eѢEĕlO?CGo/))^K'hѢJ>/J+ChH֗_~?a١eWGPOQWSgܞ|xq6Q'Hez[{ɢg,ArqRN[ ͷ~ˌ;m#FO{[&'עgв+%˯pᇭ~uO,1CCi7:On#Zo ٳ=K??uWH l.z@M>JkZՏ0N$a=yPfO=m>Zo+Qo~W 22XmS?*Ag`S.XnV?Õn{S{'_zuOFYu˗j=q-|G8 k(SCe67Κ|ۺͫ~aU0ζigٴ:ws_6i0Tmwܵevͷ-:w}o6aVYl.E([fܱܪkߑ{AUGgw޽*k~=G- 첻oz}m>#>7tʹɧ~V4GnǢ({sΛ3mn#j֒-U?zdcWZnoW?4Z6;=f>ĕV~m^# Z*lm/z@=9lomN`h-z@=̿Wa-ۜG- #λU{9[mǟlS?4gaҋ/KGuZɖ@}ɢK.j[ons'qVF6E(f>󾡉S^ֺͧ~n4͊Pf_ug\tI6NTg\g^pQ: N iOmm~]oȺkN9K|GVIgzz?.KG0:= +|GoVY[:+Via}B8zd/z@ -Nk:W'===L%7Kum]V?QZ';GOeW]sYg_u~Z %E#=%]auߝ7W_ZՏzt5{wPfg59[iu~#J,÷~e+ֺέ~Q?VY{9s*z@{򫶮saEiXx_O_`a3lˆ[u[IPO@͸ 6Y:QzRLÏ>*z":v7zuE?7Pfoŗ^Ѻέ~ea&Z3n[y֏qΏ6}WPfoQOWh]W?$h֏llK=fqW-Yuc *g;o{E(opdunZԓ_v(^zymݺέ~Tj]3=`I[Z^,2{G= kin=_ 2{OXu_PD6_Ϫ@=}C[׹Տ2Q=[ 2{WA2vlٹ^;=x0Z׹ՏJ_n@͟` 7i]V?z'A4o{EX"|׹ՏeZ-=dяşu_~ŋsg[Ǟ(z@͟  ,\01czzz8bx3lq;Տo}[]]]am?:eqzՏ1jM7ޜS 2[hQg/ٯY?8;~z>u~ܷߞ7|GW]朹EX"{G[mӽ?Xe_u܉s~{Ѫ?Nw{3쫯 +쎻yxc9׏0ζn%-9֏zϷ-z@Ʌqv7^sMy׏}uE(0.ߒoHx E?:DdmǞks_yK^|9Fxm@că?slmw~mE( ˞r]85n7ݬ~58<O=Ss$7L%WYcO'Y?z֓k˯=y̟\GqqE?:Dz'_xѴ[?0NOi=B?Hm60GJg}-0K.LZel'V?!0ڿ^hLZez',r+}}FcM5&r4FNYmFkm:?nh4{wЏ6Ǜm~\͍F?;l˝avmwh4Nvcno}x葙Fog}o{_juw~&{={f=3gN5MkCڔℸqƤ-4MʶZmId)%^E_B?BZZ:ZI1Do{ϞjB~H݋r93sfz?'?CwΝo='ٓ/7;w<}g_+7ם;wͯkwok_o>?o ,X` ,X` _o~}替^W ܹo_xGO}cwַo}'_?|~iΝ;7cތej|{h绿{WGi?nսd?i4zX,E5rݱjrMʖףPP3}ZI35݉gguN@T]{^p?:r(32 +gՐ?.g !~ɪAcR?wF;QTCTaԿz4O_Y~&Esft^—g5Gsj|MYqh??Y{xjde¿zӟ6U/qX>D22 м =/KOTY1|O}kLIpK)?g벬Jq5^裮a|O=SϿ/W{Kn{+6^x_|ۯ}嫿Gҫ'zϿtK/_m:ǟ|Ͻtۯ< +βVTc^ Iձ[,?z0G;x"D#끀$a3n{j|l%efQ-ޕI^Y'}Pg5P*#{ax/Lwd=!0.ZzH:GjP#펗igu8::p>_OU+jۭzOUO$}A#ƴ?gcWHzhq|yي(^#/ە @no^j`gr`r\x`h`3kxqId4+)ָt4QްK4MRp{u#15UV]Ru~#/Ը-z`~ PfeWC#'iVHCLD5,7kI퐈$c ɣT62Kde8Nȃ!gjXv L50"DpAk輹n3j.?R__D<1^lR}^1 #{O?z!gU)Ū@qhlG֩ +X)mAn4ΜǨhS}MX,6Qm$,;ujH(fX=jS^. G?f亇fՈc-X^mgyg^fꐗM{ |Lc¿6g`Ee,ʸh2t{z&[ÖjlA,X` , &y$eTViٲKPN\Bw\=.׻8=xe -d塳1)MzpÌOm۱ PdU-+0h"<1c/J b^B p$"MoƢθ)|BaབྷPH" F U Gtpo w@ 6} +<,駀z/ x;s-TKeh)>|,~[r9/{Tuzr@kXA\Ohԇl]OdMx +hʣYx3 +":Lmz-7NmiDdL¼1U=SWNK"bY8L<&P"j$Yk-DECVuBbڤAK0Q ~YİeÞMMZ4\u ݞwWNj&wez ;XbLf' Y \P0^RJ`?4eD#G`5*AX8ˡM) A6@V4׊ \ Q-66B.3341y4x]lգc?2;ATD +6In%KeGTpKB 2T3VvH:ONE?I ;G63ABuQtX=7+IX(~4; pM DW[7X! L':ZT&r}5˲R6k[ْOulY9;۲Mʖ. qD;cF^;n"~q[^`P#J_w>NvSU{^ҪKBռqqH$s`j\.)lbYy^rZ]b` ,fRDT{%Kt'&)pfd'nbv. IGy>c,Q+- rns?vX=5q !i,k*m!()rWi5%!j=nu"7X: +?PXWX-a1%r5g-1n_ѳKWL~;m25q 6w}e~dV @F>m +R*z:.8MI2FQs})ޭ)~˞ecٶ{T,I^NG)jPkdդ#CubG쉆=5L1q]GҨ}G^Vc"0۶2 RO+#!MG#&Gy5VfnM32}`M-lN{6"peny@Ry?1^&SA~b~_̾c}d.-բ3د{?2MSDlNXϘmٗ1i\ŭ3,m9Ɛ`{-A-@(H(A/$:,V"P:`?@b!b~5XJ\e3Cą&V4363Z'ZԠ8é`r3;YdWdzaP_S!s1ءboS"uFK\InNP=x8\ '*tQ͂1QBe *h?\Eԙ-aW| q^ #!DZ W6xMm6m®c?\Z0%2П YyRxٯr9| |;??OKSc}`^DMEXꉣxY,בC]G9гlx ȇe5Q@T;OoD< ^HeI(yDLH?HiE0pC#Z`Axe1SN=@ `R=!tFs:b{vpC|!;;& "fMD_]+EJ^9RvQ.V} h"gY.IgC*;Վ]^]nK`5ͻX1j]{8l)T I9ĥhCeAf & 2Eu-x۱e#u$GN^6ޯoG61lDC`ƴ2`?2E9yѸGr0ChI( +~z"`rʐjE,|+Kی-"NLO-/j@Gd?fQ[ݺ33SZ?d#SAn I>A:~te'G60:MX৪ ! :]uϣ ?ZY}4dMLb:AA6A䵑jr'9p:EI >dϑWPpEp3.IkͦIp㣠DR=*WEI g5 y~ƪDܟvi¼P;IG{;71⾳M̽#˪H\v@=]UHn^{(z\oᖬK|OP0t95`z +T7RO6P5fZY5#8l%5Zā!ѝuyW=la0CK=\*$~p &f S\bԧ}S>Z:H\5(#|wPm9>F~DU>mH\\g ո\E LUfFK.uٱ +!g ({C6Au]F- fe +oiw~ͪ_E}vzs^m/f8!Sl +5 P{iXL6وwzj{EXHOXFJTɇRb ө2펬24Rcm[ĩ#=hz62󜛺!Z()$Wy#e"J!l@v:r:BJH c +47tF?ǜ~+N˚LO'J`sLEMryzVQ.m턘`)CEۙ}6N{ൺn@?sWh +@Z V<юV?Ls4zR& 0|' 3b5<c&`fuf RʬlP⮹ɦcW,pS13t fyw$K6lx&30g&IUb{f.Ne}EvvO>D ZwnR4T9q/mS=› I v1l [$dD;}EREEL r$ Y*-eX&X )KD,XiNb3=$;5sDp+ AnMSgb-rzcue褝=Rߔ rIJg)F2TSsAb7{p~TDv\u,.xaj/ags{`\=K( Sf*G\ Z=jn fCRvIyH 9H"Qaܜ\\q>&Eو:$EGՓ"~~[Ec`Erd%MACGn=δJ?:sڳ6Tx=}HR YLAJTOť mO rPoGI)U-C)pր&c(b"QL訌[ T`)-"AM;g^S# P*`Qtqi18UUܒlU"Yl> hqyX8.M*9@5ڬ1,( :/F"0x'\: `WC+NLZjm (f;HQtÊa*sHR/Rb\&ײtC-:c@S.58hsDby* -6(qr`sb4D 4Q'vBc՞)nR"?@8;pmt nNflp225_yB}ڃxQ%I`W&Okt/!@3Rnӵ-y +x8HsY=ѩ$qӑ Z{,guF-Yw^\(1׻CC46Zi13ʩF aw?h 0:ĝHyI)m-D3SV@Ǹ|02ޮ_0L & 4RFa}x{^SΗLf쏶\#E桋5~)@I L2Z<a{"PюuiCiA/ȸh+m `㖘 q2b&0Tg㼫=_:2ә>$ir@!;&Q6yi}q\`,P9(g+JJD +h+ 'QK=Qcmy~qUQz`V! f V*- &~0S֒eF|vZځ^EY1ͪ؏HQ;L"kSb##]8D]U(wm + ?FTGkHIKO1$-!/8@PLDq% 累oN?9iקmtv*<}l FZrS9Y x &0iʖc-WNoѣL]Ul:.Eq%+VlbqK$(dj-MޱrmN /!Xb+]}Dee6)/wv_m]ZpٯjM n0@rW1!^S5Ř`)PwM'bM6n^]IdM%jQ6 dH*c7xC5Jsj!F Hy,bqC{Y5S9I174]px˲6ސ1n79FYk]껚4 ' 9R8Q{Q DK_476MϵW ]ĹfX¸QL +y;U}3y3rjwL;yw@6ɲ4DpԦi&5cX/'SpPZqIM n'9za hA4asN@YޭBh!9̃Y\ $:g I1/Γ}SfBFO<:`Vaka(4<[P"lEr0^0KbK;fFZ}T',\Vd.Ja i+ѩs2rT<fnd_ǥa6Cq+fĐ&ӫ"E_.Uwj~:<@#$X`\.S)Y*N.0XrKCu#q+;VWňLS2MAZ  M%R gpg +Ff"wZ,nCBxȃ++eT D:Lpo-G'4颺DFQfƹsc%ant%zO?lzcjqˑFZϤ{Rj[I $wH8䎋de6;fIVd= ˼M$]c$zÄ;M02QܔtŧV`o| SVj2 + A +47*Bg[F`; P`˹j=l)\2= qp$^.W–g漼ql lJ/֭钗hyсD?+$%Qq࠵NI^VyA` gVqe^5뢎 +恓rCjzbBR=w˒fD@Ӓҫ•igAVܣɲAdKV<@yϢu2mQCXRG(#Rz! f22r 0a +xφh7Tۤ61] SLSAM#Kaq"dz{D sM$fsg9Vua--X1,st(o`ȘyKVhċn" W?Bq #@{BL"ڣkZܥ_# !Lw2fdSqrKa9M{6#uI+tW +D⮺ +(w W ;fFnxvTqޝ&Ci\Ȼ=͚#m +#Hk 2No Qd]4Cj[K;>YC=O,<'zT +i-G a5|zj'3hէvߩHM ET]u.zn1}*wWnOBC^e)iYܕ[DIu%}I0L,bX*Shx&TڽI5?Mɲz{w~5'Q(n{*%Xש+4Ns(FUÝN`$be'p톑}ȹ3{]T@ͩm{as{%DZ(!%i=m ۧbĺ]Vώn{%uH{ӝ;Yy.@q*2Pn11c5(d60 XP7R2ov[N\*.a@c!;&+DGX3yj`6EUfsZ鈨 =E +|zBvAyK 8~9'~p+5€@p#Ս+6veN0ܤ(aLYe5)UبHj@XX:W]TŒV>/LBM26pu/˷}V ,X` ,v8簡xZ]__#QV~Zie&o~`qXծ#v~r/}G$A_nz+a~XgC\b YQBz򐔰ee*q[dҬYqD729&Ŕُ>-D>֖[ dHQ)Kj֊팪îj"Xre`92.HMIJ +~'Lsm\߽2u\%6P'{iQDeQI^\d%_TUWIYnZ;`(J"ˁ˛K++7ysI^hqN` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` ,X` 읱  +endstream +endobj +463 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 665 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +464 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 666 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +465 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 667 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +466 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 668 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +467 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 669 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +468 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 670 0 R /Subtype /Image /Type /XObject /Width 109 /Length 53 >> +stream +x nH@{ +endstream +endobj +469 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 112 /SMask 671 0 R /Subtype /Image /Type /XObject /Width 293 /Length 2517 >> +stream +x/l9=;a$.KeK \B a@ ^T,"+hT@ +H*ʤ" ,!*`L!3gS>n>P`QE'Մ @7,0(!EQX}'adgfC֠u+fXi)“iͰxNRhiˆxx! ږiY0bׯ=rif1ḛѫmͣg.!艹B5bՍ]C@ߨ]=[v"ez<}rݣ'P7B^+gvm=䵅znyinqf:Lfojw #|^e5'.Ԕ%Ss+ ao0iU/qֵ[Q}7TeB{O\7Iz.ըu kIo2xՃzɠ]=Q"k1yu[uo}tXP-˸?xq{EӭR:1`e,DOravP'cfN3 ¹ ݳ' Y.ZVY$ٱy7yA M?{kZBRR8R\_n ggׅx[("@(ǦY \ĸq<=B9]LIoUEPtj-Z"@(A +n&-nu&3EPm/@LJ5ϑB9mNQ7ͤ+~y C?-.V)g8{o,k#/r\lˋnto}(=8\wq;ޥ,"/r=8qb^6i>y 6 #Mj"/r0uXd?U0eF^(.Vpɉ۠[݃*wy ?v|YNB|-s:ݿd*RђߞM^ +vʴnnHTgˊoND^ ̥ʥɍqcY/ 7 4eƭxu{A)7n޾L'504E&mq3-%{"/ AMIܼ3ɕg +Z IrѺ#v$hvd+&k8UkC8IbS|#Tqs+#D~o/Zqw@oF^"oOwoJO;-5Q.&OJ_Ftxq ;Ė hf(wka|}U[YXՒҔ7w/X,qCoEA78|Dэ%h3 KyKȋaߜ>w[I)uMo3 2qG7l|ŷkaLw"j\37 }WȫaݰqG"FH3I4áu֙T*y!=>Y*ۘ繚%u]qœuv] 'Kljatº~KTu8d=buw=Qag5Uo7*#~G}E؉̰F-fص,N6^^`V35bƼ% +϶oڹ}ׁ=AwhߴѼmf3pOࡎNь3qTXBXUfi~-,uhNf&KosyEB* b)7_VluίHMR DӁ0pM7_nL%rYenvl4MYʽJZ-W> +stream +x͊0 E>F^ lI~CE +t7?ݺ8J)nfLן= mYkf,@ۇr8`j=uUM667N=iyxiZ}~Gq^Q 7.YDdyZDjk]UZ_uZ)Nکkѓ^莟kt#} +...n;~gw;θDDDDKP +endstream +endobj +471 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 114 /SMask 673 0 R /Subtype /Image /Type /XObject /Width 40 /Length 289 >> +stream +xI0E{,F€JEAl:俕,HN'phm9RLUK)^Z34iUUWUsZkm|izKLD^oH]xiԃ4FiZCԻ?GK/}C+%"yEDsV"B&"fIKff"ksfҮWbKޝIw /_?)2"xяGG QG7wG QG7wG_| +endstream +endobj +472 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 114 /SMask 674 0 R /Subtype /Image /Type /XObject /Width 40 /Length 258 >> +stream +xM + #KW`mI)Y ͭM9n,0j=uUMJ?kSGݡsqmq7Z꾟QX/uE!w&"fjS|1䩣ff"(vQ3Wxλ= ..\pj .\wqWk .ܻ!BH +endstream +endobj +473 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 113 /SMask 675 0 R /Subtype /Image /Type /XObject /Width 40 /Length 283 >> +stream +xA0E{,F€&(:[Yox9cZZ33UmUzʤwU=ZUx?P!Dv)xʢ_bNzƨ˼g~x +v_S(q> +stream +xI0E{,F€JEAl:俕,HN'phm9RLUK)^Z34iUUWUsZkm|izKLD^oH]xiԃ4FiZCԻ?GK/}C+%"yEDsV"B&"fIKff"ksfҮWbKޝIw /_?)2"xяGG QG7wG QG7wG_| +endstream +endobj +475 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 677 0 R /Subtype /Image /Type /XObject /Width 176 /Length 938 >> +stream +x_U=Hwg޻A$!0HM"{7E*B!Xhe"H I $Bcw]|afy_,Ԅ]ikJ |Vs66IV껂$$2%&ѶAy/sc3lF$l[m0wA*'QQbIjde lЈ:j/YzvO~:~UELۉHl`.c?}]tkn&O 5QwCԏS[Wzk_IDy?֎c__ԹSGk7l]egJCoO+Uqno6m>a0ЩOxUA0 _S=TKӪG'9;`^~7V{^t?^+Np Էk}xwE-Z·28<"~o7/yȳտ.ˢ܀鰇$3*OBnv۸eˡSϿE姓gN~ygpCjkb(Y6LuJNz[$BV6ڦ(wۥ}B"lٰ[.RCEmԶ&pA= feqXMO@ +endstream +endobj +476 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 1148 /SMask 678 0 R /Subtype /Image /Type /XObject /Width 891 /Length 45871 >> +stream +xw|w>ǣpdwg&ٝٝz/R4AQAPT)b *6 *""HPRiP=y=89dw\SRRRSoHx%F\5-ժwJj.(Ghdy^'TrW fHMn4]TOOWUh~ECܡD*QEECP#Sʩz>)*_ ߑꭔ⭜"֗Y71hV*V*TDJ8Uj7éW IVt_JjWwQIQ~?R47!䰐3B_z#B0"uA5Y#z`īD|VY⯜⫝̸p,XO$BbAj +WPWof5Ũ"TJnxhw{ V4 QUlnBf2;f]o0Y g)z>ΞpEO Ϊ^7R~A-\`їCFɪ$n 1fїKȱ5mUIˆw=_}ݒo{;tj~w;O|V||s/֡k]ztK|C}{<Է#x{_֯]a4ߠzO7n޲i֏?3bOM;l߱k= L~)L~˯޷߁KXΌ{~r#G9z짟Κ33kt~É9'sNuӢ/uӖSO>yoᛥ?|w>s;w-_jU+W޵{onn^n^~^֮۰oi6oڲ𑣅EEEE=}.g>qΟpd}3g/^tSO_^VVv+Ǖׯ_vՒK+ׁ//#/]*))zss/\Zr's엦˗//G˻T\|ŜSȎ:}.8kvعE>rt˶훷n߲mǑ d;n_M.((O>[z#G8~ߛ+W8xh_~ꔩ/Sf۾cy z'?n۱i˶?lQlw`7l6cC>~z<)o^O?}퍷ޫC_]OWKjwo;6I/a/3Ziܲ^VOo}dȱ57ˬ8Ndžc#52h{=s>1Q+Q4k'8lTzFuI3%>d3yBTUW6T .IaȐLzt'yOkF{<Q 6`^52tS~Z9UӧG{ZTc'dTs3/ +J>Kkfd} +WZ' izydz^_UD~V57Mh X˓|7a4=¸IϏ?i F׮]-ߠa cOL e8e rF(΁ +ڍBu2ȪYck7ӬӨzYkɜ-iܢn6N>ߺCfwuh~wm:uz^^~aQѽjw}{?f=Ч_^}z<ޏ}|s/_2`ȓ>11|K%%%#8|OM=~ϽdǤ^˯ڔ_sivpzy8ハ?5~䳹.Y }a,6^_~}f.]e/_Foܸl6WӊZz}r_~^n/~7l[6n޲u;صcҺ7{}ro>x9F]ࡃ>rc9NEѲcO?qɜ'sN9kgi3s)(;m;g]xN/_bӦ8KJJ쓷I% ʭ>c+O_T\4jYv,,*Oμ|[9'N';~y}r߁}r}v/k[wNu8YmZ;pz͚_ۯ?KXb/vW,W,_E_'?`v=o;f}:k|s{3ߙ5mdzؙa[XA7_|mʛo{ڵϽ4vⳣOc'>{k׮ =a#C/\xƍǞvP߂¢7ntէc;tپs]{mN:l{OaQKjWi++`4nѸe[TRiudilnU/ר4z];pju6nzHHV Ȱ2UѨj־5H^#~&-%v-˾Y+W/32Yz6lsĉ;pЁlݾs݆?]jUה}l_|5o9󭂧JK?;{3Nwޝ:/5mƳ/:'>3/RPXtg^xeȈz|ȁFͽ|Q;>z>;q2իl{9vC}[YU8xCמuӸEk6loiiY6 ^Êf_vAda?}uڵş w;qF͆ebTZY;{7nnJڱYeeeu2j54j74k7ʬ&26ά۸z&54;snVYXM4nqlnq:[doUiM5mIntgV/nZY¢Z ̪פz&6iܲrNQY7ʬ۸Iv_Ϭبe]հn.Xz4jZ^"5h*PR;#9Ub})Ps%8c?Vc'CН`95XS*zGњ fmմ^B\x16h~n77h~wMɚ_}~}nԠ[:D+W4h~W;nԢMmwF jg㡾h4i2`ߺ/\lcמ;]vnٺurWǮԾs;u߮^F|Mnm:uk{Ovgh߹Gf.xd[O啟ң}3gsOv@'dAz?ĭ'/^~wҲPLYc/+; ;֓?F +n=i?ȥee}C׹ǃ?voa#o=ya䭿}Q?9EN-v/؟u~CCפU!|?wݠ]w@Zoھc?ȭwݨEF-j5fvӸEؓ=^K֛~FFmeu.Ṉ5ꇳꅳu:SfgFE)5Z V;$VZ2_'YNhַܹ^<٦Ӎ7N>cU +ݩ[iiYn^^joVA 5իW/^d?uc۬u˥eUiأԺq˶.^,--kܲm&5jѦq˶MZm /_nvWؙv[c/h{OϜ/hݾ}q˶-s˗ԭq˶[mԢM(++k{Ok]֯ywܸq}_q:MZuuKk/κzMi "d"5|eq6)F-`77Kϝ;W.v'Q=W^,ZQafƫrn:Vu;ڴeoYZԪwu߼y!#|{}ƍ#V[t\YYٳ/6'<_~7Ν+))17Nkڌwf|G;w/vE Œoz-km`Jص49u2ԙ.\V]\r2e2f=ꄳ%:p*u=z$KKKKEY |s?qGZY7V;x;l۾i˶_7m?Um۱s_V^򧟗\e?m׮w[|%t-5w}4kF.>fX퓟|67~e[/ LԦg_7&ZfZ)S|jȈ=h(˯?G oC>nWMzޫO{w]M}rx+tm:umA[ U={G{Y6hk^+W^ϪQ]!#mX<뙦eV}]h7"ykχm\^[[s;NxִU7=~ uf=v͛kѦS6ZU{sN~zMn~dNYٵwrUl{#ǎ_~}c|ؾu{#Go޼9|`OЇ-Mc׫O)o3/zoONz?OA l蹗_GC8tlD/:aZ3I7>`ȓ[Gcٿ͗'5dĘh, ڔ[O5^2lS 5͛^x9~1 ʮ=ҿr؉g斖:}͗'9|έ'7ߞ1la:rcߚ6cq۞htm)CFAO8|{8~F>1t=ٱoCG>>dĀO<7'NOa/:0t M'=rǞ[Ǐƞ\OzOc={?v2z5C}{o^}=dΩҸ.={{߸qcv}=7o=~йe{[wӖmhtԸlcU?mm笑㞎fwZ+koZn;._ie։e$+ -]׺)}AgȈzz: :#NRvea3M[kvW;X9N|=wѱ~ K=k~{3/=1_,84^|uF0Lx86'=3/˯_-\g >~\ƞT3wy ۏO?b_ُu\}vɷK]ᛥ?m~M?rj膍Wd.o߱k݆dO2{-۶oݾÞrcp.dk{ownaJx4N/٣@<9v|fL{of ͘QБc8X|O=z8N5kUm+lݞqV~7 jPv if \]zIé;vN$cѐ)i_7%{H#܇Us-7ŒoBkH;=z8,=zzz0н>ΝOg#5BiNI3ʒYW\i8E8uL8jJ D~I]3?κ|Ji Y՟.ɜS~ CFIdഋ\R]kUap8& + +Z ӴD^r@ *̺M$h2aSE+jfGǝͪ,-z`/]Jg>sVR(SUUP#f^p1УF'hZ0#UUEՠ NXLUBUuQ5/'{pVIi={O )GPHTW&;޸EQ3W{eCT7o&=֤U;I3RO DRm\#(ifUd%&pŐT +'@w;t5x%&pz4A֩p' Ԍ%No&FGiuwo / +%%%ǎP"^HL4 fM2%)NKJXP Pص}R(35NL wе2Ezbg(%uo= ifjd#QSKeH.o (zL͆|JЇ]w*'h_!qI.^YK4NT4|rD- ۰I eSPWUV/P + jpʺRp6|ѴPbg0#%=L ~^>C8!pvYG#`XuVJǮ=`V˺Oљ EQOU89.ǟ%KJįp08Β4]T8Ո_PpVCY'S#!h.[o@FuQ6ӧFR 'l޺}{33j6FRpN{%b8 7/olvcZ4_}tQr$)ӧBX͐c8Nb*%FS"-Nɦч[c'FMQ5N!hh Ys26ST8t &{-QӔhf?q^4t{ "'(pJ)٣LxfcHS`81ST H.XTh()zfe.T|#zzK85CL?\` }i_R4/'Ⓔd' ]h-FJz+'-~EC.2HJz B6mvh4W/ +gl!°Q}Ği]NȢ!k/uՐPG ⯾p +)Fbt'@7xĘ/|iJ.$$pppAG} +jJVSilނm;vFQ!=S`/u7Klmm)T8*n9u^dDT t +kb +.$.p)"N'~|J]T QM%uI52=z8プXo>X4 +}ZOv[$kɄT _KߌޛmE#+2EH!ffp mܼ5? SHTS*@[$Hk~)iHwI]5*bG=Чk}8cӐM{?}8ɚiz>uKiuFRI3!pT|/O~s}%u3qS5N싂 rp$pT|{]p14$"pVKM%{p\{i5S1%HL9MO Pu[bԍR-T7q{©9+b%@ qϿɜS֢!Y=35v)5+٣@Lx0K`$"EC/\FӐxN_ l_zj¤dk}8%-}8}˜Ɣӓ=z8ENF>%31jVO'٦CV଒K ~#Nh~wCDQo(A[[>O(D%xh4 D ݡfT5'p+SUVN_0"j* Xi%ՔT?NhԢ'kSKYp:p_pI%4rJ,pz%bN. +kZ?C!GOU;K̙yqxr(TUu#>`:KnlnnNF;8u0,Jj6l*uS2iH͔4s +\Ff^vN_ ,88e=%=*uȪפ(އSRtz#>MprI^2+*uQl)O1$H2{B D+ G$ +'+|qIIN#%= 8[Q%d:u;wޚtQ3Ġ :hHPoANk5pb|/ub +lCBpf{/ufappVK>6@W8Jį[<5R{Su{kKÍjDE"pʜ E/;J]5.Qp?,v]4gfEp-Ε+Wpx]Tu'fWR5CP^nMA +^^4dWtlJeee4W+Oh%z>c{n`8ӺvO[$=a:h(| w̞PPu; iK.ʼqFNE.*NW4͛LH[rx'HiI\LQH.p[gyN/ .u 7nxO?y %!A5$Rp9>e]uH "\e)o.GDB;}8ڵkp}+FupNKg#5*ua):JoMSRu*^ii+prtTï,pREiߟtٕ֖pk׮MxE !CrzlkKRp*u(%"pEpk׮͚3߾?g璜))_RU]Ҹ +ŗ3-%㋆͆hP]*.gճO:㋆ A5%M'pTx/]ԨoߏU8чS=8* /5ևZNt{3^bJJ*Rp+p*hNi-M W\6V8E՚):-'@WXTTn5SMNAՅI + ++pJr"p4>-VTp57[}85]L*upT|54OٕNJ._gsOUU +ήRg:|6w?# f +}8*~Ě]98˷$pTpyy56ƴ^b!T8*ܼZ 4UVڌ%pTxgssk7jaiUu)!}8U"T|gi\8_7J@[Xw>np>8'z98*u'䓿OsO jT\tي(d4%,;ޤU]{LUU1:8Eʡz';NQ3$) f88%LI#pT|Gkں}^? !;8Us[\R9XNS2`S5A T|Ϝ1vBо؝%;8 4u*ncU8MiQ1 P]z5{k.Ia)v8pb +'@ŷV +N%, 'vRGe?к}]{B:8H:޷u.C; +_q8pjO Ӈs5kv_vUG+;>tSC+mZL*uI31-pUxݼd4%HWv?CT fUpkSpTp{S=R&X:2!|@hTVO;8%LSpT|/]ڲm{0餣{ENozhI=c{ c7~_פ۱sw. +hH1Dչ9<,kj۰)աkB):؇3ߔT+[qvai6tzPȧF9UݡGG[$k ]٩yT:\FPuAn]RQ%]*KŻZS%5áY-)F,I=uN^TL DRUUTM'upܖm+~h7~Sp,pVUjTg46o~}#pڽߝpV dZv8ڦ݇ө A1$ը +2U}O?S3%ِ[xeZꔷ:z$ ͹ +OSâjx_3#._r ~YmpTˆeZ3EEqI=an-߭- Ib0cI=i8iJ}8)fU_U:z8ρHN*UMe'pTxn,rr4 +'@ŷ7{^ +.I3%RT/ԡ `lѐnȱH/]'pv͎!MQEͩ@$%"puz>hP6:U.əNcTE3LgRDCҦÍ߽i(v7Τ +߽`t {`oTL5:/8GuA6 >9g ;s `DP QRDX^eCT3c+z +~9"(4~p?NA1{{_NwcuA8ZL F}8S F$i"G ;v~7RzD¢` :|?fS{ .#͌<?ɡYݹuA1K%VsǞ!ENA1|.J87=d/!w(pf @p块:t$g`(% +/Ja ['>RJ@(VL?DR}ADթ)*"s8 GJ& VO$UN=C@",[C⇾%:9SP 9pgjZ9]jZET P!p z^G+~%{Qa:@UxpJNغ}Ǜ?Tl-$wvkKݯ4NrP9E h +'+ޛ?j +Y9Et.p +V.Êe&՜ +rD:;||cO ځS '?3ՉiEL{]RRDXlE +(ߞDTɈ KXoaY~:8 ) n8BG ;8Ip!Y988u!: w?ZSGZq +.) ~y)OLu'bر6C@"|tـ!O_4d}CNk9:t$7Kx|Ȉa)/q&p\ba }#E+,p 7C+pjPȫ8]E3RR$W{ݫ)3c˅% +9?WQ5 :J)*NϚ9T4()aTʛXԡ "p.v !CP.B2pJS~gp.[[8\+!up*uݚɢ!XWL"ft_9XHdW8uoHI% + +=?: SN'[[K2bL3mb{~ ßɾ98cKԥX5C@"eb,gꢦlm KW S; Rpu6ayTt_ j^6 .T'h Q-+9gJݯbeg )vꪞԡ X͐cㇿd.88eST#Izȧ5CPD]tANXaiJENU] K.Ks]Uw-bEY!h:t$º ?5'~p +ARJbHþ985.wٽvCT3Et0p0?,pƶSw0pZ>#N7i/TnSRXw"Y}> )h 5~prI~giN8\5ï~+d!/8tHЪpߚƩ;P뻠~*.pWOs8E=NQ5rħD:t$‚E_;!~XGխ' >Y8\`/F?}'iNI3$ECS1%c2 +Vgԡ ~Ẍz<I6%9SPMjc-MApT8\C+p*?6pZ;NoG{:~hNk%uo'E"9JZ~*.StkNњ&J©q(pJW '^O>-Ҝ fu?}<O,mZ7Q5 9Bx gZS!CRLɹi\A'@9VTtI3%' > PeNjd[$j +' 9pZKSԜmu\"YNS K.fOfϋpZ׻EMwt!+sJ}<O5uC.j#3ևSPt':gUᔭ(hBGU4E% F:t$¼^e-s8S$ݟN.X4z]W 98CRMI +ӹP͙ >'+jmc$ P͞pg֢!YTӹV \k>5'~ Fj@w,pW6|2Nױ.EuUG"*_}Ep3gs>?4pJ)hT*.9b "M{3 GglJj"s]RŽ.f*: *pF!ùUvӯFiN/")B0,:Ӑ)A!pT||RdYRuGr8C@x<*UB^%$94/N(,*9u:~h`H.9)8S59pj@HNNI3</C@"ŢS2EGWe'9pz$m.iOH'uHZ5`N5ӧ>*Wx֜IeNCUfCVIxϠϠ&Ӿ.4~pSz< ?cݹǓu)u]iP͚`8E*)U[e"T|7fd+V fxCTUMΞ7)FPȿ7pJRXuI5Sd V`Q08B־Vb0deNHUԀԡ X(F9TE {?4MT ?K '{p\Ω!cs8EQ^Z$fJBRDd 9U>I@$5mnjRDNQ3$)ٻ(K]fggvf{oW=^mps&cl?&DA$FH PBaYUZHy]ͬyzWϙSW]Ϸ[:y3Htъ`5b<ꔺOjXzfoyuBYg$k~z8qY!r g;5Qw1ڴIOpY(t1´R~3NM;swo~Shܻmmj}yiH()5X {HN"+ؚt=ROScb^? {wəY&8 N&Ev:OwP N)w:864S}99nAp(DQ7 ~7X?3puMvo05'@83W[Fi<ߥVy'@8pJ]Ejwok ] +JVkAX>8I"N 4Fg#i.}=qo'@\ +eT ~3[$Yd\m n+i?,Do S5}Zr%@8STclNREclm,HtMb {wpFyf5paLQ~=:D 3:#G3gceܿOpe;SX|hH(#S[pbٱHA2s8=4$\SXPVH6gV +E*Us8#Y.94jNv:hEJ +4TJm=;}ɧ^? Rde]{칽S DTH9B"GR2-ew8@XАzS3J]Xq*EfSv6RǞ"{p™2#8X/ֽROMOٱHIZ?4汲Ipi^jP" ;v}BYfspN4)Ro 8 nY7"-!p8~/g+"±bpR6!8qҩap R:@op +mdu-nap*#{~}ӐP:Ŋԫ&l"8 NPtPe՜޿̫H% SBIfHԀNpYw8 ~g}ԏupYv8{^6 pij8SspVݒM7re[GRI(t.|RYdw|~"jK78,g'@8VNÙT;8si g}ΡGU?2QEYM@\Ʃ՟ަ=Am[~C /-}pV{2h'@9HN{ɞH"8$3Mv8e5ˀW{Z$Eɧo}~tYq'Q?WE[4n¸W=>S] y]7xSPPƾUWvFkV84dDۣ=w8.pIp`F/}i58Mue) w8w=TFhc.aXzgy#B(v8BqYg{׏. cǽ!w?;岰Y{l&8ouW֏. 纃|NEa?ao8۽N73p es3ih.Dj.Î3+N@e8S-MC'@ nsϿ~e+D6 N}߲{]"3NB N!8.fL2QIpa6WgRl"8 rwKt .Cw b%.6hH g!Bha.q KJ}||spfPe'@ r9袭(@Yjm[~2 {^LO(՟ÞSJ +@pRqh gs8~)̔yِD-Ùi ߧDBAp{Ǒ242m7HSR3Y9N )8uX$N9v8°Lt9uKάu43&tӾ,CC8㬳?/ԏRzBdPph M +N0spcKjvo8waϧe/o8]븳@p`YDj1>z2'@83{G= aOΠBR^o~~-C'(J](Cpi DiU Nr qE\Z?aS~+ԹPO?uoz{2,JE)s8ʸ^_*8gQ+u !8WSR(jK )8uʶx +4ܥ-[/ww)ìzN{D*┱H!-ø7{v]Iu=VĶ~hps1zfSL-p'vH298eVLwLOpNzV?Y)\z=Ot3='7~u+K]9w )~[]0+bTmEz N)4cpWt!h&liwpy*B4RebVcp&YYN0vwAYi2|p.Z| N:?:42|h(ŤRNJp` ^)U78eVnS_>ӾUEW!X18(x N +7Sph l_8~ D9#s,PPe-bRR]6BB)TuQ7'8Bc[^^?gdE-΍qA ]~cs, αߙ`,@N<3RwQI 'rګ^.jxQe{̊X瓭SfE!{-pSw)CU$ |jR*#S|Rv8EJpGٶq n"K]gf 'v+_q48U)<>Q!X)8EQs Ζ(7 w8$+)2Qr:@RI 7 iKx5X _3_د~AdIzqjN NUa~9w.3qws%^V?nEJm!/:ߨN~_W?ԋ~w8ݿ]i'@P|+~ N[vۋn>]t91&8Bp~ DB_pLwQh{ +i) \t _Ǒ,nMsZ'u6!K^gfp&"]Vg1 ]r_/cMCE +9>ɽD'@nO~ hp +UHs87mdd68ݛ5XuJUv{ev8mm&{)CksиsΫʸLũ9IpK:@e"%I2e~S겝3JpVYĪ ΢sM:Ve_w/q$8 Lw1EKvǧNlʿ}KF3E'~84˯oC=OnWb^9Dpa~Rp*&>S l\0VHy $[$8B~:ՖIZGsb&&8BpŕWS!) NQEZ7X$ \y/Gv#Rz1RvsHk_T?67̊T2 $W]s_>Rp)o'gz"c.LH{ MsS[ +NeQX${G/lk p2 @W_{ԏo8*ms2 $\{_ǑHm&lŽM'@?U +uӳ2I'8Brp|說XY&R ۶oU?&JK"%Yi3Nq(q۪zٛ$;axG/ݲX$Pùs,s7hp-8cREp%@n?Gv#bMCAᦛ/[?6&gDOft̥pƛnSj9 Go8p];>Oԏ)U!߾ӍE::@F"AyGN>q)83#wۇMTMpছowhpo8DZHz8~95Ep`l┺Ћ9-zO0Mp`5sƙYJ]0 +Rg,<{?/Տ)REb,8Y_p2 @?~PfRg,@mRp"ўoJ66ESRj#>=$D3nF CpfF BKR4&8B,엜33&8pǑ46qjMIfSd ̌"%Y),4GQ?QXǡ"Ej1֞"%YVLp;|Eo8BWY7!X)8w22vhHH-J]d9X\qfVz Eq);5Ow βM&sbHA7YB NJML!;˝R\pՖqj6]S'' Ny]xΟ׏K$+ce<Z71=+zW[F YXa [*E1 <[q,"5E@Rqi>]EЯ~y\X?ԋ{Mp͎w?Z?}.t^ ]Vio/"8{R?ԅqo,eVuZ$8Bp=n?w άi18oiZl + +^JUs݆)u>W~ $3monIf NgLCCΠ$Y'@/R?q(Rde˅]Yٚon!GRpRfe; )E=t|֏uVcDَY[a xsLtW}73L31 R?gvZx ΉVkNkJ XKN95 GslKpfeٱMlLp׿ugwLt!tympVoDp7|~ N{D]z,F"᷿mn)3#38M2Qut3+dH7 m0(31 ׽z$8&I-87m,2Gph48E/< +'@N>z=z3v8G3N3 {3Iz/oNt!Doz482Ng]NO;R>]Mpbƭ8L+2RX'?yzݓlg1s:VXm "^'@(^|pm +ΉVYT){'@8T|.]vnʫu^^FjQ(c) vuњn`?uڋuA/8RJӵX i=[=yR6S*1 yگ~{pVcJO6ĥEDIJ鮶ŬvhhߟM["8׽iW"+'cƦXow8wΓ_նYJt0E ҍksN:'~wwOwL 9-  +U<㗿[ż.i/8$+8'@(t_n=.Y"K3Q¡!PdzGziҾR/۝~7XQG(;)rh }#ٮCCUpڗ=#R {pBJ+<X$ faˣ^_:CC8cq0X$)(yhh&TYFCCX{?[ekN|rӖ&81=[A98: iCRpV7 u\+zT g?nVduPKzeK VrzƞS="h 焙fzD'@0wO᜞6Q=82nZtg?p&73s8SŤ6LG'@ g>p]PsZϛrlll|Mp|wn[(mol'@ Gw[e(t!-'g<Ù"NMrh }Q^ +άY96y||opJ]492̊( KA'@0;'~wcn6QOpw^%8u9TKj9튞"8{vN4Nnw5 ޥ <7{,RVDi){4=OwۭwۊRn898u!\ |3>[VJqdeJ @e85'ڴg2iL ?cV.8{&2 OΟu NJ{pڳH+uP<9~]whp&Y9w<guJ}~~3 g_qn]LI&X]ed$8?=8zs,mN o~pjN=9 2k"8K^X$7]o8KiMLxͭ۶礔U6tZ] O ιVkl$+;s:ʧMcXvMCxbpO*4J=+B׼;wiIZ L?IpeR3jaF-98$8±e廍EJv*D3;R7|7. EGq)u{eoxbp: sZ7rhHvZ)BSNccT!"'@(jAS E*H ڱí]~:K;8}Jpe- . І83zavY8&rq98WꂱH!yu. [$Y3pBts O Ψgl:4TN3J O θǽ\fgLKwi:V2;v4"s[XH.ث-y^ k\ +1cT3i'#qn= ^$D63#| Z(3~3ѥHs]y5n= Ne5h.DO $s8W+ڭ$ld NJՖ+rk~u%P)_!8;tkmo9Sagڏs6t:;!9sn=܇TPšഇ߅*c \~np&NWcL8%8B+tkBqZ$Y;~% {?^vn38M5)}(s_O[^}H=!aw8 s8±mvf,jHqXm;!5h$8F%㎿]"4Heo8B/K/. E2ԫMCp_ϗn58u!2id}S'8/y%^MK=[?y/yŗnqa*hc_.RO? _[nL;H엜 \m cB0J"Mi_3 {ϭ] +]DU!|Yg)3#N`ܺΤBYFKN /~+λB786Iq'@8^W{n54$Lp*wPАPF*)uܲrgZ^v7opLNP !ũCCM W_{݃>;!w.u{TǑ /km۷0 $3#2} 8׾SOw:8U^ e& WM'r[,&d}op&v$8O>խYfNXR׿vj&X"˫%8BqG6)).Hh#tApeauܸ?YkYr@PN:崛oխwCq-'@H^)n]g ++uN}7'@8^ēݺBM|i#)kN8ɭPvZ g>+uMo?ݺN"ͽRiN|\yn38ˤ:K}3ik08"}^J{")#8Bqigxn= NjĩNUT; ׽?p&-*"]v8oyǏ;.RJ^oGupڷ=vuMqJ ox;uIVDiϥ aGen04Kg;;rY(UX-ʈ0 (gs-nsapjw'gHRc|[gbp~LLRM'8񦷿?=8lwCC[$Dpx1?[i_+zΨKYJ (o=c3VgО|hh)ux!]p[nωT Nn4TS= 8E'Yd% +papVW[N3Y=8 a'@Hu.ny3*ݡ$ {Ï<ƭടqʲ Y){R^{sGu.'jI78߷IpbYjm~T78g1~_'Ku~sr6Ԁo85 NULTKMwz ' m۷snp%&ieʻC[WU8\OibՖ!y?Ïtk{Iscc&K +2+g |Çv[ML,H)ᴵ_Zհ>wkROOv8F(bZt>t뱱'X/U#;s=3׭ I9!Zt6S|K?8頝ڽi3+Z\+mzll%l Jm\:Y&u1ԔOjx>Ρ Ni"VMw7';Ax?z3JVgP_wLJDBvN C;g/w3Ls8l:X=ԭlMpNQfg.Mw̜ZXp?uk{休L"&9j?klpvJ?@pΈ:_Ӣ+u0wEW_{[Ge37nKm%u>o}{nm BdCC3=='ʴ:pa~qjDi]cSScccIV +e"䁣 +iw8yn?;2+ڝ~:@>O}ush.12+b+u ē=ٻfc.uHr.]hNuiWQN {^խ7YeF6thhgpʮZts_? vs8EV&͌EgVbv8[p +]HU64} .'| '&il,{mEpazg;ХTZtN="Ime33QZt6J= enlg֞y\x閑HaC9i'@>/~T8SK3e ç>ůRLX" N@|s_pkY}c2V'@~_v+jhrgM'@N93s2JKi gX1SR/cUĪhzJ]2 _eݺ +BgyiG}\m |}nK{De[f WzefnPq4;Aܗů5wJ=36hvp!r9HNLL4vWlܭCC6  NUpfE7}Sn]R֯o"8-iZp|t뱱T&'@@:}"Sb&4Qccc2S N@|;G>5zԟq6@N`p6' Oω9!uPpO3 W\y=߭Nn۩6q3 :ꤲ߯W^i*N@|a:VI UwW7 a8{u85B)~Ùs%@8sgWW)(T&N ~:VE\z 'u-,ElpH;!RH sCpʫ>팳ܺY +U7{9qGtkל % +uP~{[Yw$-K y^{,O θg?.up|=wk;cCL+.ulzO~z[WSKY~o*8HHAαH4zV$ ni,RZ^p=. '@88ط}n-"V{PpFBN`y̱o{F̚ +ιĴX$?|[Ke/j&QLOpcvkYn.8d>.KRǖsZ"J5PpNvl1 LGԫSMEWSRƛ::\dM̼1{ǭ]&Y\pnH1?ɛnv)~ߗY1wN=Եh~tܛ.ۛ.8;44dv,؁ֿ;ǽmʈ9;44666Z)?kи.CsX85m ŤR~kb{ttp,//@n .ĭ.*Efsgp:O ?oz[PJ D?/@;׾mn=Y9[EoQwC憆1k7'@7xͷ}y7ݢźV4ļХ-5DwvN4U]~̀RF6 K`wmw1 +85/1it` '@pVnoņ#?EK*,Gt3/]#k!ME3]6<ēoեnYVZm_܈?pt[V]v1wc;:9i~%zzzpp%ˮλc &%־h3iȓoB :4M]t眺EހӋ^<ē_ye8o\?ऻ".WZ5n^ƛ` '@7xm?}O׮_niR'@W?ti^4Dp:@W{CJtɲ+ٻ;L/^֑p.\"gbN<?#4tWΨi'@Wx~|4M_u#}~D)oB EV!wC.uN4^~՛{pvdnļtcd8Rg^ҁڇ1]Okg'w<=bvxòs8×c8}?Aʀr΄.HŜ8K]r|,j&"RooI7 <*NrJ +8eSd8˯_Z>iI/JΈbN/A C_ƷZoj{).u '@w;Y)8pXNptzxgҁ"1q0tt~dNx4DH]b˯\{Í]ܰ'=wt+'dFv"ěXjSt гMc8£k{w{m,.B +y=N'aN݉t/s9NMǟ>_΄9!spHwNO]LĆ.uÉ ' ^}~rmׯZDÆHpc8 NLE$ '@Wx~64js8u'0Hptǟxr4p"v{3k5oO<ݿ9nW\'@xOg}m8zKKsf;pқ4=ܤprEЙt`o oWHsNX$#~7LڞN7);D:1woVv*Y,F=ZN.b ov4c[93) +|KEC +8EHc8E3H8>Npt?]=m] "W}>t7:pֹH>ti".;X:}d8~hӜx:wn}84u#OΐNn',3w4EEؙ!B'@7C:p&"c6}~oݘuKlp7D:@7kV0yQ?( 'O>õsiop٦|駓>SKj0w:pr' ]݁Tƺݩpځ +8?zhхcԭbw!ݪV6L@|ioT3fozkOv@]r5i^nޡpj<֭3DNúԫ-=.aڎM7)Ye3LY,FsNPM/)ޡ+Q,F"tL$fSٙ97wߛ;;g8N93&iG2~G&{CNΙ 8 Bɨ^rLv@7q'w.éқ܍N@9tKpkΉW=Q=<[m'U?v<4]>'^pZ'NӋM/~''{?PN='e8+f:dI]nMv?i ^|iݳ=_ztc׿4}GnΛwuigN>'K|wgygo__y>Jg{tc<|Q|y~5񋕫onoV+VY/ŗ.84M/{ǟrƷ?'|^~Eh}ig3>Wv8iԭ|oi|1cmןz4MϘÏ99G{B8}M5;X$&Rr]mmgm.'17M˯jwc]<긓4/qvK;oulws{vjk߁{/WvQǞǞo{7CfpC:谣9C}wMs;z>ǟ4M':Stin퉧usO>cwΚӟJ<{ܹ,9Fiw [ .>o%\wCO=EK]t}OnW^]U?)M_s \wõ+V]w_[5sV57?Rrnݭz=w}}ϽO?#>G׮]_O_߰g{׭S'{m|u=y}Æ7|kƍi~ ->䓍7[{~t}?x=v]mw\}ӵ+V]}u?2 /py N={'٣v?o4=v_kߝP>_mvJ>rWItvuv8 +F+~tuSFpɌ-Ŗ;ph풭ҥi߁f4}۝o;k9qzԖҘ(NrlΌw94MO9c̝n;$[migW{SrO9=MӓO;ͩV0rwΘWM0mf4};V̋ygRg"f""nh8!ö>(Ӊ "bvDArB0/dNEdCkY\D z~! "EC=ײ ACF$G>^}N+@"9qCDG>Q}sN4@yZ3{}**@:ӘG3%P^O3leh=iQ'|4~ffKpc?%_fuS$]~\%V?L%p/GWNijcFfL7kUUEf45n4Ҳ?_Dn`r̝7pC?ɍ=\ًvXj{`t)JrF^v2 ǘ66]x.x kT.:_6G'sCܟuՀӯ.te{5hB:܍.5gN8hL[ хcZ 2/hU5df 3^$W5pLΌbw. 2MȐ dv0Xvz[c+2Pw".D /;sF:vyT#̋@ۉ 3[4647Խr՟ryƚGKӭ4Q9ӰCy'7\qy ƊŨ\˵bEZw#ZTˎSq]M/XdYrT#/ +/ĀeYsG) +is˲ +gʑE_(4 +/Y|K]b՚+WXfɲ+\O3W/YvW,3WZ}\~rɖ)e?\ +5忾fJzc5O\q\X-j㗙mQch+iZr2io]|>{iul'Yј9TCJZl.k5UjM%I4"O,siZϿM^|w1|nrIX^,HN;6ܱu`n2Ek.32yE|ɧs7.ZMQjG"})TʠeO|I2.A94yhϛ"an!:.L/nr)gk ^JlߟsiTAq #s䌳.8Kfz<? /l_ Ff#ۈd0/p8x1<Ƀv0Hn#hVqd*RS6o[U^Βu*B]v3U*4M5՝Zu',Y,3n ;-URpUʨxX.S ryDX:Pf=/,02[+|Ÿş^ҌZ9/iAP!A<FԜ#KMX#Vsbͦ6T˔'On:AraۑS5y:I|tpi0zV#%#[@-wJwTT,C:Z +47ML*z +CCzzz([XjQŦа78wp겧0̸ƎY-$ow),eQowԊtUm[.M-׈e/xK;՜ ͸zBJaE2Pi>T7JqW]-2L&2R +E YJP4Zyy_a(US SW,yc,˾@f'TH%ideoVlR-_Tժ7NY:47!ex3Ĭz#&ZF>!Oi?hͼnq5ި;Z3+"yIdd4@%SiJ8sw\n~Hd-`rl$@ f7r-l͉U:KG=gب%-NCe<(<0]z~Dz::-) 'Ԝ鎛I}"I7ѭhLC~l/V;9~=6b +Z2B8p {;!5Z]'s%g]Ŋ6eiZ0m^J#GeOVݸd1Lװz)yѠHZUeLCʎg}V晩)"poܩhR4ax4|pfRuW]GN=hVgKiƁ 9TC7))Wt s0(%P>XHd 5L)d$*4Ž$S)}yee +|_WT F{!eKxkYjGP|P#4>NU:_Tθ#}|ˋ n˾FqU73egnR_6n7ȜP?)x/m1P+_VXf==>WA)bDzJ/CNP +Aol)Pe 7TP6bjW g!5fR JL[5UXԚ2,ϨFW`LǨ:Q4eIpq%,WìOybl\ 4;sC /ht˒J`]㗖O bc\%ܒ7+T@%*HjDsCoU9OT>J ͢i`ŊEU_VIjrCdΡ&%Zeou/VaFEoL4êӀ./jL2dcO1eNdgY6npzQk G g7 Vmʓ4^Ҝᆭ3LOR?Te&nj:y5pE֓nTZ*Rd Kʜ9\ Ѻ^F)4=_CZ#\.F2/D eʝTUє/;AyxJ1%ݣS,Sv&Szr҇DG#'^n1E%1El\T_g36!۲%d^Ɂ2/'2f,+*S#"z_zBEo'ˬ#޲N&1:-Ҡ J%>D__w xafW*\C}C?b,6a_I|`Ur5ry_gШz-,lQ6b`-*UR5WTϖ^xg"nM7mFS" 'Н,4,ר9|8WjQ)Ûźg"wޒT´[bhej4?YeVXIzҫ!e2ˡJ E_70[z&i) +endstream +endobj +477 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 108 /SMask 679 0 R /Subtype /Image /Type /XObject /Width 411 /Length 1115 >> +stream +xMtyz(t$wfIRPVPE*EEHzKŃ-(هox7 CiM?/,Ïdt:$I$I$I$I$I$I$I$I$I$IjxqP, (+Lj"(-i4,۞^>eUV q7k[3 +[QY4,㴌*ΪM Qie/dfp[ϒ$i~nܴt⼞OмJ$g Y7o'vG^;v,:~z}Wv*lEYMI6}O◿\7|7K6ޖ,Zʎ/{fe/kg/hemNO6s K6(lspOjl⼌O68!a8[A=;ǎ!axVL36i?sCp7\ʠAk \ AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4  AaЀ`4 - ZiЀknݠee3׃VN؉Z? v:,44hiw>Jṗ.ήa gWhw{CpwgeԠ-˳+rÁNN%jp6/QZ^}-U[A`k:iׅݹ灵'qZ$I|v=:[?`c9{/|k/l,.֬$[t6hW]{*ˣ&Di -$de;YC-8|8Ӻ-Hz@QʚUڙNIVcO覣8/)K(m)VYZ>+,N(-i+$I$I$I$I$I$I$I$I$IpJ +endstream +endobj +478 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 111 /SMask 680 0 R /Subtype /Image /Type /XObject /Width 40 /Length 257 >> +stream +x= ##%qUՈ*]jN2< !B-ݾCNw7RJGVwOCENr\3z֦7CU5&lqGڟ穣vi8Ml}-Nuԋ}ݨgTƺ"BDݐQ3uU~k)Q33};}쨙+zJFQSowV_p½Z/\p].\p"Bxsa@ +endstream +endobj +479 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 115 /SMask 681 0 R /Subtype /Image /Type /XObject /Width 40 /Length 292 >> +stream +xK0 EYF[/_Q^x{FQ;8NͶm RY)%7j&3k^3񚙈14oFrH]UQoj;*-jQ>4o4NӺ%KhzHUwhWDhyyWU+❐FDqύ_Jz,L2jk +/~Q7j2Ł}^x} (»(»# +e +endstream +endobj +480 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 115 /SMask 682 0 R /Subtype /Image /Type /XObject /Width 40 /Length 292 >> +stream +xK0 EYF[/_Q^x{FQ;8NͶm RY)%7j&3k^3񚙈14oFrH]UQoj;*-jQ>4o4NӺ%KhzHUwhWDhyyWU+❐FDqύ_Jz,L2jk +/~Q7j2Ł}^x} (»(»# +e +endstream +endobj +481 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 114 /SMask 683 0 R /Subtype /Image /Type /XObject /Width 40 /Length 258 >> +stream +xM + #KW`mI)Y ͭM9n,0j=uUMJ?kSGݡsqmq7Z꾟QX/uE!w&"fjS|1䩣ff"(vQ3Wxλ= ..\pj .\wqWk .ܻ!BH +endstream +endobj +482 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 112 /SMask 684 0 R /Subtype /Image /Type /XObject /Width 40 /Length 256 >> +stream +xKn0 E,C H$QCU +g"A}@'z<DDDDt.r*R2]{w4ffkf9inq1r nU5OTx޸:SImwa׭{:4󊈪^tZ&""Rk]jbSZEWqN]k}Bw$^SpqqWmg\\\\n;~fwhI_ +endstream +endobj +483 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 112 /SMask 685 0 R /Subtype /Image /Type /XObject /Width 40 /Length 257 >> +stream +xM + #KW`mIա]x6B!tw9RJ)Z= 3;]3qLDQ|@[rX` 9PwW7{ꪚ6j֮7CQJ4nx;}?GS/!w^"R 7."uCFMD> +stream +x= ##%qUՈ*]jN2< !B-ݾCNw7RJGVwOCENr\3z֦7CU5&lqGڟ穣vi8Ml}-Nuԋ}ݨgTƺ"BDݐQ3uU~k)Q33};}쨙+zJFQS…Ojp…{wV_p½Z/\pE! +endstream +endobj +485 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 157 /SMask 687 0 R /Subtype /Image /Type /XObject /Width 363 /Length 189 >> +stream +x  Om7 +endstream +endobj +486 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 117 /SMask 688 0 R /Subtype /Image /Type /XObject /Width 275 /Length 2790 >> +stream +xsT/P6˽{n{v7! "{0A"/E$ALqSVZŗZ)C20--gaY3s~sxKJU ϭzBA +a*VAqYEE&Uq_("ֵMϮ'Zi7*>T$4ӪkjWTȡ`$m_ f)3gMhilz\xpIPE˧nJ wwwm}M1Wnھ|xr7_0uNw}pmOdUsO~Zۊf)!SUoҦSk&Mg|>sj+z'+Ǎ㟙BlU!S-u00fϭ4cҦxbɉ/ On8nVuK [)Re9U'Ξ[2*w!bG]U>$*&Ϫhϭ}ÄC/vͮI;$5*EWF7/^:gUKC(Ӭx -3CK.kxP'~mt^kfH%]3ja;;*]"ߍizJ5k]*:NYESLh̜5s7_޳96qpf5:2!F4B%t?/<..k\UT&T;2!|ݣ/  ++=IwAI9$B5;^47]$<%asu2bI/Q?sj /vENTȆl5Z5NWM!ail*xœ\ϝO>Ö ZĦl|'a.{"`0"sgL':K+n#gtO`L LŲql7W.pX]Ο TlYCeAǫ(|w.l@ L7{a^ǯQg-.XT/< S gnt/z]xKΰdY5gsZG]£0u51)zdJ|g ? \8 +f9<Ͻ|xnf#Q)> 1*#Fnf*\/<:9I } O<6$ɹ1@xMeP +'7K%޻'a<^ȬghVOiػq.ۡuk'a}WwW.5xwwV82{=mhh\(<_w5Pw Vt4x Ló&(tpdv /]Q @zo:9ou˅'a~uh[6/iQ Ȳ0 ?<;=QΞ%eg +%<ӐOG+Rk3j O4|_&@],f^'O4<#1b +EZ^9Nxrs[ 'dƅ'an޸jh /aw_y;;b$Z&uL;{ Egl3+@awZ4+>O oN/|syZ|𘛅3"GXs]sp@;EgwpnoD/}7moH.]7*u}:-%iKT }|£0uc̀[Y9ZGZcC |}4 Lœvi߈mVV@_l3IyޖZG0LfKSHaﭕ9Vx ^׋}=<^2\U8jtͭ;Cx,oמON-0D"RG U_!D`հ Bf,}xrd K'I|z-5skk=B!IxlD4G *϶5 zg~/}6{g. +>-*IЈT+n +eݔF>wKxppx{S4ШTM9 v;Q*/ܺ/VC_?tĩB~gX,kOwʺ ەFⅴ5E>#s YәYG#;QIrUFd('hySD^Oojl_Z&tEQ EG똫٣B70d& +endstream +endobj +487 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 152 /SMask 689 0 R /Subtype /Image /Type /XObject /Width 164 /Length 1296 >> +stream +xn:y}2~""@ۓo%YAWʰNikP) d۶msQhoJ}ߓ&ѓZa٤kM>1G@aln=İ谘T~H%uG[H}m!F[B[gOh iUr0h]ICQthXܝ%y0o+= fDqW@lNht`v?ІIe U5U%VYu[Z̪-\y[֘km! 1|||z\]hBw9Ӊ@YޜbδgZ-[1f0)~[mSqe|A@3_\uqJ!(,mV"K +[7[UZ]A푚@9;#)"*=4,I Obm&&3F̶m 4Mu}ߗJ@V'/ݗ-2?ɧdĬ.9 Zi0t +endstream +endobj +488 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 117 /SMask 690 0 R /Subtype /Image /Type /XObject /Width 427 /Length 2934 >> +stream +xs ,jevg% A& _41b2SD$I)e$%LK[qt tZ_R[ov>1V}n_y2y;oB!B!B!B!B!B!B!B!B!B!B!B!B(赱9>uY6gέ%e l鬺j,4-u]7M}Ck?zmlh`K;7͝9x/=|o.\0o–jif +1zc}m/^t[k˒}޽tJ$OfFFj̔AR656n_h~sğY\VqWWǶo7~y`׾G6[4wbEDHlJAz}z.ZW8w-j]0:ニ''#Vw3u!1+jokiiʔ˦ݗOO&;pj;0(Ʀ+M8Vzw47joK22*Ν&*䐳'R5xEԋX۾gy{KGɇ 9珿Ls\xhۆw M2޹|^PM]shcjR/-4omڶΔ}jd|z^_)%0e9E>;( :G>7ό a͖唜7VVך鲺z~z{[KcC1PRM leu^U:ѲJ;g Pd}/|? "Wgr)iu+afn6/\_ mS +S[2Sҷq;gC + _<W]o{@[jKӊ$('X<O2T杳'~mߗQ$Wt|wVi$;2R9#m' GYE찚5'㤸%I՚UQ<9 6`TWefwwuO|}*D,#%pW>>|+|u%//)7o 1ot|7~-Ԑ(%+"^~?.9v,`<s9xlB"1bdi 'v|蝳& gffefWO)3\fvTD`4!lfO|m~/j~.3W̔H>w/g*Rg P'2[7O|kW"wE͔:F>7x-g P,0XǏAwOÉ)i15w/#;7B{O2!AaoUKTͫ'u|x?'3{{'ub#or΋6&O|ҪN o {v omxA"!:ϝ3|[Ww7B{)!̮#V]d-T0Ə#K> ;G;/2t)!?eJ QÊ-<2fWoy F .))ݾRy'̬| 5}=8dwƍxsW#[2ݳY3{r18+S-@w? >76L>+:A;?e`cQ,翊^df"'3} p ۱=Ui`0hf7ìYJ>cW/Roڽ Zf#ӯy|D{9s?+>h4+kf,ߍw'> Kiy%z +[DK}GSɧ]/|ò|-gWF ?YӊDqs0V;;@`JAz}FYP,;]O G@T_]Kbg?b_:$ ׽h23ýQjz}nbcꚺw'䴮_ɜ{z]1Msx0 G _xy2~i$gUdf(ҳ8/;/ʪ阼'L쩅v uW7J$&/Zn?I5%gR//TvJA0ŸXquSO74o'y^rυef74>wOO&N m%;Uss9o1>q(-54!6LW/=um<я١X*^}I75ݳl|b + |ʅ޽yMOUMޓ΍j3ˍג{(+f$aLR*Sj0fu +fX3&~>O -٢n9E,)-IIJ$;ODT;ʊ V̡}_.I*uIjciAJBYB!B!B!B!B!B!B!B!B!Mt +endstream +endobj +489 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 126 /SMask 691 0 R /Subtype /Image /Type /XObject /Width 129 /Length 1036 >> +stream +xݎ >4ьh"P6ɜldjl/Nȗ`۶'kt,N),˶mu]#Rj篯O9AZI)!<䜓eYZ̳mtW9,K|T +a^'y]ToGG8sy)$GBvUwS'y/ל|^#4 +:CEptֶٶL~~|QsM~Rg +4;_C:z.@R?. /NBK:ޫ ;Pg^qI`hvOA8CIRy) +!.F4MCr,˂*p8_G_=eY&J2uV$\T +~-j>}40 yРN^鈪w0 ~#N/FXQ +%坟yno3- +^}kR]v{^:kf +po'{&,#[IC>c/~c?_~OFQx,@:m=2h>/J:.(AՅ-Ϯ>?? ]'0ncFT#ùϓoU!_/Q] @+?bW1^X  +-|X:S:fA!T5v.c7 +#0 +wG?_!c "hSz+ma0JD<D(1IG:(qLٓ.ڝfbc (4Zd#޼)W-wڸ-و Σ9jAd_ʠGxΟp3Y=m{qJGw}ۮ4Anb1<ߥ$/D  +endstream +endobj +490 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 127 /SMask 692 0 R /Subtype /Image /Type /XObject /Width 201 /Length 1427 >> +stream +xn: B!ﻮ(<"B.4cLQۈ]^k +:,/~,kYiÿ1bѵl҃cK&]: k& r,DTZ;UܟH_]_OOOIt]^+p< L+YJ]CVEW{Yn81{?I4ꬵUUn;lt%;7á*fǾsPgﳿbM_rysCa9׶=$/qz_cܟ{}2C>QeN'cL]]ћaƕ~Mu=ny2c۶?''Z]I`H&Uc3HcIh0}/5d74d<<l1i&IVI鯴ŗxYEQM +#޴mδ,MB\nez`rrI/\V[G"[]Zȣ`&(Z+9,b =-q\QIcTu]V<|sϟ\c9njh[cd~6^^^jm[R V݅$Q[YHɔͣ)Yb3!3| 潏Ā?%ι$s6]lIЊ&dp +$qH^!4$tz۔F=$M"Tm.a4ymw{=(x$}FIW4V܎uɂ{=ŵzR9֙!n&v) &cv`XIZB/Iz02woeYN'k'kjZI >$%,w8keSl +endstream +endobj +491 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 693 0 R /Subtype /Image /Type /XObject /Width 176 /Length 935 >> +stream +xAhU.؈ٙ7ݝ7fK[EX5- JUDPZ*H ĶjD`CAD!x$\&ӝ-{ߏs?` pWj®UVs66IV껂$&~CN Hrh['ɆvL6Qg# AsW[[1LL6 B0.*]6IM]ʆ3Ͼ2ɓ/-@..^R.:No'"]߃ٻҗg{5tc%O 5Q>pC4S[{?wg}L:(uL KQ`kRp8}X{_zs~Sƴ!`/zͨNuOhO  x1UNxJwhm9顠UhA,=PJ(JRz@)=PJ(JRz@)=PJ(JRz@)=PJ(JRz@)=PJ(J}Onz(w6m\t'Ҽ[}4a>zU}#'fwi/ C=<~K/zgoh>z_o}}X}AԦ}?mrw>1?_>oO, {H2ҩ$+fso\ +:wׯ~ŧ?pˊ0 +g1TVJ)6m$\d;%Ol5NUhF۔nqwO(_ę2vEbhMV$->4+{3ox29 +endstream +endobj +492 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 608 0 R ] /Filter /FlateDecode /Height 97 /SMask 694 0 R /Subtype /Image /Type /XObject /Width 176 /Length 934 >> +stream +xkU.؈ٙ7ݝ7{mC# +6VmA(4BNFzЃ(*" P xOP̱L;Ͼ;63o簿 ]4;Ԅ]ikJ lmw%I>HteE3he2%&ѶA۽c 1o6v6 `DO[17 B0**]6IM]v3fw_p,:pui̩z]U"I8颕 pp櫏wѽwnqK)G1DSqjO޹}&xrDO-"(/ח+H}]*ĽgYCyd!m> +endobj +494 0 obj +<< /BM /Normal /ca 1 >> +endobj +495 0 obj +<< /BaseFont /AAAAAA+TimesNewRomanPS-BoldMT /DescendantFonts [ 695 0 R ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 696 0 R /Type /Font >> +endobj +496 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 179 /SMask 698 0 R /Subtype /Image /Type /XObject /Width 253 /Length 1198 >> +stream +x]UyDIΙa̙iG,EXFQ֖h*EQPP+ h‚ndv4:=}?'{IX`F8o]S1r&-왲m%$qI`Ou3˞i& +{yV{V]3-LaL^[W$mJ7"UqFLqU||-N:fbƏxzf,,q8lboq" +x1꧓͂vvv/v!28Pr??]k{I?\}Y(7^ h9wnÜsۼmϴIS`{vv{m|@:4I5=q[s7=hzfl E'7Nɾ"{k1졁!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(|6%{l18T6fm/?IS`8!X Ekofz;*do"Cz EO?BS`0^yЄލ{yվѶZ|ply6f5@;$?:um/$|yx65t{/Ͽuz {͍7/]]?>O.Jz0(LF{me0L)ͰA.ȾqFLЉ]ic;*0WX&kQx&vad&oG7;U> +stream +xۊ0`ߢTX4f,ݶwĞ#:hFD₈Zn1ss;5"ιL)|Wt~13ѭc0&ZTΩBܺDz#?ϭwW+ZVezXc~v̯qԓKro^~E$,J2hsw˥lC̥Y0r9N{Pɻ06wP=byOLu̴p(V* +endstream +endobj +498 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 180 /SMask 700 0 R /Subtype /Image /Type /XObject /Width 220 /Length 1541 >> +stream +xOovqֶvK N\xCҸӉ!*2VM;"'zԧ39fO~z>J{׽t }/],h(͢~ ӫQI0(7镌$ "LǺJ2\M0_c +j`~Ḱ]82.6_}..ng4\KFaqt%H ~<ޭw?~|8[2)~yܻ$yX'k^;͝|ͭ9m?/nn>d" L0ɏ{~AV|vc( Adi0XI4hO=L& 2L~d"'- ƛwL& >2d",|dDA!a#C&2NV!փ  22o2?22 >2d"|dD.10Ȑ`x!ˊ#C&EGLd +V2>2d"39|dDFq2ȐLp!|!iO!)a!m!u#C&!2Q> "2QC.>"]G2>ʐEтLtFd#hG&!LD1ϙc,?3p^e"E|D븝>r2|lX&"8ÍLDq홈$K3yve"'lD>~26|{ngx+?1ójY +:Ϯgzg'Y3d"ǘ|dD03Ȑlg~!Yʖ#C&]GLdK p2>2d"|dDp5Ȑs;Ȑ4!Ȑ*Ȑ3Ȑ9Ȑ:E!uEZAi!OGD.hADg#B&:G/21f>F<|m&"LD1?c3Fg"D8>.q |cu&"LDE+27g" D>>ӛ>8LDg",|p!f"d:D4z&"2j>8|A&"@3*>PDh[tJ2\Mv9viAbL4M i,Or{|&Oy}wT9ȍ,QR8Kts{9 +endstream +endobj +499 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 40 /SMask 701 0 R /Subtype /Image /Type /XObject /Width 141 /Length 471 >> +stream +xj0 Eo -,׉d~w0aeܑ0%Zb0@7)@Dk]遈D(л(hiL9g}B1RJdH)H2nSCDRw'eLvO5g.pM4c{?3igiFYvlqJ_d|VzhjY3HD{f.ؘs1朙R[46uͳx-G؊]㱞9G6cݻNgK))yۭWL7KǴ>s//zoiwn:oiߘWg٨WW3U=F G.CDOp-˲7??oyXf^/$+6`6j^~=8~l60MLt^dw_.zaɒ]> +stream +xWG/#dG@DE\1⊊ +hDĨ4`5"Ѱ%5 /7ޜ۽5S00 39GzS#@ @ @ @ @ @ @ @ @ %ImXO$vvIBQ ^U/,N8.;Z&4N34_ᤜ$Ys Hf8`MR$IJʽ !i(⿂f)JNrY$d~R 0'hn$IIlEI ȃ&xy)!Ǔ2L>" AI/x:;͛7ib_T/O4I$DR/9+/2$!$)e!2|DrDՋ$=*%HzÕ.¿e0:b z_ih׏?\ug]/ߣR|,_6 ;yq +y(5QX}ң +WWɴVyae4i-E1$ MFF#]:_Kc[}5F;;9Fq]/ U:7x@Nq4_a_F`h-✽`!.o}' ЦpJz-&|JY{ _vDGﲬ-j9 i+?2$|qlaGPGtRh~s$(}M'!Fi!In%=Z-l~x@NR$C[h^;hZ2"hשLQb^G{sAq ~žJwH4kžISZJjᙂʛ7^<Vk/_ f8hK\4ASߗ\,X~-bLo!W~JS1F2߲lojnNN۷ѣ-6.pRaϐKs$H9XAra`yDNKq*/8}(?x.& ٳq#{V&LYX^!;eàj;~A11g +=|);:n_,/InG]Tڅ[ |I釳=JNLŋƪʳ:|`vZJJc 1=ܡݦ|°P8fģO vQJwu.95k"B̡|QU=#_ 78c@?FڟyrZ GKO>tv*2Y?h30BVs9,p*«Wx!eH?l(nO\ZCd_z"€Ѻ e-U`'rs]5n?։RjD9]\nL?R[^4瀏Ux>_sHmVm.& ~p ?Cכ??XPjϣݝ5-cgtVDg?39F2" y|h] rP@}~4 +a#LkV%<}Tj ;U|ڟ%dMn|ܪڰ#GGGG޽z!eQ}RT`aowGŐ҇c'(F=ؗ.ZKmFXZ:E8d=0ct[ׯ]t8Jmc C͛ViD:ygX\3˲gPx@'!gBjIь=qvv#420 F$ Ajͭ|䉸x47'ZEoM^\&֭? dp^Ov$'M ]Jy +˰D_oҒ "p =JbIlSS[6m>Ӛ?3/zgD)YYTaK/Ruj"g.9vԩq;7l8婉a$GX2쬳7 +%$VhGAUj3Ju^WӧOa;䟱; $Uޅ%3DZ2VޱP_ ~=Qؾm(%h7Ŭ\Y~(ͯUZu+-|FP??ogT`/T4_aY;Եz3KTշPu^8Cg_jz ØBk?T][5~zffgX̝rgOsqpݻ֦V{[a~ +&?I)!nxfd_K}S%n۸ +*CUۭ7JjKJ0NǻvR?n~a)S׹99׋0kdXԍ5 #Jg +3(_ybU+A#fM8%rK<^ISS(% + %%&?QJ?'02ƍyktuuEGpwRh1 )ׯY+[:XZ׾,TfbTaqDž{b +m%='AOg!AUp +B;JmՑ"!sgŭ[@-q;DGDnݜtt8zU+\gN\̩ST\T ~ .hU114kn]8pwԶ pvbŲڅD.:Yxz.ZGZ~Vp1ʮTkݢ:M2LjS B={rQ&sAHgI]C-08>3?@Z HuxF-V?zw[u ;fw̱6A>{#7͛7]e__vqvvRvk=6{᫒9paV4{5u.0nԆ9[<߾3`fSS10'"8ezыx|&C- I]]k_9nIXô*Z3p0Ã)7.f3EoTq&IpEj4BUs#@ +Wכ\*gRjN20Em[mbɫB ^$>荢SwVo޸UScxzTC)w3W/j)9sN( ~S<43YaÍ ծE/[TZ>pdvA>OSxD;:EFқzAV?f$f>08 !uA$ A?=*e޾dlQ"ٔ0 dr ?YnFp4qwvC>Qe޾G~pvD:weWWّ73xhy(c4 ̴LX:'[/h2fTG fccר<'z1LR"3-~luWɪ +UgO Ð4-̣>KlllH{+A EyjjYx@b~B9YwcUÇFqae2'$ kV%p[kp5+,)M +/ˑxN<+wsX怾y +W>'AtԡeUp0x.ܘ؟p#88~gO ŷrwvIݩMMPwiЇ//i=pu>ga!ӧ {Ȭ)r%/ڄŸlSAɭ Uz +`W#43$O)8s[v6qSeqH Xb 5"JJ~#L.ʡY~pVYIӜw5{XQc[ꎝuuBIɒ>@-ڦhlll({KmË oe Z/TWL[ /p:Jd666Rb8GIKΫȰu +?#|Q񭪪;tuc$CPq$Ed +M,x@=0#cXp \&h9 IQ'nAf CP`T#(F4jIQ֖0jK|¸ֆ&C@ @ @ @ @ @ @ @ K# +endstream +endobj +501 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 213 /SMask 703 0 R /Subtype /Image /Type /XObject /Width 310 /Length 1314 >> +stream +x]ofឲ1r2m"~A|74b蓝uQh(OV\+8 a>|zon\V^e7TZ?e{v^W?_|TWXvȪۣP׫쇓d:Hۓ?{ +b.۱eۓիBkvm]Ѯ9=d;/ںк\6mMs(}ٌ$lE;)vuI + + +ɦOWE$O ~GyMwYՍ'g#N˿ByBA2B2B2B2B2B2B2B2B2B2B2B2 Ib$1Ib$1Hf$sO$S($S($S($S($S($S($S($S($S($S($S($3I $L9$Ld&A2A2B2B2B2B2B2B2B2B2B2B2B2 Ib$1Ib$1Hf$sO$S($S($S($S($S($S($S($S($S($S($S($3I $L9$Ld&A2A2B2B2B2B2B2B2B2B2B2B2B2 Ib$1Ib$1Hf$sO$S($S($S($S($S($S($S($S($S($S($S($3I $L9$Ld&A2A2B2B2B2B2B2B2B2B2B2B2B2 Ib$1Ib$1Hf$sO$S($S($S($S($S($S(?Zvڡwl]9cΫ=fjkoW{\e;ݾhEsȦ9lڱh9lbZc7.. /uS(?\)v;ZO<—o9eWw[~}OQPXuBN#mo8K{Ww߳}X泍BybE;\nsV]Z p}쏗87WJxw[xa||\ Ϲy_ߒwNnݰw[xjۡyVM]-ۗvɳve+۫> +stream +xkeИew-;;/4K""BĂ  mH = (*z*AB zPһ<;ɺZ,?>YζZ1c1c1c1c1c1h ^RYdehTLAZxzr͢Ov\{0VAZIXi; #O3.:Z#0k7>A^9_07 C^Z/qx:ds'[No̗fpG6K1~tw?˭[?ݯ=Kwc/{} Mړ{׮9?(y,cN [j8.|<_}( 7׏onAZXjxPÃqGWp~6y罿e E?q!@|{ذR2=!@8x;Wm?pSrsɮCad'i aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaspIx4eesѳ;;?H(/~~ϟ|!@ڗ?\nZ +2Jk Ϟ%V7ɧ Yoş}k ~RijE4{ꙟnp~PtÏ͆rtDϾW T]ʫ;xI(H+?1[kԳhe֙']8:uzm}c(+ R{֚Yd}~4Eo`> +stream +xOo6qAđHW  +lYmyzl6I*_ҊxNl~@r&4Ѥlj>`q޺P6t^4cYC+r>E~^W9Uۙشq6Wu6t/n_`ͅݿ3mr lP꾚4C6R42ÿ?5S^6}8oo'P1\z~H!E!C,YxC'` wy|p'0nhh⺲I%O3*#2WقJ.X * Y?d~"E!C,Y?d~Ȣ(`, +q(`, +!+E!C,Y?d~"E!C, +!vȢ7 +!vȢ Y?d~"E!C,Y?d~Ȣ(`, +q(`, +!+E!C,Y?d~"E!C, +!vȢ7 +!vȢ Y?d~"E!C,Y?d~Ȣ(`, +q(`, +!+E!C,Y?d~"E!C, +!vȢ7 +!vȢ Y?d~"E!C,u=O_TTFSKo^Zoϟ7}jvd2ןNW/}8 /?޵< +|5y?O||u(V)}[+j_]̮ޔWś:ZjO&Vo>Ͽs1T:Z{ +jO?x<9\-Nj_}9բxÙ5 +6~0W& ζ_G~|$Tҵ\mkMMSeG¿c.TWiBلӃcx6m4)B;P4nO;MM"Sܳ:bm0w;w4o;"H[2cKwuw fG2D|܄6q;uXl;̧́(56~(x|]2F~GH> +endstream +endobj +504 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 146 /SMask 706 0 R /Subtype /Image /Type /XObject /Width 214 /Length 1096 >> +stream +xke꡺fwwٝMv 5TDAEA$=hbC(5H*xAR؃P"Z-zIfgm*S>/f `1c1c1c1c1c[3.[ݼ473Iun>EYE͸$U~bI$] +۷ Q. +)u~a5 LV،(;y|.0;|8{kؕN.Z|ǞWtniIJ7?ݏoy|zqInS/p`nr=}|0Mo8?J~yG Lo`znn㷏Bo߁M<}̙_xE~ w +ٹ~ !@ۭXӭ~kC/OX`AQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@hQ (Z- +BEТ@h +t]YZ.m}SeNR}2+ +| ZxZ~Z?qzRww^$?HܼrCOt (ѣ?67ǿ.3 + G֭7g?S&ޗ~[U~"lyǖVϮm`W:z[ᄎƃOSg5wQ4bO3k_0MoMԧlV= mVk()z/Ʃnj MZ$A'JdMǟrin3] +*?gIgLc1c1c1c1c1ݻ + +endstream +endobj +505 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 40 /SMask 707 0 R /Subtype /Image /Type /XObject /Width 160 /Length 911 >> +stream +xKO@ -N+H= qh;K|uzWvqŔOhx{1 0 0 0 0 `$qл HL.{gO<{wzߵ[ =h+txXuzG3&>vy m:Ʒ0aמ2S}+s5:5L-, ӸkXo$jƉT͝wf3Ue^K2%-$ +^B/G+[Xc *q>>NHlaq<q`l .F,v`O{R_K\U!$NL{\@*ث4!q^|5pL/V2>T4py(BVTt!= <&!-,ڑ[SmwRgm7;t}Y; xt'vڨ=BӠV-x[}jeX;}j ;t.w[=l>w֗VE +Z~nbKW!b`jReSmk焪|Kw +i +0:* v֨ec }3(2VSלgR?=tMf0N=S84M,Z'X$tm:҄ʮT} {mBS=vqՁg)'SgN㌺rh86]*D[4 S~h !բ6GߎMwFżZm}~ASME*Bڊ> +stream +x1 EA  tc)|z]dso+]]]]]]:]]]]]]m6 +endstream +endobj +507 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 296 /SMask 709 0 R /Subtype /Image /Type /XObject /Width 124 /Length 2242 >> +stream +xnۺE^/H8>۶:J`XmiUq!Cd*䇒9/9BJpC~$9?Pc@BHrlcUCBu\:]y%:%x1mL#iá1WuŤcEA&<[Xyn&-!s5D1˲8EB9 N'>\,t:=-`‹n:d;4 /aeYҎu897#50t] ­0 rιeY6#pYʤRa*U.%@A,Ƙ-c:jq}::=O'Jpi^N|XV\V(Z5_.*G FsrXVld(XVeY"7shamuşaؙkaz`3}:sP _OxnQ#<ǜH2jFP G,8jQdν:edQ.<,k~\آi˪Tsb9佗P\AA!oy9NJ:d?X.ϟ?#m/XP;MuDK#eYib!4ѿ+zs[tj}<O2:uDDDy8sߧ8J5W#_v\+!dU|4DA.]We g 9纮]AU:Y,v~U.*Q]tm+7B0XU9\x9އ~}. +d.k]tIh*1>7\6Lʼn]]˜+Xb>;8U}{uDDT׻WG ?]j-Xh{y兂m2>]^E/|U,î4{ t\ +( ueы}MJKΜ$vϨy +) ٱs^Zu<.k/ +Ico[GT?,-q! +GC8ʜH_ PrL8NcFu8DT!ʎ +ЫV# AUҔs*H qU.g!UcFtE@|۶AUl< @-}{HK2缇DAVb/ĎHU:o{dj=$҄7ch 1r54rF*B]1:s82 Cu EWx:&djϫɓJFaV%b<=2d9oHxݯ_j*MeYаX[5ˮB-1k&}e eE%(7>\xdaR  d³+g_ y'|M㋶my˧ap.DOh~}gN>'u,Ynvv/_%\,s퇈9",bc ]# ^t:Y\ymdM|>}t"MS4Yڙ?jJɹuP_`b&t, +endstream +endobj +508 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 303 /SMask 710 0 R /Subtype /Image /Type /XObject /Width 133 /Length 2250 >> +stream +x6`=C~ـȈɴHI[Qz:<ߏ&ReHvȏ}Ǐ!eYyהRqYu۶뺮<8?3MS1ݝNa9s!lۆ?̈+Q^Dxd39BӼ~? ψR=f9^;}?"Go~??K|>L|j ?cu?+/tLg,=bw}ӓ +˔0r$?^͉""few]~шƛ/e۝N'đp8` $"'{M:,l[  +S(TE?hALFχ/ܾv ?gծx{%Pb}`vPMoxܶx<{vt]}IDcAUDż0 J_g<IB\VWLkka2Rم"BEUȅK<Ϩ5J Bi{ܗCCk5O`6Qӊ;0PBB놖 {vjRvDM+*5zBTl²Fa +#jՑچ|/ɳ#j4M MQ糟β}BRٶ7څ=#a4;AHDt:] t~D tŖ˧CCB:Kǯ +!#hCBiq/;ga)jx[Cg8@iGhR jR}IEMp=Vt0\~tjGR,`gK Fo2:#jj'ƨ9"Э_0 uw JT^ vDMsu`"Ric4;!Hu;=|tYJWqejj6h)"R-*Wt)"#숚Ε/TB˲ca4朇aIz<ϗE׭]tt5:Tf]JԬYyťnUiWڤFmuHlʰ#jͱ+5(ΠV:)%Y@76P:l3"RvDM+.7W:p:H)YGWOtEGW:a_#jzVUǀUNW\tpCrKsV".X +G#W.;]ǞsV:p53;Bv p\pL!b̡&OeWH#jUNW,v(\QI%Pգ;OE +UsY#;[5zn!I)qԽ\61<#;"s؁O]W!]qJlM5C:;*W]W/~@vP +!irqtVMvPqg4EzFQ? $ ;v6r 4Q:tiI+D4[!(k>^tB=t[UJk=ŽHTgg4pCjoXvPԧbRT4BR.*:XիRQb9AqV 56)/˂zuvDҡX؁ȗyŎi~A߰#jīr<v?kcJEWb}U?|-숚$mr}]1F?߰j/NXUk:«v3aˌ^+MLy4γ*։T!h7h(a4Ĕ +b!JVj;Xwa@R4x>ۯ:ԅΚQ{jKqυx.!#][\. +T څaGUha)c> +stream +x nH@ɐ +endstream +endobj +510 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 40 /SMask 712 0 R /Subtype /Image /Type /XObject /Width 181 /Length 820 >> +stream +xMo@8]|%[%J mczkD]Oowq[̣ g0!     r sC߱ 'VH(!n86}%︐dH !䇵x<[~'7} cm,>(Krso/ 9iwHlc]O$ v|6b\W1"FswH0>\c_IVsLAN>Cg\>]. `/s`k.sR:\d& (4J|4Sv:N!NA\P0Pt HA{"'P ضeQB(4xafu>n0מs;Gt~P[dnjloQ_vؑocd+ɪGy@BUw=Ef#OyeY;KҲLno2id~k}cֽus@:<&ª?D{md9P뿥t3oXͽ6/I~;8i U=n٣P辩XthtHo|ak|Օ {9%x[*w< O2隆+ʛ+7H+o ţ*6WtxˍC5DZ$@֥:BP1XQ[τ     'OY{ +endstream +endobj +511 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 516 /SMask 713 0 R /Subtype /Image /Type /XObject /Width 1666 /Length 8622 >> +stream +xݱkl[{`ѝ;[Dd*E&Q!@D}m +&Kc+ r I6$ra§尉"]~T-NcOnKaÆ 6lذq{Æ:φ 6l7\$_bذaÆ 6\po_ٰaÆ /_bϾo.}u~aذaÆ 6lx__3l>6lذaÆ??۟ߚM k~g?K<93lذaÆ 6n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?n\ gG*IW׹8f7-+yʄH)}dA @mm;};E81l&]\ 痧/f9iZ8;RLۚM8fa F*)-Gp9awjͰkg-\:(~vqz:8:XZڷq-T&]^]"N Hu +ߴpT@ ]8*z#5 Nv-?> +stream +xàSU|Z +endstream +endobj +513 0 obj +<< /BitsPerComponent 8 /ColorSpace [ /ICCBased 697 0 R ] /Filter /FlateDecode /Height 88 /SMask 715 0 R /Subtype /Image /Type /XObject /Width 40 /Length 506 >> +stream +xN0 iF`[ zPdGI[;@ZIȿ|hJ_;SH$D"H:brSL*Lbr3Lʈy4cV_cm6b5ay$c4J0匿].>o$Hm*Ԝ7c@Qq|=,g5WQ'ʇQh|=i'mPp~vHt2pCfHSV(T5p\Mɚ @"H#6أUU$ej +kᰝ -]DW?og`Ǫ&>rޭi/w@&5^X N>D7 s(+{]Y J04&W[5@(K'C)E^\ V_U9PN> +stream +xAjqC(l)9%xޫ +2wdfffffffffffffffffff6q?5L<_ޏ󥵻t\~IvLunھ]Ɍu ?.\u ut:?r'rPuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQ:;t>.Q_]|Ե~6øf/3ݮZ/o:Voۙ} /S( +endstream +endobj +515 0 obj +<< /Ascent 438 /CapHeight 438 /CharSet (/greater/less) /Descent -9 /Flags 4 /FontBBox [ -342 -214 906 786 ] /FontFile 717 0 R /FontName /ZMKHJD+NewTXMI /ItalicAngle -15 /StemV 82 /Type /FontDescriptor /XHeight 400 >> +endobj +516 0 obj +<< /Filter /FlateDecode /Length 673 >> +stream +xmTMk0WhFKv 9l[JאqXWoG3Fz&ݯL^G wwwWtYZ]#źmC^R3)M(8oY۾^86팃mƣaL`ePFepnGιVmw'trW5l>;4m=\%=zBkFd,A<Ӻ=tbof< f6?j`[.YfK6og\XeUWsk?|et[[-&o<7uH+ DVK P"B!"0PL3CQ,bHN`I"XN3M4 H?ӛTRHj-`T9 +<2POGE arۏ&:(3Vq%.)BęBxA1*81c4 +XyځaAr.YA K /]NO\~b}/8ArDI4䫄idcfiGXY%V+,E>_?ۘFl!TjoWGNKGv0O/ hշǧzTя^DRzx +endstream +endobj +517 0 obj +[ 636 441 636 ] +endobj +518 0 obj +<< /Ascent 435 /CapHeight 435 /CharSet (/u1D456) /Descent -11 /Flags 4 /FontBBox [ -342 -238 1247 786 ] /FontFile 718 0 R /FontName /XKEAZI+LibertineMathMI5 /ItalicAngle -12 /StemV 82 /Type /FontDescriptor /XHeight 400 >> +endobj +519 0 obj +<< /Filter /FlateDecode /Length 590 >> +stream +xڅTj@+fxFoc^`<ͲW[;[z5lA}Y}qP)79ve&LFc.#ۮ'&9U P˪hoj a[B򲩎\;v}{Uk[ȶNlH"m9KbXX!t7H;{wm$T.Fy]ȮW'i,9_e Y_΄3&.񰩎z<)O"J@X`b'Gn.Db Lj0r`yfJξA5.* f# @Uhk2&}{HYѝ[*\q<1Do8bSĖt +w;z=H0ωu^N y}3|ѳpS|$mS9 BBu?0sq|s^9o[w_|h[jӊܷTZ_kf_ +endstream +endobj +520 0 obj +[ 354 ] +endobj +521 0 obj +<< /Ascent 56 /CapHeight 0 /CharSet (/summationdisplay.1/summationtext.1) /Descent -612 /Flags 4 /FontBBox [ -5 -2960 2900 1444 ] /FontFile 719 0 R /FontName /THUYEG+txexs /ItalicAngle 0 /StemV 1000 /Type /FontDescriptor /XHeight 400 >> +endobj +522 0 obj +<< /Filter /FlateDecode /Length 558 >> +stream +xmKo0]P'!BHyJ, +  (h+X=7>&޷4s9ꛜf}mM&I+QA8۾ʷԱiI6TF羠1qRDRSvvsWG*4S1]򇚶 9JHeWZΎ, ;e Vy7Dz/j3P]6XYɶk5k +jJybo̶3z +:_bG Rvךбm\UAmϩY+lekdc.0 5Jp!xj!, F!B3őAH _%䋐*MPFGK;Zkfe9[Sc%5a]07:s6xi8pdXpN ̬9;{fl;K9<|qw1B=}/ k=6 _pd {9Ӝ_}pPn:FxM.%\]ը?RDo[B. +endstream +endobj +523 0 obj +[ 808 945 853 853 853 853 853 853 1323 ] +endobj +524 0 obj +<< /D (subsection.2.2) /S /GoTo >> +endobj +525 0 obj + +endobj +526 0 obj +<< /D (subsection.3.1) /S /GoTo >> +endobj +527 0 obj +<< /A 720 0 R /Next 530 0 R /Parent 282 0 R /Prev 314 0 R /Title 721 0 R >> +endobj +528 0 obj + +endobj +529 0 obj +<< /D (subsection.3.4) /S /GoTo >> +endobj +530 0 obj +<< /A 722 0 R /Next 315 0 R /Parent 282 0 R /Prev 527 0 R /Title 723 0 R >> +endobj +531 0 obj + +endobj +532 0 obj +<< /D (subsection.5.2) /S /GoTo >> +endobj +533 0 obj + +endobj +534 0 obj +<< /D (subsection.5.3) /S /GoTo >> +endobj +535 0 obj + +endobj +536 0 obj +<< /D (subsection.4.1) /S /GoTo >> +endobj +537 0 obj + +endobj +538 0 obj +<< /D (subsection.4.2) /S /GoTo >> +endobj +539 0 obj + +endobj +540 0 obj +<< /Filter /FlateDecode /Length1 895 /Length2 42116 /Length3 0 /Length 42643 >> +stream +xڌchK-ڶm۶m۶m۶mvm6W󭽿{}?/fĈY92FƌʈIJL+hbod*foBHE ci'diocijBHHI`biB`djniEO)4`'D̞0M\flioG@ojJ'SC{;],M (t#S'K;S#O KK :{(yS;*ڙ:::؛uۻ[ڙ9(ۛ:Mgljl% CC!njgdhCjdci_nJwKY*bal~ Lߺ͠{SPq;pqqࢧ'?&:g3:;SJ(忙 iA_dEddiVN߮#*HC[Kꦖ.Bg,faj`bW-Cg%] )hgncJ? [T_9i99Kٙ?,ӿϊv&Uo; L[EӇ׌w-kdA@7_oߖ2 + {x22p02q13pp2O˿߳.P!Vٙ. c3"=VTFzDRP ByOdn^>]"GF PP앞5y +=P,O*-G_. hbhP_{px, ĠX ҥ5J!zGvy3h䍗 Tq(W# {Gċy pȞXXD}Ӌrb bnY)oH=$Ӓ6q?jy!c 5{2% ه@Bj 8>,?!] +V-`I;P-{YcM9<ܔ_VL4D@9ʞN=0ty^1wQڰ.-P]/OPs,Rd(ZwB^\&6ꅰ{u~τ-ťI0+x>[\;iꁯhtݎOìzc逩/U7o?#Re@@\JxߪQ-1vx۝V.t#y|δHK8mqjG9C6?Aͧ>Z([F",MbvRͶgzA]6!CTƱklEF4h +ydxu|fƕ{;UbBA6b2f}bYǴ~؋/0':S\$y#U* y:HЁ"IMʹ<`R>bؒƣfkc3.,}ҕdQDhE{@!*, !ܽӒ+:*ҰzU?`8dw5 %h)9)tZf^.j-z~IAccX;(yAkS]mTQTņ1|%V~5 +x>o2Y%X۵*4I]اhH}{Ce b0O@cq箏 ӌޮf=ݒJ˓~2L6&սUW&c_eы a:#ytW|eۋ.#NuLA)ئ +507S {S[z'? +#Yz}]bkp{MeBL`Ү|!yXC&|h(WHM8pj ţ_ +F}? +]/%F *Eb &4vC:#KٖMAtg@&vhβ6}kɹ*Q~vl/d0>03MQr!b@ٯL"X =n.j5Iv5|u^&tȹφF) }ɷCl=j[|}$dYS@0t`כ][BfpIiʍs"ED<(6$.hD (Sf79nzH/ + eY$#QY-(.DqX ^lY!7ґD[jͽ>`ajbÉ$x;4D]pAvtE H"9qegLݢu(7V +T׍"eTRiS~&_tTw>,(o ]l1J#3c]̎xȋ΄cBI.³&%Ȕ]|DO3OPUǷsngZI|~d4Z!VJ%7j$Z-7Ts +[]5N_o׳..+*3 FMʤ 6&pSF0RDY 2F+aK;PFZ%@"&UyrW^?g9NnU+P-B/O-@Js6ZHI྽$Bfn5Rha F݈_mE,#rk{P L7H#Ve2F7?Lo_Ow.TL(t-7o!\vjB(=*0ї=#hdTt+P +bH nQU +v};_̟K2POW|~ `_n)#knp}EaNiqaDxbq>#8sEcٖ'RVw#t,<"lk~SC$ &\0oZ#7 cg%CHUE0j+7 W&` _ʡgX%{OWɦ];ޔVjMKM]e麹eWL4A(Rg@h}*ؚf71@Zz%М1U_1Hr S<E)h 3JYV=3Y$mUzG%,י.eBTlK&$<`ͭILqVmw n[h&b3fVQEɯCN+]0xfyUw + )g@D|4w8dhB,A,(O7y@ަ> ]\Zz/[Jw5&my9|`?*"J`Gxz!}j9p1﹂ܷ{+%A?jUXw#oȓÚ['KfRzxiRpo򀵜Lݕ20!n+q/G^0khiz t(? ![FׅVI fo+  IY +l55>ZR& +'n0^_!-5#qx*^|jXnlֻ1e +܃0MbV#$O]C6rpNlкwQh\ +QЏjQ,`W"~AT>ޅYPԮLd{\J=:UTJ2Es@> =SN.kQ$NN؀`^pzTAsto5 z{<:šd#cou [O[6vbOH"9yT0p)$DTy#45-,ﹽY3"|X#gvx5=n(1jUˤH'^Ol4SbofL?*4`vH +iB }'KH0Ǒ''c|. K |zLJiuCw|oA^6!hC,Pc z}('j?]!7ޤ5#ߨGP50)5>du_z5H@~_0Zy7>*lM` +O׎u9$M~5@3%;FPiKȮ0}51hwV][08l{P7G<^WdFhPH%Xij'Z+SqrmHo3-ͪy*<= EZl!5=A~ |".]] +rcBby"8sHb6nnH|P7 y0Z*~>rueC|923 L;bԗG.4>wV-y9mO1y1aȯeH*}!fw {'ꔎFˬ [xGqJß˾4 +madxqeuwLN튏0a"!1weU&AcVLw b2kqyףiLX$&wK0Fo_IdB +<@ 5ЙA`;+/a=ŊtX[yTeϑuDVbzl݌Ea5}oj*J"n0{n @4}ۿlŮ-?lCkW`@sv!O.$L:&7?uz~Z+h%uCžR)'U!Y`iIU<%'=6SH囑 + [V?%*Ch`'ϣdM< !T,04ƪC1e}_\.*Zf,:IXkz%ZcQl*n+c3xТ=7U:f&ݹȎs1}zY?,$oݝfX{"Tl^lq/Q@Ɖ?B.<"oel\Rvc *0?W`b蜎@7J3k@;\uޱΫfw@ w +˵/VK G>(Ǩ5dG wb6x +$:h7c7:?Ĉ(xY\4Otj߳~]$d}pX`7++O l%j0#mL^S63"!fk즞!%7NRLMmCr|~z xA$iSj`A<{^,F4<f[n,UjSsM/-mh᪝f1)ѱSQz?Ks"bGgX«ruivQLS ˜[^NO$#iAxϐ.ϠSɁtcį.vH7<.^X t7OoڲɁ\BE +j +r)_559Dξ#sO 7{ `ſ9@ |Ag7R+ R?9I]<*ib +EJr 6txidX}OBfΣ[ Ai|Grpn]%AHS A +e%DeU{VãxFy{eh\12G aR8ǭ!|[7v@.yPnX.)WIOFWveXJ;F3#u2Zv;`Ï *rJhX 9hf4.cKSlAJ>mIiF/O#. {1:QPٗU~ZDC_COLF F[j`vZ-C iBZ ]!$* +8x֣/586牾kAhn!F1Kvh5lcS v> #R"ڛMIX5U~8&QZ 8W 0f<^(8f8j L=ѡDplɁ|]LJF^ +$hmӾb(?`{|8ޜx`ꦱMчDl';]W[N˒VY^G`{`:SbȒXr2E5!4z? AokyyB}k4}2յ)ԸG OccbP;]ľR)MBS66w@ }T>||G9'EݏM /~vv0$Kutre ˢ$c60 `Hj;NF5;#ss']853+e~)$OY*@:F[LC$巔p.Ot=ۻN;vъ5uv-/Sy='[5 +{KRXX)/QPTetSgV}z֖;#sf)gOBEDj lm+kͷx߉Cbd"^>ź{Q +Ǭ$Ĭ8s6>N3Ӫ!%%No0.mؠ_'5e9:S~2ZoL9BQix-7p`띯F{& +6. 9(4W:Z2IQU}nS!f؅N^@A=IF24l?5=jo^̉J D1z7b xyM=W(J9cyMϓJ:FZL>c$]2$L06|y>B)ރudϽզ1S)i񱡁{J4_ +^lد.T[$q"+%']qt^R0,s1f B+UJ4 u#E=X_[J}\8ݟ +1,4~S!rf9,ZְLJt \P"2ٱT #ؘlPf}kWƜJ!RT/;22м$E s8=w*"S%vA!61ݒimV@jwA$~2e;Ŝhs)~pt8giGIwBc}߼ƴ@Q>Es_8OёCg|'h +~ӛFΰUs sWlJq,TrK:V رhܘ ܽTB~Y?c> cZd26!VL>j oZ B/qeufU8Zs;wC4ȗga6&'/w{ckׇ ZX?2 㬚.lD)o3l4y_`o.b6+wU +" 8b~ԈިL0gR./S'QnSڶ*w p' C;xuh`9Ănn^*kA g;WQ^]\β +ֆ>ŵ,;zxǃ;^&D\oIsGK01C$]կ>58r$7UYZԠ`>Ekrղ&a9x"+8.έ1rOqʷdڪ)]A:RS/{F.צ_hQMg8C=V&։z"lg{ztfO/_P֑Գٴ`;GčqF5(PQY z^ q0dZXJ 'U Jx0D7@mKIb!R#`t>N%/ +%{X6pRcdr<85>ddnN8E;tћ4I2vR{m7>97kR>jجa{!j(KEj>FQE?WohqK ʵj(3.1Ozfh瑵KPp΂_Sa61; j l3O`Bc^~uYLc ȥƳ Kj"V:a"tz+(pֈTV'U) P֛:y3Y+Pm2̯>C%(@,de{TEMnk^ZS| ,Tԫ˓*B/nV` +FTOBBxB4 p +4] {_u qO܍ЧDd>Iw!/LQyˁr6o{}MD ,T 0cQ5t0]$}9yd@M JM^"LR-,,i㔦V0p mhFk`9"2I70\7ʡ?Gޛ?ѻY)ԊOFFһ1,Z߈1 \h].fmRIwfr ng^+T9CEѴ~Զ=Ia3FE8{'l\)dgt Y}O|Az\eoiFW +VH\U-xpWjtkl^u~EvޝfϿy=ʺ) +@#<"TgM|OU‚aB LھSB8DwݵlNb\:,V:Vm%(!}L*/$}v,7 Mۮ#:Cޕ=RF+Mh#Mtln^&ix-V{L=*!"%U"t>D9+A"%g8I;%io ++XȲkl: R$Y`Yp|k7굮πpD +=ܔ(8^xY݀XSxnJ`yh1#NE + +Hm _)`ڹ:uƚTmTG>E^u(vU콲`@ZOCHqw!Q3& ބLU1CZ ~__\X#Ȥ,X?)}%y@Ew˕di͐m~Duf| iTw(H,2] I"5"=3N{OS5PgHk|nV)& +m+x5pmG<qO:Gw]6;[ +\޸í.Lԯ1s82ScI(O}Z9gU6b0 I~~v .BMSXdWӡ~{%RIwoFiR^]-gAXȩ&B-D1ڼpk;]|b?[;`nSo+?q;d fNe="m%Aå|՚c+$u[q:6Dvy\+ JJxT!umq kq{.M<&Y8$|/uqoJC|.. +URuwPAay2vLwqیFzc +S|鑳~RZ Y|"P~WMwv &//AB "@+6tI:^)PfAF]9*4MqnaZ}op8ybd'T# +wCJc=m 3upTf,nr2m Rsuٸ çq_Ȭ)yMҠkp]xsp7F}3| W&uz;&7?(sq2u't(&,$O^ h"b+1\A%YMDVTɋ&^(*MޒU{81l)z+Nx0P=p{Q!fs`Ȳuap*?j3VeS~A.ބTJVĨJ84?(Dwv&xɛOA'|.*]"QdKtXf8_fYצ9q +sP1+MM sJ6ĕGɘOAJK;%7Y!/,^?(V^Iō=&= J8 v^f)<܀s2렦:عxxۃ-'`2 +-Ƙ* LAo >/5%߇njkM!Q껀>Q}]Dtw=1!z0EҶ/R=\f3.8$MyܖE!\+M4u,$9s=>`AjX*`Y+(gd  tUc6p4uȦe^22d cȲxZ )<-]ڪPM]cCs}){5Me +iκDqel 5$nbM [Z!%c3F}438&Cir" ǵdqc֥%t;HR!OwphsN ZCW%둗rቴ=^_Zv CGVJ׹yab%!N#:m85om{^vP=}%wuo_u9.q%>>ŤUke9Jظu6yU$(g/+ufx8qGJ%/$ +?ݨC3|k7ίHB@D2]Ř˸f\F=|M6ˌ(] 89쿛\Ϭ+By7T8"z-PPگ& GGOP#@୤FrEjnmbegOz1ܖW<݅;]e-C5nCj6fo7+% Ԗ"* dN8V.u>*X /T~CHkF@z,mTtDJeݭ[ۼlcgƪ\.A'k 걡Q k$3gR<'>p8!M"I?vPBǂqrdXlzٰf7Mk:֓6ZW6eꋮB\5GTQ=EHJt`ebڧL<:u[)w^,"21U~M‘PﲊfԊQmz$}࠺{Xx 9~NÓ(60E3J?aa$G 6BQK~hv^;܇ И iW4Z2%qSc~ Y&r[gmΙDʻif ߹ r)Cd5IV0gY4/.u"&7.k^R߼JQ\ aUaSg_W0Wlݜ7RXlMM=KՄWrlnަh )]=9YaDŦ[2Rϥ3L´MuٷSR}_ÎSQ+[g6z$0NAC gc2l* +zd~)d!` k>gjI1`V-ubBs53 n|8{hu}1_0i@mUThowE7U{? +OgOSdR {kʱL;@b9kFzd.}4we}b,r +7wYW eMN^z}j!1PP$RQK7hrcwY +rvev.{%u;=f˾ 'o!-)-خOS??4P[Iyvά~Pfvuj! +ȉ V\ڵ)'vJ˄j?E }2By`|hn-Va@%eCȾ|vnyNMJi4BxfHu^c;dtIhV?}V |S +78 dnpdH+)^` Od U[ᗃҪVYΣUA7b'֝˅M"x!\(Gp#'&k*0 8IdbQ"-[P(ҵdd^ǚc-ua:U0AΜmOÌxvCFA7kJcBl"Ե2kdI9w=b{eֳk]?:yX"ܹp߬+)?Јɡs@0pMۄr,{湃; j8B<#4[/} wB6=lN 2ЮaRxKA$NN敹F²)mpn}n[DT +RD@~whu :ƽF}{˓u"<k\ #sU ya$;9`KuA]lJrJƀO@6Op7Q%uAϬ+W_b\_8xo@34? rN#8A֬)2cLCfqVG}@Y/ }9ΫiƨMBq*b~ʰA}ޚT,%G1"]eBZNDZΎb 1﷤1 +,~gdۜډE=h+9:Q6cRqh?fo^pcz8 +wBsA5ϴ+Z Vs9h fs=Û.}#/=k^HXAEŌ j){f1a}K}QR T:jG]n:ܽ89yϢ1-aڙGdDxG~3/f\)/p/ 9DΣx֨ U|/~IFNdx}"ISi ᓁ#jDjD}耸\8qR%1c|rt;T@MGr:BGUG3}~古 Aܩ䉅E5KWuC!tɝ0gU\M /SR~B\yLA X2`׵ژZւiT~ 7^uX\ =brڌO5`dTq*]pmr"_t{>#VG6õbXMcKߣ1mk rVE6)$?E%ڻ-6Ly"jrX)9R}@XI~cW3u洁Gk_CPGsn'8`?w{(TuưoɽmG,6ۏc_^+=J,7bJD|֓RdO[1bBP) Tiprt/l¸VZV-a/)fò7WΤܖMR5"GLARo`;)k`ku[䚵m-2Y=O1yh|VqK"DĈKHk ̳#@$%/\jv_Xo Ü1 :!'ئ9AC!Q +­~&cuڃ5$g؂Zv'D +IX ޳u喺llʉ:ʇoU}CʧVd˩Ɍ٣" O+C+KȽѤ~)&6gī )}w,4'L>)ҟGf*a&j7M} `A1(ϨB˻k*T< #jU`cѓÛF n%IZ&#I 䴴ҳ/:*|MPPR%^"Q^Ih#rnoH!iШX1.ok96!P +f4lYom)B獑nŬ{361SP@C+_ɝIx't=C#^A5m\'1k0F)Ұ m-regWIvFwbOHpe-ORfblD$s1"6[L:nqsìNToAf /mSzp@B_aIE|,f 7DJjGi >ITA>xZ]>O; u~j>Iq{Z;U]kgF$A8}Tc*CDZ?}n~{/N$1' \g2'w1YVνm-:bBOGXUbv-_JM\! A'Fybuˇ츜.VM`SezS-*Kya`?V[0[(n[PmGA"s5s4,q 4Rxtejܔ\5({]El|i3C)c cHEMtt# [ + Dg$ rhg}'e^+ tw/MCVРFyo(g| +PӣY.o7nKۊn`;u3g=цhRk|RڹY/"L̐jO.H;х]E7׶+ +!N1ʀ"ml7+cy +1ˆ[sm{!崃$J>N%rwMXECgI2w+ zʏܔ(־%wT k|S$nyi K0UF&W^Qlaq˟(oǧӳ7qXFju{SL{evzRB +Stn2 +ᮜ`c`Pܠ HY+1cZgٝ/9YŅ M_ k`,/ +1p&e e$L>nipRd _#٥0Og/fQULi˺#k$|@3E97Ὥ WeɔN68`YV4 tY;$NGUPVԂ[{N-:bێ,ӆ7u-{MQzN1uɷqKwB{~+h v^ ?64 Ӹ~:ȭe߯ ;D35\mv .B#}J%s8e@`m^ǝ +i3x*(^pø͔[ :W(R"ux`4^v`?O.q{=ax{WP!*jmV;.B'I]CRyxˤSW~l>e! S_oa92% S^O&ݓIlDHSf8.> +y/ L9"i.J!b.-Z'Y1^'_^Hc_ӊ1 N |{3#y0.7Wެ* Iqa[*2cZWTjHsϏ카$[ͧl'w~3MZ$IΣ4Ey> Oig&ɰF xޯh`f9$Az[+hPV*.`';\-"2G(HD +:8z{]&$ +D0hPɬtJ}DŪ5%1{EX~aoޟ + 1/գ.vo4>zAkDK@]fQO3k V#3G"!?kJ@';T ]>D8 +J*N1ˬeZ ,BFNM:ꆊ HA^TLP?])uMnؙ_hә`z'\M8,RhjM&q8-l[G9&#[ɸJ_}ď*0.Ҵx=z]8.p ARdJCSL¡BAw9Np; +z^ O|arZWZV%'#0=3Av`iS 84թisNiEV<mcurY7nX9*@(k'齱I0dpKf;T=3G1L[xHI1Ċ&7pXjI7=Bd^y|[]|I/ܔzb/+#]cn捐XoqýSB5pufjc3PQ/!d~O@Ӿj12pj5Ư}'7$z IEמnvjv0EI\?{F;MNӁ%" @&`䃘U4Pw&[Yv)|m$Z1ًٽ|wV*bv{zA*88P=/9~xSp"W &e (+3 t~n/c'n=7ԴYC +v9{`n'Yr_nL{S|YIozRQ7G]ٶP+Z%էgKC4֟[CrR6-=9 +[Q+:zlC|%!DtaZzQ4 oo+k?۵C(JfbJ9{pYMϝ1~Q[Xdy|l}e:ֈ)ҏpnBV}U6Bτ ]MW!Hm+"RF0c Xzfm5'-Eh'$/95 z 9B~HE6p7-s|נ,cI#qUE.w%5a';9`jI>BKP@\+Bzqu^@2=BSB-WM*!vMO?m 3R? x~V q59e#u0Dx2/f8Ybf;r#1=4k)*+3x?x-"Ds9</ SG'K-.Q ꒌe$wD.d |T;."  >ژ;yuKtaMf挞Xgy~0nHۂ/~Jf`fߢ"2~) ;z gT7p$6$F@'B|7r.EjD8 b,%!.]BhGѹI,$_G `*MQ C8j4#-_!y0$xR{+ڮ:{r:RrG)tuu'@`bi ++r6<0ƃҭcŷ`5)rkdkxߑ_'4n}Yyg޳j`=_=Z..Pa+nt֬D[}r^2)փ3-bUE*G:6ng<$^ˆL$H%fP|^5̀ڛʪ sTPEtm~BX*A;)~PmA1@2Nȳ0&IdՆCu/;̕VTύGyd\#Q3#TCMΊVJ09VKYTIiXsKag('r|n= /6Y=} R9g&^"+dpo9v,rp>;LvW Kl5dz6[.7ȍXDWl.I[EP?"g̣m0 -}X,͓V,1BHe!GZ/jU::Ħ>4x*boXQrai$1rZ0=(6>Vv{|xa"˶9?8vDЬ}4xPmM.47qk|Wn(-xoa Տݩ3w+h +O~k,{:mqoue}wilqL-[$ϟ3E+3l?߳Cd$Z:=h)_y O H`avwƲh' n|+S2_UzREmrI4D'Ć-()E/T$P`ϳI>q-v֨tCF{9< X;UX)|dqsK,V804~>2g,h:W˜n# +vzP=uCވX ZIV,!A)%hMJJ!xDթhKh'1k]4fNRg*sY>L$ߔv6@xR]oT;ּdr*-VZѤh_6NGVaSBsX݀d訠^ڐxʢ t? U H->-錹 >1^!س98AG L{&L!] zADE/ |3aE*_+E?t#WlXk^Z*5a 7hr6.)VF` nӘtv]jPEkiDO)rl8O] 6|;cd2L{ Q $͓IIIu}&ᬍe J:kx%.Vs2ҏH9-PԳ3Ï|"NM 9S~wl +-~mQuz3c6\Fܺ$n8SLuUR^O'5_Z9Ai+UDlȞTA^!JR<7q8g;3@` +ר4x4m׆/LXa]49|ϋp$X? l5O[CbFؠ/~Ђd4Lʨ4U +5$L\5"WkvƱ9'#i16FHd[wKn1+I0 +w<,tA%5U=HLӖ4[&B#|5m&hڬ n~ b&_]f_֜*ڏ埬\[ݚ_QKGYU~ +v +$b:F 5$ +y<5ҊY?Zk_S\-;YC ԖLP<&ؖ$ &MΖ߾_IoL:fsdZ +LҲX*d-'U3a* ٨FXCR8v`NZjA98c\f5W8lw]m3,{էdPi _ m +ӲԘǙ.Go=S5JA22 J2V@P@6EDƣ?Hy:;b D1]N(ZLNKe7Ù Eb>6]kP8D=ެ}!tu& N^{eu$z-Px1*x&i'Y`2rbs]i5)vqE_55 TLꉽJD*3Q7' +]]~x?i@Od1G[}7ӴQk\):IɦC2jd߃K= \- 1oX fUE}1`W[a\J#\ ݸΘGbɛ?W/TH3 bӛZ_{{+_Oh_TIeP^\aON]u:v9,ͿnrPC`7)+3%`>̗-@!-,}iϠb&@:RG_ 7]dcX]e]^HG@/I(o%`oVk6'9J}pRfAtJf?),}©Ф7͈,{ zu#WzVOs `j_ +ɼ%1\׾܆ Efk=CG +^=q0 Gȅ +aAG2Ed+[_b͚=Wq# +#7LE5"3M5YnvVڂeUKï5otg7tPNXl\=yÙDfg^U!Nú67Fs1¸ E>~LV7ᔯےqikiύ,V+ɾ[IH_Ic8*k8nxtT!䭬Qޞp0k2,\>dLLJCiݍGjv>+؎\a0I^ ʿk1*|[ u.2׷jㅗDЉZEOV<*DQZ; rY0j~(Qx kҍ@D/3{wZh4{# xKt 5l zO+|m׋W-Xm "^ED): +. a9t8BDװ-㐮8Dw$,"J6 kc6ތ? ?RWQFrbؠlڃiݏx2yMY?Py)AdѥqZ5R +,1>JqhoY!E,Y B2aM54`H_s1uiБO4Vr:o#L6sMS_TDܘfF_9▉l:dnϘb`L6D9rMWg:Ww1sLk9j}HO[ChO ,@qrcԡ4xsgE?[)\O宗8GQf-nTߊX?wիR~YϹ\PFJ;;l|%aK x—mJ ɟ& v?&%.#] ة\%Pm)/gΈwDQN!&(.iMNN} 1\ Ӫvƈ.e&g/HF(*Jz# o{k\eD2kuIcpnd +t\kh~xƃe>n?6CS C|Yc@QDeSvmsv5`lr"NyqDi Q@0u{7]p= 0x~*ٟJ}R{ wڙ2 \E Ӵ?ҙn:\^Lu70 685/mȻVW^b~gKliipet@,\#AؙqTTl ?D"WԘEXms4 JP*B6@vh `is,a۳x*3͈tL 5B® +m\, ߑt= cr! +U~rKnڜ +~HK? c. lk, g]SbضJ6(ܹxn,,rաt7:((l`ݽ蚇1hio)H꯲q轜Sx|iMIۄ"g 㓩|hVvu~/?EeK8Os @k8֖4<Y=t~N1zX_^ty6hb;5v""@).7#X Qa|!J< h;#\YmdS;"ҋd왨%>s:.:zh: +XEuJ$suq(L?y|`;DԉW#I̸Ea$-{$,tG4L8.ѰgC!>&vFIq.]]w QN?vZi3y'~,YheXs'r9vmzilrT2|T~?x6כr!RA7hErgE;,>ߋ[@64#A!b1乜At"̾$Lߏk3) +t.yAėxl<?CWJ +{6!F|p;,Dm|,]H:'ChZ '[%.soIXԐ@.O#ďtQ;3snOIj}y +I77k!ھ c:0|,[1}o&YM*.Vߢ=wI$Amhށ<+e.WXiБ :%؈Po2=)/gl=st쏵dZgI.\>8)Ok42Ty-XOgK5vehDP$.hFyEάn]H>*d){)|΢njcu82>~t QENTSzG-#K83MBE!7u5H$ ǒjMr*Y_+k8 +tݶ!0ph *O_G]Zjo+;3灓bUIcRq}O)a--Q.`ځSZ7O:-p YYz:)+4h +)y(BAZ{_ lxQzO*{<>L^s>@/p| +y~rnr+NIxq"9FoR7a$_i|β[:6 +@6 IEqJ*'9AyЮyh.L!y? +'Am1m? =$ |K*0rP̳w,B"(y1zXXueġt6F՟3 0!M9ҹzwFmsS󢗒l[Tv)VQ2 oL?2X8^X,S&>L^H,:_}" Q튃fC wlz 8o( ?˨ '89h|A\GϿ`O^5 +1`q)H 3 +G,J Fun*&2N3 #܁4@a. K0iȝ#2c,AAdSrS`ʖp+uѹ:DnQlrXIfoRۀ@icѯ[C +>p5j$+arV3zk{%,K; "? qޖHZϓ\@1mzQr8cԸ ,)6= >7$9Ul<['Rߠ2l!r.E2ŵF;f V3b*z]jX8,Oՙ /R/O(1.?ZcX%vG b;hjP|O$X}R{"u eF4~ j;Eȗs;w+YSFgdQ̻yDz,~섶g424}\vaG:.;iSo:x:ae k(5GC6%V9P`^ hGI6 B( +V'n=Ǩj晈nk+g92[֢#\m1xVj 7{y \I %zmNj(80xGIid]xb!`ꊎ~anWX,\2Zv7$q#*1˱/~B4m >Ya|'j'C[x;,E)/j90k8>K= ƀbLt H6R +ƈoE( H(иlX͡%RrQS$~6ib^g;#]ACuT +0̠1@t|"" "o3_Mj6w0aJn,?r`\TN5a`V!ݺPZJ;bA>Xِ#>~QFH7d$qa&TƸ$) +h-kD m H];WORM9av IV,rsg~dz.geű'Шu4rb}70:-%O4vcq{.N #9AWtGuWfv+:'Nh?K;o2Y;0qgrGHqLZc>* +ċLM`S~ToB2]/oԷj 3ccj; =TԞ /ȯXfG[ݓxyr|TOơWh{(smܐ7v#.x3n_YZ3D& &YL$kEQX*KDzNmJ#]D]uX.~57Ɠ5EL3Y0f(h)V\ijuB$!mMF,{TH[|b.7@E08]@Whr_;e˦?wyD%j[ +z4:uQ=I[<{` 9N :X?{=wk]4vV. kPY॒֘lGWKKɍdlI- 2z{@ϋmf{7ͨR\v4'( +VcE藵5icf +T!w`e*K>xuG΁'Ӡ"߻n vyJy4UAC8cRm!am_D~DMA]b]=val8 %vm3Z[2MP k*):rƴP >p}|=\QCku۳QnH;@*p){W/_tWgLv>"b4*MW5p1P⢌ܖIͦ(70&:/@x1/֠w&ԕNoDq^PpMu91 kKBA dGvA@TdKFRN*z{jQH@_m6}Zf']P5U]2N6Qۈ-77KZ+S[A]7HbyZ0$CҬDy z˵xͣyϔ5kͽr?oD#5>?r!::{t )oӭ4> tw [H+[OD־7WM%)ΰ,@_IwÈŤ+F2$2KL :f-)V.|c>H6\фq6E|޺5k!qxR`ϛ*ٶs`\҃*,}38:ϛ+VstI +D}+1 00 bLK/Knj3.kn]\*.Rh)e; !XH^ +}Od"2ȳl$ASaOnf.ۇ?̗mZ \lϣc)z9DBްGh )aP^a*{SED/~1h5/%8uB&Hj’S-d8H8%4QX{M"qi"_w~NgkСy(37A΍lqE;֏O/ Oy$0 +af)l]qb| ?!pH=d3)h(b(UL{`6=vۆiA_ɐy%hz?oIlXaf]xwmUB(H&mqv>M RHֆ[.[,،;:{[#OI[V'~ɮGKrs#/^1C_$QEH>2"ɜm H9%\ϫ$&)'V +"5[gg,8-h +ݍssT!$JaylRkjQm9w,zjg)*^oMjTʒEw L%`k R7r;IbxL(A&4E=.^J}4s9a`~ѓ=6.|scfLmL ^yĹ06fK9I1MW/+I8Xi-*eMHx _ '[j+9yK\GpRI~{8RJFDO-ֶRF8 ?;,kg(ʅ +eYsj_[«!=U1O7P3]t1c-R3; +)zç,1k >j7vp!d3o=ֳ YY}saZ#"8 qBeY}MR@=X𹎥TK|8uӕ5 W +*~h1s 7$Vl('nӋq8l@7c{9w-RISeeIY{c BݷP>w,mzxR4[:a[ pVBߋom!ϟïVGfԎ>f5V*d@ 7j5P G=Vmvr (k8)FZ>vvmj+9/XMh"ֺvFdw)Q2@M [ zF ? Iaл1Ft/."tW~.]hys9ͧr5dGxs.`lTFz,tdWbZaD=I?p{צD/v_[UW1 UTN#0EAƏdJHXXy!M% +:f3H@qS,\<囪;_Y/8A6+ +Ȑ1JvZJkeNcyƀnlG[aOcܬw>7y_Llis^kF=Т.p3 `B3( YDMf%wԏ;10sE'ZF+*93iwHyAS ٚ`XUDS>*%?D':^E촷P>co*Hd?5x.~9' Jʫo[_* J*"~ELM :Έ/oEHѨbx|!qXn +5?(i ~lhMCI= s +ex=<7B97 +ta"^ {w}ۚeBeL y-/,n&cIۼ7Y;7& Vpx.5 *k>Aͻ0ۦF23A qǑ/7#HyE)u1/O~]Q< B,m<?%{=ԂV$%=F./c{:@t3J.hWv*3ӧ?$: WZHuh.Dp jJ +r\Zv/_׳'(Yl!U{8:7IxN#/,u ;# +\W,xY]}"zSԃLSL+HvI.i(V3/bJ:=o P>ŝjf ]溺Y7,.%%ib"\O|tde7 BnA{ɠ8{㾤HqW{ErBH\Rniڸ$wXHB=g@sv(y'Qz h UGn\%Cn+gL1KɱnaAzגij~V2Q|Add井j + +7^CvXjT<cGO:˜^\,O]maʑӺW%oZ/~k7`.4~Q{+K'qqF8LjB!c}͊$/7 LY!A\) +Y^;7y>xzPW7ka} >Q48c3{/n*z>՗Vt ű`HƨV /Յ樬(%DT KCK +d_n +KI'fooFa4Aqm,:QJKHKs{Q<1xGcSDƚ!sT۟褼۫Ml%涑)"!?0%~{N{޽\peb^qNՉ EkpgA%'uzZx_#r^ZU_M|Gꃻ9! gW0@1ѶhubI|Y'6YO'h̕@W[Kf~Oxީ 1 7.S68=)n^3g˭nFW EBDb ;oD|ȾzB=)|pAhR"Lm7Uʋ!K`VaSH=YRmnLGJ7n6k!FTػ "ĶgKGc1j+d'! I9Ηm5}o<0b`Uvx{E +K;͓W8x`2CWg-4*9bwhҰf^>Pdkܢ)կӎ+7xR<o >y\ Ʉ>jC)}VLj@3wx"U[4 +ΟC8GWR w/e7]dWYV?cʨ-F]Qg +`X(=aTv Q)+)uΙh^{%y ++bB(4h3UCˡdK6)v dNbH#4+ n}u?E ]YzfT>p(UuIZ(٭N!d%#:H;jIk[[;u> /CIDToGIDMap /Identity /DW 500 /FontDescriptor 724 0 R /Subtype /CIDFontType2 /Type /Font /W [ 0 [ 777.832 0 0 250 ] 16 [ 333.008 ] 36 38 722.168 47 [ 666.992 943.848 0 0 0 777.832 722.168 556.152 0 722.168 ] 70 [ 443.848 556.152 443.848 333.008 ] 76 [ 277.832 0 556.152 277.832 833.008 556.152 ] 85 [ 443.848 389.16 333.008 556.152 ] ] >> +endobj +542 0 obj +<< /Filter /FlateDecode /Length 321 >> +stream +x]n0E +/EdJ !%H,Pi>Te_1CS @}<.3V{ƻWoˢkJxWC(K3Ov#sf +~曓oek|s--ݹo.YQp K^eZ &̻k%>gڹڝ$gL>ԐI+^Hd9eq`GCUs+ +endstream +endobj +543 0 obj +<< /BaseFont /BAAAAA+TimesNewRomanPSMT /CIDSystemInfo << /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> /CIDToGIDMap /Identity /DW 500 /FontDescriptor 725 0 R /Subtype /CIDFontType2 /Type /Font /W [ 0 [ 777.832 ] 38 [ 666.992 ] ] >> +endobj +544 0 obj +<< /Filter /FlateDecode /Length 245 >> +stream +x]j >w&%Ct$+4sMH}e{oF# L-A#8ZbE W֓ޭsĩ_89Nxf# F8=dwf[ )`u jBYvi Rq> +stream +x}JԂ(28hiRpi"VSAOHStspu+.c(#ARx@?F[V[G@`*dK$O.K o@6`O,f'˘O asx0A6vf 8{c7%opZ:u Q 0Q٣F *Ԑ(SDGACAajgrx]s PxM cvhO> +stream +xOJpG񟞠 ? G%y j".uPR^q [vZ$I$IWo8/|p~FgI'cV5,|S4"zhPy"=nW湎^.c^24cQ2`كA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dzLW U3T3ngfv#=k,/l]=44֣=W/Ѻ_[o>eF;'Ր$I$ICk +endstream +endobj +547 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 166 /Subtype /Image /Type /XObject /Width 190 /Length 1045 >> +stream +xhqN? S1d,Vd2?f(RD'6A i]mIh[6[}tmtsnyݷ{{ @4M4M4M4M4M4b'dž?;vrz &Fnubtǜt5{f:&pr m޳q)7=t>c tNO|WGD`gzgz^W nZ e]NJ+(rT75U;L1Pi0}`yo@mߡ F]"F=vseG"C1 R~?x0ie)o%sKUo~YDs"Z<}D^{ՇWd^|5>}K.rώ,sxD<ˈq io,]_cOwxD' >闌U8C_$g`8-/;,o*)1wvq-Bx͡OҗNdۥn@o#6֗S.rr_SIx,HO#oBNH^_E!@/IϓQ5tx~^⟑a>ǚY'$C~^er<ӯ׏'!S2y DaUCHJC/붅C#ǵ[qᔌ ^|£{LxX"'S ? Q(>=Ljyʉ:m͌"|$Sf.Sy頵=ϯ)u\WFLo!Hm'wdL(nP2'L0_+K avOza?MY!KU +LE]Xfs%fVCk^qsozsqٮ`s&G[荇9rKȰlf>iiiiiiI} +endstream +endobj +548 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 132 /Subtype /Image /Type /XObject /Width 262 /Length 1283 >> +stream +xOu1/M7ԕ.X +O[zxYŒ);t33 \[ ?Ԣ2Ah[; ֠o1\x A޵8_ v|>o$I$I$I$I$)hE%3,+Wltl)-Xz%üGƘ;ͧۻzj?|m2@' duuu_te]T]X' Z_©o,ngF㽖RiOODcJk_YW>{нGM[[u^޷vt&=˃`ԎG'':0wՊpSj/_Rͳ1:O`,:>`mF< zl`މ_gipf9ߢ j`M 2,.t/NfɓNjf G#w`CQ"1ɤVtmZopKZ lu2-њFف. F #w3_ a:XF(CLѡų*aN?:x};:2v@񘔬ߛT0*tX:9#Cg+-\tTJOPNJc&*O[!^G蜴p/|+my1FSŏSR{g:%lJ)^gg)Y1tJjxVNIv:%5;!t_n,ng蜴󭐏I+o xUA5I)rC'w?Qƻ{ + +sRBy>:/w"BmV` eW|:h)G`,;ĮL#0Rb?qE~X2,R=cӃJ\5XDMj |57M8ԝ ת9^7  5B6k@QN)Kcqt |ҿBwRx |߸|256I/ oҿ^M!>`z| 3ڝ8iԪ!Z{awGw9`XڽζܞX#W??Y:D&Nm,4G%fYH-Dxu)nJE[^VM۪w/Mx*v=W̗u+^^oK5c%ln> +m<[$I$I$I$I8- +endstream +endobj +549 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 102 /Subtype /Image /Type /XObject /Width 40 /Length 219 >> +stream +x/ P3X5ALqB"$&~ŵյe G=7ϛqspu@رcǮv;,Bc|Ζ.,/.;fvnrT,Q28VvҒs.nA9 +F2XnE;#GM`q3q|i1<=79~}!E +endstream +endobj +550 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 260 /Subtype /Image /Type /XObject /Width 484 /Length 743 >> +stream +xiTaI*P0"ELN!]-A;hAI˽y8ŻPvr9 +^\>=%n.F_RW^Rw/=%~<{&k<ivJx3앬~nJָi5iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧqP+Yr앬q>앬z,w> +stream +x+agO-BY$"I^Rrْڜe{"y)*9\]j6jkYώi)iOg9ADDDDD?SH*&Ee糴ڳC:tСC:tСC:tСC:tСC:tСC:tСC:tСCY:kmm<ˣGnKr.[лr[gFv˿ۤjU٭&'õCgmU*ѽmCtFC\]Yަqa.Nͦ5x/y2=uhXjHX?n5OM1[h_~g;kKcPCĀA O|sYfل/$mhӲ{o$iO&bH<*lZ3oFUXꡍз3M +Z p5vh׶L 4>2 l +endstream +endobj +552 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 158 /Length 461 >> +stream +x+agO-BY$"I^Rrْڜe{"y)*9\]j6jkYώi)iOg9ADDDDD?SH*&Ee糴ڳC:tСC:tСC:tСC:tСC:tСC:tСC:tСCY:kmm<ˣGnKr.[лr[gFv˿ۤjU٭&'õCgmU*ѽmCtFC\]Yަqa.Nͦ5x/y2=uhXjHX?n5OM1[h_~g;kKcPCĀA O|sYfل/$mhӲ{o$iO&bH<*lZ3oFUXꡍз3M +Z p5vh׶L 4>2 l +endstream +endobj +553 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 156 /Length 417 >> +stream +xսKa҆,!?!A +n ][#ZĨZ +'Gk4*!Z hJj($}{S""""KH%L)%Dl 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذjeSj)=]զji[m`Vh[3q/v>c䝿pV YMk; ʬ li'2[a[+.[3MsAd_a2€Xۮ"OѹaMQ@LܑYE '4#*,{wWfտzc/sβ)IY3V2eoy'2';^9ļU GKOn +endstream +endobj +554 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 156 /Length 417 >> +stream +xսKa҆,!?!A +n ][#ZĨZ +'Gk4*!Z hJj($}{S""""KH%L)%Dl 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذjeSj)=]զji[m`Vh[3q/v>c䝿pV YMk; ʬ li'2[a[+.[3MsAd_a2€Xۮ"OѹaMQ@LܑYE '4#*,{wWfտzc/sβ)IY3V2eoy'2';^9ļU GKOn +endstream +endobj +555 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 156 /Length 417 >> +stream +xսKa҆,!?!A +n ][#ZĨZ +'Gk4*!Z hJj($}{S""""KH%L)%Dl 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذjeSj)=]զji[m`Vh[3q/v>c䝿pV YMk; ʬ li'2[a[+.[3MsAd_a2€Xۮ"OѹaMQ@LܑYE '4#*,{wWfտzc/sβ)IY3V2eoy'2';^9ļU GKOn +endstream +endobj +556 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 394 /Length 69 >> +stream +x1 Om_ +endstream +endobj +557 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 40 /Length 241 >> +stream +xϯ +pAXX 3ԥ5mZ ϷXi"X b_Y "7]Pll9rȑ#GO]sGVYD:Y\>L0/xvs!7\+D +b-% Zk˜Z2[Kt+wfٔ;pU0qWJ*; w7T;늈`8qeĊAdjU=GQԗzk +endstream +endobj +558 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 136 /Subtype /Image /Type /XObject /Width 40 /Length 224 >> +stream +x- `3X5ALq?BۢMW aȘc~mϝ/!B>7\0pppppppppps+hY ,=9lᒨ"uӭi:G(Y,aV%Kvݰr#dѡtGnr,3TѴp33rT;0_5͙:|<;:nw-Fz]=zy׫i +endstream +endobj +559 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 544 /Subtype /Image /Type /XObject /Width 313 /Length 3632 >> +stream +x{u5Shr6Nȡ!QdBbjmK1rjJ*![]f٥h:1c3q]ns}Sz5ow=s9s}zvjbhR'׈[mͅfǫăkb}˻\+د}rja?plK?6j۞5Y;y…*.ϊڎ1iaJ>ƞ׫[ ٬ZS I*]u5]/.1G֜%T.;2lSʩKL9''2l-d\ظȰm_:–Gޡҡ,6#inSWSE]t8UUgX0.r'$;I^ר)rQZ!F>P`z!>Z{#,*y#uMnt|ӷBu  UWշzzvܧΕbyuI?vd+E,n.pιoEۤ||J'}7kuE ZuA삙F!?} uE7™ ]5,uE12)~Avm;ZY`S{i9w0K,XM0Z`Qs4| \MNY \&g,;#b 0,>MZXn0ncus};! ajj}uIMuEECuEuI`[IaP`Qp:¢^W`2n(9j#,E_`Q Os 9 + +~yRɤuT7Z,j &iYl{zT#~yn0i@`QT[suI9uIɏ3;s@SLz P7Z,YzjuI1>J@N1uE4a uI=N@-uE ฺ-uI-сJL#9ӫd?\n( &?LL &ZuI?%FduI/{쇪zp9s},n0iBB!G`R حn0L &fL &MABouIˠ4A`0!(n0idj:9眻8NM=w & s &G8ϪL ϨLz&L & .\ @SS}[sz.>m0MamuIe఺DR7ؔ E &aB+uIo@WuI/c[l{us9w4r6>W7|0? & _L &s%R7tʫL &-{ &M%(ă0O`R+Xn0"Q74 9"uAoM`$n0.S7t +*Lz:LOLϭA-uIKLW'`~&eU`Susι~U& &#/IaRtW74S7}+LI0i1 T74O<ٗ#LR74)d$#L YHQ7j5sQ[&U[O fQ7(0iT7ߞ&)0iO~!D`RI`k!:¤u0Z`Rv3WC9sRʏ>[$!#,JL#Lk8g/ؠn0BUGP7T'#Loa$L| Xn0)P_a +9-+B]`ҥC-Q0Q8(xAaWOaXn0^tUG4&I'qaj6"uI:¤Y0S`RA✻ժ.0)ibH3@0JaRoi0i l=S&y-&%킷&5Ʌa0@4H/)0|5Hus.*PT/LzUaQ?q?f|ZR]aQ3%uI#,JX y]%} ULW+,#Ljemz6W8\ +1  #ȩ#L+ʼn +fo+,|p¢`:¤GSQ`SZW%s[J_gx +!̆ltKhI} 4ˀ>;L23, 躽YaRM F#O lj=7ޭ0m@= >,I~?p:àҋ"/Pwpd:àK# XuE*.[a>IGE>Po_\H@Բ{JN #q:=Y$nFn-A "nVGtGk2~O1X뫉-`[aO_Fn$u9_LplsL=M%z|wdWwm-TWxF( r9: M3![C~R,Nfݙ/wq깂;%T~UVwXTouW3v`i#SD!}N[Jxpl>ԡ:ǖ]VbΏX$αޠcXr?9nGjV7dw2FӛpÐGo_ ,gC8v|jo/f]Nl\8i@G_˜gh*˨ s|E'kLyw>?wU~>Ɍs/v9}u?2ay}o9{t:Jnb[rՅ^rؚ/jU?=^.ּu9s9sYp +endstream +endobj +560 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 918 /Subtype /Image /Type /XObject /Width 878 /Length 2622 >> +stream +x1jA?Z 0'zAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ6 $TAEmQTAEmPQTAEmPQT5ڠ6 :jڠaIBEmPQTAEmPQTAEmPQTAEmZSܩ *jڠ+Sn=nj7 +endstream +endobj +561 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 148 /Subtype /Image /Type /XObject /Width 272 /Length 428 >> +stream +xOJpG񟞠 ? G%y j".uPR^q [vZ$I$IWo8/|p~FgI'cV5,|S4"zhPy"=nW湎^.c^24cQ2`كA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dكA {=dzLW U3T3ngfv#=k,/l]=44֣=W/Ѻ_[o>eF;'Ր$I$ICk +endstream +endobj +562 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 166 /Subtype /Image /Type /XObject /Width 190 /Length 1045 >> +stream +xhqN? S1d,Vd2?f(RD'6A i]mIh[6[}tmtsnyݷ{{ @4M4M4M4M4M4b'dž?;vrz &Fnubtǜt5{f:&pr m޳q)7=t>c tNO|WGD`gzgz^W nZ e]NJ+(rT75U;L1Pi0}`yo@mߡ F]"F=vseG"C1 R~?x0ie)o%sKUo~YDs"Z<}D^{ՇWd^|5>}K.rώ,sxD<ˈq io,]_cOwxD' >闌U8C_$g`8-/;,o*)1wvq-Bx͡OҗNdۥn@o#6֗S.rr_SIx,HO#oBNH^_E!@/IϓQ5tx~^⟑a>ǚY'$C~^er<ӯ׏'!S2y DaUCHJC/붅C#ǵ[qᔌ ^|£{LxX"'S ? Q(>=Ljyʉ:m͌"|$Sf.Sy頵=ϯ)u\WFLo!Hm'wdL(nP2'L0_+K avOza?MY!KU +LE]Xfs%fVCk^qsozsqٮ`s&G[荇9rKȰlf>iiiiiiI} +endstream +endobj +563 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 132 /Subtype /Image /Type /XObject /Width 262 /Length 1283 >> +stream +xOu1/M7ԕ.X +O[zxYŒ);t33 \[ ?Ԣ2Ah[; ֠o1\x A޵8_ v|>o$I$I$I$I$)hE%3,+Wltl)-Xz%üGƘ;ͧۻzj?|m2@' duuu_te]T]X' Z_©o,ngF㽖RiOODcJk_YW>{нGM[[u^޷vt&=˃`ԎG'':0wՊpSj/_Rͳ1:O`,:>`mF< zl`މ_gipf9ߢ j`M 2,.t/NfɓNjf G#w`CQ"1ɤVtmZopKZ lu2-њFف. F #w3_ a:XF(CLѡų*aN?:x};:2v@񘔬ߛT0*tX:9#Cg+-\tTJOPNJc&*O[!^G蜴p/|+my1FSŏSR{g:%lJ)^gg)Y1tJjxVNIv:%5;!t_n,ng蜴󭐏I+o xUA5I)rC'w?Qƻ{ + +sRBy>:/w"BmV` eW|:h)G`,;ĮL#0Rb?qE~X2,R=cӃJ\5XDMj |57M8ԝ ת9^7  5B6k@QN)Kcqt |ҿBwRx |߸|256I/ oҿ^M!>`z| 3ڝ8iԪ!Z{awGw9`XڽζܞX#W??Y:D&Nm,4G%fYH-Dxu)nJE[^VM۪w/Mx*v=W̗u+^^oK5c%ln> +m<[$I$I$I$I8- +endstream +endobj +564 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 102 /Subtype /Image /Type /XObject /Width 40 /Length 219 >> +stream +x/ P3X5ALqB"$&~ŵյe G=7ϛqspu@رcǮv;,Bc|Ζ.,/.;fvnrT,Q28VvҒs.nA9 +F2XnE;#GM`q3q|i1<=79~}!E +endstream +endobj +565 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 260 /Subtype /Image /Type /XObject /Width 484 /Length 743 >> +stream +xiTaI*P0"ELN!]-A;hAI˽y8ŻPvr9 +^\>=%n.F_RW^Rw/=%~<{&k<ivJx3앬~nJָi5iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧq}iܧqP+Yr앬q>앬z,w> +stream +xսKa҆,!?!A +n ][#ZĨZ +'Gk4*!Z hJj($}{S""""KH%L)%Dl 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذjeSj)=]զji[m`Vh[3q/v>c䝿pV YMk; ʬ li'2[a[+.[3MsAd_a2€Xۮ"OѹaMQ@LܑYE '4#*,{wWfտzc/sβ)IY3V2eoy'2';^9ļU GKOn +endstream +endobj +567 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 156 /Length 417 >> +stream +xսKa҆,!?!A +n ][#ZĨZ +'Gk4*!Z hJj($}{S""""KH%L)%Dl 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذjeSj)=]զji[m`Vh[3q/v>c䝿pV YMk; ʬ li'2[a[+.[3MsAd_a2€Xۮ"OѹaMQ@LܑYE '4#*,{wWfտzc/sβ)IY3V2eoy'2';^9ļU GKOn +endstream +endobj +568 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 156 /Length 417 >> +stream +xսKa҆,!?!A +n ][#ZĨZ +'Gk4*!Z hJj($}{S""""KH%L)%Dl 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذjeSj)=]զji[m`Vh[3q/v>c䝿pV YMk; ʬ li'2[a[+.[3MsAd_a2€Xۮ"OѹaMQ@LܑYE '4#*,{wWfտzc/sβ)IY3V2eoy'2';^9ļU GKOn +endstream +endobj +569 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 394 /Length 69 >> +stream +x1 Om_ +endstream +endobj +570 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 40 /Length 241 >> +stream +xϯ +pAXX 3ԥ5mZ ϷXi"X b_Y "7]Pll9rȑ#GO]sGVYD:Y\>L0/xvs!7\+D +b-% Zk˜Z2[Kt+wfٔ;pU0qWJ*; w7T;늈`8qeĊAdjU=GQԗzk +endstream +endobj +571 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 136 /Subtype /Image /Type /XObject /Width 40 /Length 224 >> +stream +x- `3X5ALq?BۢMW aȘc~mϝ/!B>7\0pppppppppps+hY ,=9lᒨ"uӭi:G(Y,aV%Kvݰr#dѡtGnr,3TѴp33rT;0_5͙:|<;:nw-Fz]=zy׫i +endstream +endobj +572 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 544 /Subtype /Image /Type /XObject /Width 313 /Length 3632 >> +stream +x{u5Shr6Nȡ!QdBbjmK1rjJ*![]f٥h:1c3q]ns}Sz5ow=s9s}zvjbhR'׈[mͅfǫăkb}˻\+د}rja?plK?6j۞5Y;y…*.ϊڎ1iaJ>ƞ׫[ ٬ZS I*]u5]/.1G֜%T.;2lSʩKL9''2l-d\ظȰm_:–Gޡҡ,6#inSWSE]t8UUgX0.r'$;I^ר)rQZ!F>P`z!>Z{#,*y#uMnt|ӷBu  UWշzzvܧΕbyuI?vd+E,n.pιoEۤ||J'}7kuE ZuA삙F!?} uE7™ ]5,uE12)~Avm;ZY`S{i9w0K,XM0Z`Qs4| \MNY \&g,;#b 0,>MZXn0ncus};! ajj}uIMuEECuEuI`[IaP`Qp:¢^W`2n(9j#,E_`Q Os 9 + +~yRɤuT7Z,j &iYl{zT#~yn0i@`QT[suI9uIɏ3;s@SLz P7Z,YzjuI1>J@N1uE4a uI=N@-uE ฺ-uI-сJL#9ӫd?\n( &?LL &ZuI?%FduI/{쇪zp9s},n0iBB!G`R حn0L &fL &MABouIˠ4A`0!(n0idj:9眻8NM=w & s &G8ϪL ϨLz&L & .\ @SS}[sz.>m0MamuIe఺DR7ؔ E &aB+uIo@WuI/c[l{us9w4r6>W7|0? & _L &s%R7tʫL &-{ &M%(ă0O`R+Xn0"Q74 9"uAoM`$n0.S7t +*Lz:LOLϭA-uIKLW'`~&eU`Susι~U& &#/IaRtW74S7}+LI0i1 T74O<ٗ#LR74)d$#L YHQ7j5sQ[&U[O fQ7(0iT7ߞ&)0iO~!D`RI`k!:¤u0Z`Rv3WC9sRʏ>[$!#,JL#Lk8g/ؠn0BUGP7T'#Loa$L| Xn0)P_a +9-+B]`ҥC-Q0Q8(xAaWOaXn0^tUG4&I'qaj6"uI:¤Y0S`RA✻ժ.0)ibH3@0JaRoi0i l=S&y-&%킷&5Ʌa0@4H/)0|5Hus.*PT/LzUaQ?q?f|ZR]aQ3%uI#,JX y]%} ULW+,#Ljemz6W8\ +1  #ȩ#L+ʼn +fo+,|p¢`:¤GSQ`SZW%s[J_gx +!̆ltKhI} 4ˀ>;L23, 躽YaRM F#O lj=7ޭ0m@= >,I~?p:àҋ"/Pwpd:àK# XuE*.[a>IGE>Po_\H@Բ{JN #q:=Y$nFn-A "nVGtGk2~O1X뫉-`[aO_Fn$u9_LplsL=M%z|wdWwm-TWxF( r9: M3![C~R,Nfݙ/wq깂;%T~UVwXTouW3v`i#SD!}N[Jxpl>ԡ:ǖ]VbΏX$αޠcXr?9nGjV7dw2FӛpÐGo_ ,gC8v|jo/f]Nl\8i@G_˜gh*˨ s|E'kLyw>?wU~>Ɍs/v9}u?2ay}o9{t:Jnb[rՅ^rؚ/jU?=^.ּu9s9sYp +endstream +endobj +573 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 705 /Subtype /Image /Type /XObject /Width 374 /Length 4124 >> +stream +x{u03 0lPT"CZl9ĐFlLBhCY4j%fCMbHh㰮jvs}/]g""""""""""""" mn_gn\lZz.+=6@! #;" ظ1d`Z_v&ϸqa۴ٻ Ng +p>PI?DD7 zp n=.o=kU-N2dޗ;ۭ2<ҍ{umv!j+ni(v!޾э ASA )S^=87bJ ƳCw/{pΟw9;Ğ7n]]bJ% h]bO`OhgKl6]ACx`Opͫ.l?bN1`8;ž.O-cfV7` Î02|n|3X5Pap`5z6r%2v9 TfGtkީ칔Ɏ0g<[aNV c7X{[j뷪on-ļLj˰3,Xv9џ/#)kgu.0uiקG#Tز`Rރd) +^Y_`N@&;œ ; vM-.0h1>ѵlߚ̎ Zj&7*dɿ}xQ3= aNn=;Am D6#I<;`PByvYe& ޥsДaN5,j;fe'ؓ{}y>m8ü\ ?_rof7SaO vEz Fv.e'SK aR\vVkdpLaV smn ;@ħ]û]z;œdL{ ڲDeK[ 3PaϢ{D`u.g'ѻ#&g7أuPE@z]`jLݿ.X}+|x/i )Ž(Vd7Q +Hc7 ng>`7SO(KTc7[Qv=C{*mN*}B{z ,eDDKdn0`O*`OկN0S`PBJ= zQ{ʲ DD$;oF{V{iNJ2`O` %MU# ""Ze:J`8x""%yվY{ N ^˷ TN (ƻ-.ӺU"""?G. :f7س`Oe ""Jw N08¼{hng )YߟvA9@v=qXgؽK(.P*=h& x=Fj} Mt˻Zc`*0`3Nȏ9U{".bA] ""JjpV} ,{ro/vH,= ^a7S/=vH la7s+`OADDp066v=mw ħ`7n}{K <L7uӢ?@-Twh"9OGiϱ ?odS #0ԍ?">4w"q"by-XsnxNS7[yf1ύwέT/7an44eչ@{%@Cw.yB?gt E-&!; +^> +>p47 j o[6cSq_1׀{ccCa_G`½ܰH >9AkU߹8V~cyMC]]sΩ}pW(EYwG*_8'Cn7-+wI &ȟӼ;{uarǞg@gqWegbc_?S,p>B]V՝=/F]_p!lYCwۊtXGA#Ӯ]v>^ߕ(o׷{̨0㿽1ԟu,]_.K.X0؃.gGXTI~] +endstream +endobj +574 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 180 /Subtype /Image /Type /XObject /Width 192 /Length 1208 >> +stream +xOHQ]Q61IX,Yv˞!'P! o^.B`^$^l =gq_Zxe YiVҜ7e8EIֳ|2-%fI01)ÿ$@Ӽ"W7-YHE{ b BlCL)y|K5/KdL!;SeYlt1ʰI_SFfUNfm|)ҿ/Yұ(ҳ+:m +))b,4{k߽#7rGH׆`ٓIi[!fܷ<mjuDH4CN-뉞TI-0򮚙gP[d4Ή@vܘh?0^r{FH̐Xh6PqoqkB3DsZZfꝾiyDy9/9azez| /\m`vf!q|0XG\#cZ[鼭3Cz[ކP4ocQC›=Bf(qo%g4AmXO`j-T(B_ne׷Vd9?LG=+P4,W7C#g}}/!R;m ʐΡ;;_yFb`f|Z3APi[bh&:1.hV˿KVU+ݟb׊o1 ߍ'OAKo|Eӟ߆(`?- +endstream +endobj +575 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 158 /Length 461 >> +stream +x+agO-BY$"I^Rrْڜe{"y)*9\]j6jkYώi)iOg9ADDDDD?SH*&Ee糴ڳC:tСC:tСC:tСC:tСC:tСC:tСC:tСCY:kmm<ˣGnKr.[лr[gFv˿ۤjU٭&'õCgmU*ѽmCtFC\]Yަqa.Nͦ5x/y2=uhXjHX?n5OM1[h_~g;kKcPCĀA O|sYfل/$mhӲ{o$iO&bH<*lZ3oFUXꡍз3M +Z p5vh׶L 4>2 l +endstream +endobj +576 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 918 /Subtype /Image /Type /XObject /Width 706 /Length 2513 >> +stream +xֱmAA2d(4P+_hc`ӷiS0m +M)[ +fiS0m +N)2+6Ӧ`LiS0m +M)6Ӧ`LiS0e~*Ӧ`LiS0eVm +M):Ӧ`LiS0m +M)6Ӧ`n*U +M):Ӧ`ʬLiS0u +M)6Ӧ`LiS0m +MU0LiS0u +MY)6Ӧ`LiS0m +M)6Ӧ`L)`V)6Ӧ`L)"hS0m +M)6Ӧ`LiS0m +M)6SvWR0m +M)6SfEЦ`LS0m +M)6Ӧ`LiS0m +֯Y`LS0m +̊M)6S`LiS0m +M)6Ӧ`L٭_J)6S`LAiS0m +N)6Ӧ`LiS0m +M)[ +fiS0m +N)2+6Ӧ`LiS0m +M)6Ӧ`LiS0e~*Ӧ`LiS0eVm +M):Ӧ`LiS0m +M)6Ӧ`n*U +M):Ӧ`ʬLiS0u +M)6Ӧ`LiS0m +MU0LiS0u +MY)6Ӧ`LiS0m +M)6Ӧ`L)`V)6Ӧ`L)"hS0m +M)6Ӧ`LiS0m +M)6SvWR0m +M)6SfEЦ`LS0m +M)6Ӧ`LiS0m +֯Y`LS0m +̊M)6S`LiS0m +M)6Ӧ`L٭_J)6S`LAiS0m +N)6Ӧ`LiS0m +M)[ +fiS0m +N)2+6Ӧ`LiS0m +M)6Ӧ`LiS0e~*Ӧ`LiS0eVm +M):Ӧ`LiS0m +M)6Ӧ`n*U +M):Ӧ`ʬLiS0u +M)6Ӧ`LiS0m +MU0LiS0u +MY)6Ӧ`LiS0m +M)6Ӧ`L)`V)6Ӧ`L)"hS0m +M)6Ӧ`LiS0m +M)6SvWR0m +M)6SfEЦ`LS0m +M)6Ӧ`LiS0m +֯Y`LS0m +̊M)6S`LiS0m +M)6Ӧ`L٭_J)6S`LAiS0m +N)6Ӧ`LiS0m +M)[ +fiS0m +N)2+6Ӧ`LiS0m +M)6Ӧ`LiS0e~*Ӧ`LiS0eVm +M):Ӧ`LiS0m +M)6Ӧ`n*U +M):Ӧ`ʬLiS0u +M)6Ӧ`LiS0m +MU0LiS0u +MY)6Ӧ`LiS0m +M)6Ӧ`L)`V)6Ӧ`L)"hS0m +M)6Ӧ`LiS0m +M)6SvWR0m +M)6SfEЦ`LS0m +M)6Ӧ`LiS0m +֯Y`LS0m +̊M)6S`LiS0m +M)6Ӧ`L٭_J)6S`LAiS0m +N)6Ӧ`LiS0m +M)[ +fiS0m +N)2+6Ӧ`LiS0m +M)6Ӧ`LiS0e~*Ӧ`LiS0eVm +M):Ӧ`LiS0m +M)6Ӧ`n*U +M):Ӧ`ʬLiS0u +M)6Ӧ`LiS0m +MU0LiS0u +MY)6Ӧ`LiS0m +M)6Ӧ`L)`V)6Ӧ`L)"hS0m +M)6Ӧ`LiS0m +M)6SvWR0m +M)6SfEЦ`LS0m +M)6Ӧ`LiS0m +֯Y`LS0m +̊M)6S`LiS0m +M)6Ӧ`L٭_J)6S`LAiS0m +N)6Ӧ`LiS0m +M)[ +fiS0m +N)2+6Ӧ`LiS0m +M)6Ӧ`LiS0e~*Ӧ`LiS0eVm +M):Ӧ`LiS0m +M)6Ӧ`n*U +M):Ӧ`ʬLiS0u +M)6Ӧ`LiS0m +MU0LiS0u +MY)6Ӧ`Li='/p}X0U +endstream +endobj +577 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 158 /Length 461 >> +stream +x+agO-BY$"I^Rrْڜe{"y)*9\]j6jkYώi)iOg9ADDDDD?SH*&Ee糴ڳC:tСC:tСC:tСC:tСC:tСC:tСC:tСCY:kmm<ˣGnKr.[лr[gFv˿ۤjU٭&'õCgmU*ѽmCtFC\]Yަqa.Nͦ5x/y2=uhXjHX?n5OM1[h_~g;kKcPCĀA O|sYfل/$mhӲ{o$iO&bH<*lZ3oFUXꡍз3M +Z p5vh׶L 4>2 l +endstream +endobj +578 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 158 /Length 461 >> +stream +x+agO-BY$"I^Rrْڜe{"y)*9\]j6jkYώi)iOg9ADDDDD?SH*&Ee糴ڳC:tСC:tСC:tСC:tСC:tСC:tСC:tСCY:kmm<ˣGnKr.[лr[gFv˿ۤjU٭&'õCgmU*ѽmCtFC\]Yަqa.Nͦ5x/y2=uhXjHX?n5OM1[h_~g;kKcPCĀA O|sYfل/$mhӲ{o$iO&bH<*lZ3oFUXꡍз3M +Z p5vh׶L 4>2 l +endstream +endobj +579 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 158 /Length 461 >> +stream +x+agO-BY$"I^Rrْڜe{"y)*9\]j6jkYώi)iOg9ADDDDD?SH*&Ee糴ڳC:tСC:tСC:tСC:tСC:tСC:tСC:tСCY:kmm<ˣGnKr.[лr[gFv˿ۤjU٭&'õCgmU*ѽmCtFC\]Yަqa.Nͦ5x/y2=uhXjHX?n5OM1[h_~g;kKcPCĀA O|sYfل/$mhӲ{o$iO&bH<*lZ3oFUXꡍз3M +Z p5vh׶L 4>2 l +endstream +endobj +580 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 40 /Length 228 >> +stream +xϯ +`3X5ALqmѦ+bL ŵյe GO>9Dn-;Ya~"g-\5Y$^n9M5%6\vbѮr{Vn$w,U0:rMTeҝ +wV2nvFΜjG79BǙgG\B^< +endstream +endobj +581 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 162 /Subtype /Image /Type /XObject /Width 40 /Length 228 >> +stream +xϯ +`3X5ALqmѦ+bL ŵյe GO>9Dn-;Ya~"g-\5Y$^n9M5%6\vbѮr{Vn$w,U0:rMTeҝ +wV2nvFΜjG79BǙgG\B^< +endstream +endobj +582 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 122 /Subtype /Image /Type /XObject /Width 206 /Length 168 >> +stream +xܱ Ab*#$%\-|`ETp- v6?7OjΫWNjtRӥKM.5]jtRӥKM.5]jtRӥKM.5]jtRӥKM.5]jtRӥKM.5]9^9uLj!3uR +endstream +endobj +583 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 98 /Subtype /Image /Type /XObject /Width 98 /Length 511 >> +stream +xؿKQ+fb?@h(hfǖ%pt*pZBjpl!p Z7 + + 31ꄕ$yΣR\^t:N6CT)68|б#kvY^jB +~KK|9@3@3Rţ\.$W 8^e0eѭQ&K&d…p"D8A!/x0˜ YLȲQ  +q} +ATrћ=@WHB+4YW +eT(Rt*ĸBt^-E@ +0R96 6iL F##O)eɁY" 'TI +<5/fR2}u(Ն`W@#\hz ?ks|~iawK5?wZф;|Y?=kf7jot7휮t:wz5^ +endstream +endobj +584 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 98 /Subtype /Image /Type /XObject /Width 98 /Length 511 >> +stream +xؿKQ+fb?@h(hfǖ%pt*pZBjpl!p Z7 + + 31ꄕ$yΣR\^t:N6CT)68|б#kvY^jB +~KK|9@3@3Rţ\.$W 8^e0eѭQ&K&d…p"D8A!/x0˜ YLȲQ  +q} +ATrћ=@WHB+4YW +eT(Rt*ĸBt^-E@ +0R96 6iL F##O)eɁY" 'TI +<5/fR2}u(Ն`W@#\hz ?ks|~iawK5?wZф;|Y?=kf7jot7휮t:wz5^ +endstream +endobj +585 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 102 /Subtype /Image /Type /XObject /Width 108 /Length 848 >> +stream +xYHAGR*@z +z2z.+M0*P2. 2)J,(+Ȭ>f̺moW`0 +r 7rЦ9M48VTD n7~MuX%5MÒ]5|~UEL8՚!Hƃ\حuqYysB+=3UuAc,@%8tH ?fVJ{WշXSu(rqQr|vW;tHwvW0³*#TTUWk ^FfVY@, popLdA4(TiPiH/jȓ'U.qN{PMV忘?dr*"+,w. 'UCaL}xxoUKt]D9x +q(+Ru]!>^[Pi.BH [wv +3UƭkpuxmJ[agA6u=lõ|k,ᚿ눇W鸾v=Bu]zvpmJa+jhD<,mWu. R.- xHbe{vմzb:L'܎n*C}(no(\]\~OH>b=3/4ͯJ;-Y5"hj-Gp^{ۥm^cw[I̠?Y]MscWgV;OnXO߶ jJ3fTDt `0   az +endstream +endobj +586 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 240 /Length 51 >> +stream +x1 Om wp +endstream +endobj +587 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 222 /Subtype /Image /Type /XObject /Width 1556 /Length 867 >> +stream +xձJa@"""pr Y4;/ ;2]E܊<, a|s~tw#_?\ts3٭lp /ngۗcO5;Zٙló'] 6yٕlÇ'Ϯ`> +stream +x1 Om wp +endstream +endobj +589 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 126 /Subtype /Image /Type /XObject /Width 393 /Length 440 >> +stream +x=.P@g*SHLAC!1+Pb +d` [$bJ$#$I$I$I$IzHSv2Xhïu7a=;f;٩ۈ}Xcl]vg#+p]ـqdave #ّ-eG`G@8p# G@8p# G@8p# G@8p# G@8p# G@8p# G@8p# G@8p# G@88ˎln`~vd #ֲ#P:ٙ{(ٙ;XK'4\en/t.Sk6>~(>ef> Gfw $I$I$I?S +endstream +endobj +590 0 obj +<< /BaseFont /AUWCNV+TimesNewRomanPSMT /CIDSystemInfo 726 0 R /CIDToGIDMap /Identity /DW 777 /FontDescriptor 727 0 R /Subtype /CIDFontType2 /Type /Font /W [ 3 [ 250 ] 5 [ 408 ] 10 [ 180 333 333 ] 15 [ 250 333 ] 17 [ 250 ] 20 [ 500 ] 27 28 500 34 [ 443 ] 36 [ 722 666 666 ] 39 [ 722 ] 41 [ 556 ] 44 [ 333 389 ] 48 [ 889 722 ] 51 [ 556 ] 53 [ 666 556 610 ] 68 [ 443 500 ] 70 [ 443 500 ] 72 [ 443 333 500 500 277 ] 78 [ 500 277 777 ] 81 84 500 85 [ 333 389 277 500 500 722 500 500 ] ] >> +endobj +591 0 obj +<< /Filter /FlateDecode /Length 440 >> +stream +x]͊0Fy +-?ҕ& ,C>c+,UtjH Hf݇aV8v?-v^yVOݥE +_4ժ֮su?'U|C8_Cm*FN_ۋWEN<Unuj;pUFvi>%Ne]65w @Khm2U%*3d2 +bO͞{jLMdA > }Ґ ,D󬰅֙4]tmZsJ)i 5ՅBuCז 9Kΐ 9̽A`q0܊V 'h9A/5,w$Zl[pH0r F#a$9#`0F[>>4}cj[i`IyOR e+ +endstream +endobj +592 0 obj +<< /BaseFont /ESHXQI+TimesNewRomanPS-BoldMT /CIDSystemInfo 726 0 R /CIDToGIDMap /Identity /DW 777 /FontDescriptor 728 0 R /Subtype /CIDFontType2 /Type /Font /W [ 3 [ 250 ] 5 [ 555 ] 11 12 333 17 [ 250 ] 20 21 500 29 [ 333 ] 36 [ 722 ] 38 [ 722 ] 48 [ 943 722 ] 51 [ 610 777 722 ] 55 [ 666 ] 68 [ 500 556 443 556 443 333 ] 75 [ 556 277 ] 79 [ 277 833 556 500 556 ] 85 [ 443 389 333 556 500 ] 92 [ 500 ] ] >> +endobj +593 0 obj +<< /Filter /FlateDecode /Length 387 >> +stream +x][k@>/{K@$C/40 +u<Rgf,Ǹf9Gpu 3]:3]3)|i5|J)Sç3 'c_suG/=d {Ι +endstream +endobj +594 0 obj +<< /Filter /FlateDecode /Length1 1937 /Length2 10830 /Length3 0 /Length 11845 >> +stream +xڥP[) nݥH&Z)ݡS"-NO|g̙̬d%K=T`VPhcvrL]@3Qii5m\VlI<{-`8 C@lа6urJ@K~wk_]:Y ..)=_}eӿ< ْ vavXH@߾C\QA@ leAe+X-u߿u[@!̕M߂캚*=[C=ެ\VN=?#V,QnzW{E32QKebҮT r.6iӷ6ήr7u_q4s`Xك B`ij kA,NiU6U3Mks;#0$HA̟pp<o9񜛝 uyv8> }q͟ӿo`w;@-88 I.N?7x~#?77Fo$Q78 ߈-z"'O?GXtq.wAr.Tw? otUSX,yRl@T1F(-ȁMJmLV >1ox7F91qDYՔ3 zsK6 + wϠ{Y d]mi~8OwָpT"I*@:zzƑϒVXc} ?PEf('q +QxWmXRQ~S{*O٢ge!qLRV-z8Cnofi) 9X :z(F[4”G:%i6ώ{zQ ͋7_4"CZ^+}.#4 ]cK?͐\W9Wk/#E + Ȉ[]IJ5j\w(S.ϰ5nO!)up֦M,ƬiNdD_K6_z_LvIeqХX +5RRwNSY"'>*U-Gn{}nk5ȩU >O?r>bevլ7T|,So&“_} 않\ۋ?qD0&-g{!o -d⎣smO*P\|Hp1Wi1{پ>A"AЖzѽ-*R8"g~06j˯jt)1%t~Ibm +& +?}o9y&TtlRd]@(6e1-̯{SZ%.͆KAGin \+ u[O~JǥaH`+8Be!ճKJjgBƕ^`40P=W^Fph>'1I9Pu6}KJDjNW5}q123!kD9P.g1f+)Ygz2Wb&9sbs@* @ |);PAEDM+||`;sNkZ"g!6RxWS$=}FabI\$He~Էx7+T0\}ZwOxrIk w}TS +Dj5v.{ +~?UB°g0E2s0 +sG-/eNHHo/P3 +T2>L> \#Ɖ e(!hW"wtafg#ئnF? +yi,.W_3%K`vm~cќgΦ}.!r6~ Xg7DݼXTQpäU6I`<ÒN}K^&<[l* 2wpxMÝ2#diƅ?>{1BrX7σ=54w g#1jޚ5ywtrs]1QƝ)A %bMw7aD͗uG.[)Cj~\RWu ؾhqpX +'0w7g˥"&F+~zmng¬>uLѠ~E/yWae9KPPEUw9aDhv/|+3RQ$| RU23xI8|̄gnM!{KP3$[CqvJ`mP3N'FB{0z HI}5(x=l1jPd庒@c4q΂y~T M##kF dj_q\şTEZ:{#-@sVDЉva1m+懱HedCrڜ*Ndϋ/x4VQORyBq']2:ek¼ fL~;m.Z򒢘/n|XXWpwbҾR$+e[$4㬂+:pc4WߧQ +wK3;*^G`h۶yL!ͯsn1/+^5}`s 1(4]mt= DtQ:lKE +qk03C;@uA@"*:T j*[|wtcDlWƠA +]ϝ޿׉|0xξfZDC źQM̕ez̭-'5E29ߙU3H7XԶo0pUA|3R0(O5|CYElUgBٶߠNw4 +dN=-|:'\ٌߩN׏NyK|R5VWp K&tpoX:?T2O +'MEAX +jEsdzMh\i3C=dR]ߵ0^wx:uىfOX~ءB%2zvjI4Z&yoGf{v"W'eؚVIe&у( +"abb_)zs SxR'W9}p1mE 'M=<ޑ1Pr9?xb8(Bp' 4vN\c5SEw6yas1'z5@~*@1= vGvn'Q':b($X\MK/X&ZǜNRj v49 +1D@ +5l0DR`wQbTiB36{6uoA战Cn508)z2ҾvΐNꍖl )f$2|Z_6U!&CxD4Js󬁫=؜,7KHBMCJ-.X!b8MH Uޝ=hSo,[1o|g⪺"BBap}SF`_$Q†~UY&n>_o[ל2)rYudh4Fy@Uޱ"Qś]m2nmFąԉܩ1OМ6s)FFjJD.˄[ HjHD!3}fyQG>iOS[L=׸ip +(9QiiƪEA%ۿmVƵRi, m Oº Cw@\q ++Rjq eRONT)U:uu Kg/.U՜sdVF*ɹ(Р_zȬz]R%9E#S8SQ2ŀ};d!}8)6I:_I%7`b\ ڞĹo"[t>ED=,7G7 Ж΂T,!q=v46(` ,ĴTFy +4?|7Y+%FC+f8HYAFD,x[}zތHƀPsTm4ԨAn b7_fEhgzs4FEfǛ>ExN`vtڏ jtCY_yi.:#- Oސ+2~ +FZOg4NEpgg\ؘLWޘRӫ;[Bc Ԭ2ղcj`5Mt<\Ļ'gƷ2;l~yl*> f(OsN^rt9:h9l=5lK–\q[_g3z´bk Ym+iYHmn]d'r6Z9SIrffǯ|@qSh1P#3VV87>a?dYw?*W6ilTլ^Zѥ0GIHA}]<59hUdx%Xhm|(NZẢo,nyJ8^F`4oj Dz*l&5넅ųPU_"?4rmZc'f? K^-$QH|H6EH<.}@T1]2Glj?&$(~#`p"[Rvuߐh‘/TwoV֌QY]IhwJW:UQZn>I6T^tHtJ}6e&ׁ\B9mYN/m2 @mSwԁ$*0/UfCHy'ṵZ[7.Ay⍟m`pUu+ UZNH{eÉNы1x W:j:=#;#A$–^f+3R؟"?^fJ GA᮷TLUL~5#{YrZN1dTiVJwh:#^uA[#jK֐{ED<>\tjyD/IvCs e7Yu魵@9ه'Ce-OhP٧]ScU8r9{ B:״wm0XwTE>tw Rx5VݧuGo5Selߠ4bjCS"|PDE/~fiwSlɂuгWWYt[yEk%H-) n\IA vwSX.O u2>Dlx!,IXLφ[Hҷiv۴7> CJq)G4`GTO&Er9^hfjh0 +BR:GPm>tbS W2U~RP,`dn +\Rp|T'Cb8 _q1J"نm%Cf^/n!ʣl/_h+Y/bYWٟS|,%YY6՘@λ9[pZ(<'?E]u\\[ Qy'߃ \NcE)@ԥ.xNP_}B_Lba_B/%3Xblsa%wkRr2񳴫 z@[r L&'m. +fŅLVYsFƗA:E|B'_ũ =?(oIopy8L7W$h7'+*9ǵߝѐ5ă 5r'NR;g(;:; ůY:2}:"{Ρwu%<`o Eqx7Y}WѨ$T>L36ǐXQn4߳"u?؆_Ly~{c2ɯ@DA΋ ()%| '}ӭ#d-˛#jIzG%ou_i tmif}3w iw%/I2U>^&~@N" - pbxMF2]NNB컔l4[;\.Aʽ8>p 8 5MkB5[E8 &2)nFG )?@&Oܹ\dR8"؉!ԲE9&WOn[4d~rѶ]>9LBzR_sT  +VɶA0Jltx:=hr9sM'w^"d`-uqw gTh +/1$D!N(:QV_nƿWMKweo1x$GیS];|[hڜ7ԧt͙&f9E<<U꓿Xwλ0X >n>y܉4w +R<1ҋsƎ_Ζx0G1K¯, b(#nSJ$N…ڑjF! _J`u+mq+#QpZ@YVnTmn +gtՂ#i=L[lA9b|Ys-õ I?si g !vO0}ڗAU4;T@rE9ʉB-I9#шݘ6/o/|}q춚tv%Ŷ~nI}=H_S$40WPヺ+^ɺ&ܦG~AA0s7(GFV%vuE{9Qa*^u$Vʒ}c&(fKH CiB*0hog;N(;ɣ6;R&N1QY!pPS?`2\VwPJzΣ>Jfr/ʖR>qr&+/g?q=^I!- \'~mD:zS'>.ɵw:G\yXrq{j:*MR+ 5oj֪v`a\b se(f 8HOFT6ğQ*޵QM p&;vb[7 }OC'}10]7]b)W0hp_Yίa] -/u=Si=ZI$>C_nmd(V;pKMR-KI^!W5`S.| +w\INzj[]elRrx+ n +B .ۥ}kih9+Vl~fY7xZu`تg #M=񀆃˿uZ^*faf?,% + @}\\b8/|-)8n%գ /.m)29vH>ɾ_shJh:Tb&羑9Сަޗ-H<20E/Y.?h4 +` /D5= IުFHFӌpMDUAϚG>2O~E?"3ֶ꫿kbo;ыܕ[{b(ɭU =0.S,kI#4 +%--ܦ/![}/kdKꆂ +nߋ9rhL#OG&+iXn,GߕG^`E:CF~k:~/|B)ma{y5'|iMq;:X<,'.s%{oTB0p>N|&6^G.~Eĕз۠Ɉa.WGP5V)>=ͪR N-ܫRE@Kb74tPfN$;BBt^ Smg5]gY'(ch Qdqp>&(mi1eЈ5ů\x 4{ + ήDr|}ѨA}J8i J0֬ N'lU׹sVBLњ3wGKK %(^A-c0iao+}1VT5IJr;`YC|g_P~:Y/c&=N.%l jx/R$9aYr|檿䤨H۠+-*. ! x (+AA5S2wOBއWb{ I:ƙƾ׺Ÿ+(kꥑ*uVI<(&ֺ.FKƴ<_9g1V^nV +?l!#hRbc׋. 7[˝fBXF +נ2J|[3DԲ{;*o`LKIO/" ӝT~ƜB{^R);vpB޷w6N݇h}W5 +_ wxi#A'v˲ +VTi6zrdWY;lP-dY0ZeKF}6R,aX ˻ +b4#,*s"yeǴn5>O-}-GNB@Zr{.pX4,Fj`\4 +>/Z|Kjzr0!`<W NG Ūꖀ`Zm̟H~rLoPhd=/{d] + +Vbo.?IB?l8П?4Y(FL.!R?جI +endstream +endobj +595 0 obj +<< /Filter /FlateDecode /Length1 1420 /Length2 5652 /Length3 0 /Length 6456 >> +stream +xڥTTk;;%EbffABDABPAI )i޻]w}k}{w?{?~qTr@QH H"ahA$@ rq . 2yx"PHbU<` +APz!0(`1$8!0]8 Fȥr@8+p s .7!Hz(#OM}I_"{  9"p6<`^8"tj0U (7DOG!$ -|CRC:\]aH'!p@@{I(+즇07x |V_ϟ_7PH߿!0g2'(zcHJI=!$_d-# +/^nx>'GЛ^7/oFEô?fLX7?v_N C\.b3{}c ?eWSB: A:`@CG'wp)kD: Ǟ uF<=o4ėx3"?q-,Do(7/t& +$& \/7HD Rn4KBH/\BDIHۙ^7[qӐ7}|`P©/(L؃ڰJLW$z/;g]g}tr&rgiٿWL%Y*F k:PqN?}ixRTk^/9#}hEq9ob5 P: #U1Ȉb^å!}^sd)UĚ?#O,ds׭47;4J5^{HwbDmRF$rM/n-%M>[t@8+!-;pu~FtR?>p}7*gzq*&/{d^ %evi8[F:.&yۮ-OaNQ`NqGH~^.8|մCӴE*RM:ۋ":u/ ѵUѣaj *pQݵȜwiSs'EyWEq0jnQ}[1;fJv$H?޺7>flw1F4e +6ԇ&N.2Jcp~)~hFuZPw&vAw&68wg4͘9S;/=TA<%DKN>0zf$Tbсhqڼ= :إHD]: }ǰ In UîT*!H C>ngo'Z1̛)#Zh n36S}QFrQO}?0SՊ` 7D꬏DbM;+)Iʑ6i* h6$ Fݚj"$PU1{#~=4KW/pgFǠgyIdRwVnj >Wfm2Ի:L/ +|ݞ ߙ&H(n{}LubTtpWSÇ̬6)}$=Bל#Ufm l&) +'w[ΘONt~Hz.@}{yRxH .EV.a%h?zS3s_;ٙKHOQiBϧ[}{ԥ K)ThO6HR0vGqېy_uh] 2({l,--oa]w}*=D*iK@$[Tۢ =`w~z)k<.r3]{*іDr#L%L'׏˟.ݾ˦o|?C32?N@ ʪ/ o=҅my +Q*v Z,79Nz2>pzuh3Ff,y8-\WK"^Qg[SƼ޷yXfeM%d*RՎ]TͤUdGA*OkHzF02(bJ~-a]f`Z_!QX=.h<ڃe>EL%0jN;vnZl?$(!Xnv+g4d9 Nc_.vd@ƽBB$jϺJSwE,V8jzy$ӧk5 VO=#p *b`9bt,%mIW)so!œi ~%h[ˇL4odqj j\K}Q;vPF|i|tfYMkL+)+dMjl*܋ap U# [4yftFwVjJf{jtOO0݂ȉlNc $%_I +Puf+82b`Hx;KPer;PQX$ON91wB}78ȉ'd ;ČAA9B=kA9XO^L,͸`~(pMxѧ2R\ݻcK"!8җ-SرC.-?XYЙyCNpJaVy8K*d¯g[3EdyC[JSuݗjI2ToFq4>I?:t^VbOKz=8ci".YDNTQDTmtHն!g!XzG7)I\_\`/becRE)dH3y":7rd.G"~!:YqmV_Mo<,ůX2zJyȋL.|,._r>3&e·'A)4$Fn+ݗHޭa$5`reVS`qwV3J tyeTt.B @BPtQdv} +9ZC]()(ZOn{/k+o;wğ=xV}3pxOlp{|I}s&%7^/a9ВL7CO;@㙩TЖGguyU\n3Mv }zqœykRt-P5V}}Uc4:Wٹp2~%$|fH\N{+#Ll3xյ78/>|pChkiw.b> B4Nk>hΎCZk?4MC }v*nҨ,[,]ŪPBUWRG 3Za= 0_*##L zܠW\ڰ{UYId^ @& '7YWxll};ukBlmi=,z-,(}̉3Rsv.E+-;چfu| .< <{.M nczJs#A&v쉾m"{Q@n8f ";U^Ya >j{&=K韆wqjpaHqmD~4e*[[PzG _Pޘzz 0;A@a{9-DbJw|wW_Gp> +stream +xlpf5vިcNxc۶mc;۶mo}sU5jM1gεjDQ^(ngLuQ0303qL,F@3 [8rZrjb4O࿨&.Mw::YM pZ{(ola Pb@@ PSU bktmvvnfqG bgfGht⁓PPImE#k P,Q'@wckhkP? 8 4_Aq;G3?+ubMJކnd4K`://z4sGJ|A$U\*('`? ."ސ_|r֬,}Ob[£~Py* 2DDJJ4gYSd^ZeW(8!&j#eP Wz4@"2OUM˔^^{NGM}7i~PHav~\¶^WSGQГlyxvYş(/3iUҺ5҈u BQe5U/NeڲtۃRGce4[giRVh)W'Q+p CK^as?9-_K8k?!%WzE r*.Z5>!R?NGrti~}Do B=,-xX)xU0 U~tJsʪ +A]ן$x} o6>P_ L>FH.Xߟ뤨a̽CY)>eI6=GŪ9 F4Ch\ÈtHeބR?dJR1|Y,3J5&]B{u)"e_Iz +%f =4kB&e~kF"|qu +sKg=b#/܍L]hzL~HT%XŵoŅV&T6J:;KnMJ$>-S}gdsw֤u{2=}tYзsBL]Nta~@[^|w9sI*6hMzt$̧w`8} F$LpxK\,&<2UB +GެbI)Ob1;oҚiud4[UԼSs0cF>[%O U9bLQe#3"rw/w_4~)283]]|C9=`#iJfl y7[[V:UK8 kIu|IJ: c N3KK[DsYɲ1 2Enm{iTO8R~6#{XLLp7Uj +o1Z0nOajF|289f6jDrH<]cVfKwҲJ5'5<>{Gn/H}fO5PN |Nv6uX-_Hě,Q!\-\%DBSVih3 ]VÒ!7 +Bꚟe!tAU\$m_Д.HX_6d"m* t3B2.f-3$6H tYp,h;w}?E4x̀8ovU^ rh 矉`gWۅ/bC$pZHODnlGwx:@q`TK$g|;dCO~9K"RB 0 Yfq Ek $uaL%|Wc]GFi^G$Ĵfn܂~[-@90ZJZV;DY:e|"7!*[}OW `8mMY@y/(e!:u"yνhB&L! C#6ԆJ汏NF!')s#ڎPMu.Hpޏ|10dypډ5gXG\6YF߽YK8_S6κGatʗ'{iT֭Z *c^qa睼#Km*K*H\SdԳ,Z{SΕ-A,fo15IqO".f' Hˏ`[uE=ܤi3%N""4q\ ;w $tHTA9|QwKM% =6e6JSC&iѸ73?V?frL*P%D O =wDwm5І1[݋`\P5h ɤlEd&%9<~dIZi9}tP7 Q¦V]H‹i wڔˆ{~wc} }WS gx2rp6=sC[[u#tRqBs_X\`iŧ39ؖ^ճ٩]eOEӺ4|/Xbh]Ra۪[Kx&Gy_X$?⛐| /kQMFu<Ǐw!c PIb{B7eneoDuw)|"*}f26C1lu̳aG8[a}EX^yr.]:S͟u<7qIF Tkm\A W?oi=׌ KdzH\n'"+N>yp*?K10 z\c" v҇<5/8o{Ph۸z3ӶNR5MKO8L_K:!L5Dŭ;ER:&#Xϰ rBWy^ ̜AqIٺY9,-Z-߽U?wP3ƢeS'udv;xĴ.' +/;ӳ>2Z%*Bޑ ^ef$ +wF7z#DP/KgV`SS':CMIEB/35ځOH=b*M ks[N7ǹdY*Q]/ ]I^}kj_:j;:+741C-rnlFDdmثYl#$lJ?ڒ!Pc6^%r6ZMrˏݔ)|n[$,:F'Xlp՝RV&nA2h9*eꞺ o".cg9W!^A8rױSpG"*Ŕ*V*ɲ[MrpG5Kȼ~ep6QH《JHi u.X /[C.3ANo"?gX%UڷHh#)WKs`0*>:ʤXy \p2)֢5XA23vVUFN[0Di9.KsJÿO]Sg>s+U-_]vJ'k(yR=-Ui!/œA)Ti~bfl֏עEVEY +.EMgjr"Z "ƹsV!+'Wڧ0⏈"PG|_ie[%}p5+R-^u%Ujso)i_B,RXo^@}D/y$k8K_!W2sbHXD bs~,//x|Mmg&3'~EG d`^p3^Rf~mp۪mquz8tB~\2%g1gLKO͕SEH޻$gndp,!-1ץ=?  ^V1ZKעC~x&{1QRѵQ0e6 e\+g3Ii\Ʌ[N VDg ussxM]lɞμJAg6$aY1=RRJp/LT~5\Mk  q{9͈Xʄ;;r/Dei xe/9[# q +kn + Ms\?pUGrӋʹ(1 +j蜉sWڂ7sYttkԈ势Ȧ =]ZD`c ©<%Rsk4]txv'<Ж'/kɒ{N'1飖 L o^Z|we}íh3e7+Y*֏OD'm_'9.@䨢jIx-k"X`Fd|L ?Ʀ #Ml{JH !>#itǚ-BV֐Ɔ;pÛLa @ťͪ /.5_wZ C y'z S\ѹ;+kv!hߧV"dٹ]o0qP ³W=YN r5fH˅J$8<n34d6oK ÔR.i}7{+mnZ0^E /["Q{qEGgS{YXwS6h%ޓ J'F%,cP:Ey +WfF:#'0KOGgD0|H RMA-/uő>%z-Xgt="7jm00J圩kZY~OOۨZ쉞-}Fy/7Jzl*e 5z/^Y7my8-q69xy5k 0'=&eNȊ~{1 6&;{O~YϋH FFgp.teDJuϺ~}6$q3ǴtgAF"BeD_nA4k sqI){jYŧ\gaA8|bl"LA %o)ڄ44<ʸ٘$W2f0NJ9J&Riy:~Ӏm: w'fYH"0 mӞ!?.h!@/4*({ݡ5XP#Vs0 2:&n?l=6hć%4U()uT?Ԓ0\^Bh+ݜX5М +B>6G@6 +UIŖ'n]߸A@nbe\']w7L +K8՚ +$'7{CsZWzf}^̏zi=dV `j3._,9K֗gf2z!x]LV|w!`"sOLy2q)V%Y.u9 Q;tSYeۏx^(u>>r/^E"?rW +Ԁd\HN-]O83+z7ޞ?&gb: ֑wu^ZM&a>GZ[^my +Beu8%T_06 s+#nW>䂺MV-dMark<"wWYg1~ sRޙQtlp"8=Z1Ks;Tc⵴dPfSojgE#!m!q]@9~ R +*w: +x{$ ^P9*;laqr]E=W>xn I*e˾ D|Y*_A8BTS%:9ҷj ?,Z?(v4!rem + En׮ǐghPtsB@c˞KpcH0v\L? ݁uc]Xh ˓ o\)#_iu8U]˫+|񜆐}QSsyۿ?|Gl mcEw-AmkH[̹򡧊?YSs#) 8ʦ! 8Si%M=O ^p75̳PTQ͑2cnmg?ci&03l,t\e^\'h ,yn^;, y*'u~.9^^ ‰ 8ڡ̟B{-g:+K\uCiuQm wKм&ڀA 4;ȓd너M2kZw=Clu ;L<"DhiMڲȐna)oi.Ԍ ֟<+P;zC@`4!'`p k] [JY.*%{W_/}t= * +v6AC;DK`MuENR}EUݍzK aܚ2QA8V߻eM p`խ=h Ѩ7O|0LNDŽ ˛C_RY86~ε pC Ҷ"Kts,>q6#labls>aO2؛P+ԮEYh5]"ҫ6z.2 oܹR%^=8T.OxԖ8K5KC0dz7&_a7ёGq/s©WOG"o|{( ͛xGNC*l,CPμ}KD,P}R\-㑏몓ZyP9#^KKYWdk:fflS" +* jE匜-6(* +&VҒ?t/m@KQ|F B /?SCR}KzTH$Gw: +W +8y }PDDŽ؃'\SL +OJ]y <'|H[jxEZɗ9vL/0C5[}\G\F+V1Ԣ/6zN boO+a_OJR_fLHd31"fĩW6!NJyIS+]=Ґ+Ϯ1Za2+6@$ޗ*EbZ4p9NDTBD s +$hN}lg;XNk +D5AJL[ Fjn Dy5x&Qm}w~U7p0SXl_5;r)]H3"%/7E_-Ywoq Ye!vR}h^Q*|;H6J7lLYֳˋ؃)XO蔛 Rj? +b^ܲF^J1#%-i0evy>{W̰^>|\0p-obpՀ3S8߈{== O*mJ}RO#.N\C\5 +uHO.peZwƐ_._( M ,Ucofl Q}Þ?29ߺkGUD*cT5rڏ|4FT=`u%Y LBj)c_N4a펣!/!>! mWiR5]lo/ʷ82)RZvT9$ \݁+?•ՄZ`2r[q"ky~N^.`GD <5\,k8?zT77ﻡTT '0x|o"N})&gw䧋Y-9fhͤ! TQq#騱]rY]x>gQGI-&b sZD[#"=/"dfR+n0Ry1X^:n[:v w} `ww x{JQeɯ ygYۍŏDw=}asǖ!.[>c%J"sr}Nt:|rc7UgŻ7Jl=jp4@4 oNAyHmOesׁ?6ʟHRsVe+\L,Pa]3I6<$֙3k)M"PZQ?icn3X`!5f0U.)z7lD: m9யA^!.7*m[cif8RgB,3 J瓮i pbATN{2ks]jAݯI%HЩ߈ ՝w[ +`zS50_E9n2\ +[vg?6k'C,Ny#S_5WUWnSP"f;8NfUӅ$u^-m8{L%Z9~L#gS@׳k|Zܭ’Pk^tPp]Yxl5˯[?U-1iBM.NB"LzsGf6uR "x\ yqSOVm(N(NKBwYaGS-(fhFՃ0+EV<DzKHLżqiGKpL 癲FX0H&g3l]{ K4M':FS͍ JpU{gGX*WWodepƜC-klsmQwtܒf纳[VJrȩT\A3]o}`W&b+Vy@&6jSvi  9wĭ-C>hvq*#ZJ}Ѡ$W81};w7O­LuM`ze\Z,wWS80XU"RI(4w]4$FsڵtCLy+Ԙ{nŨ +WAKP8_lX v)]0U 4ΜH¥'j6k:٣Z$.6I[8i#VhrEU!#JcT̒j"pv˿ 9d,tK죋 E $֭[,LAvEHXةuK\}atml@3:st +w:QbVbKޮm&K .wma:h(-:n߅CcprF)R?{QnBPUCOnEA^롧ǁ[Qb}K3g_h6^80AoԍZͣ4J&z g|z1IE\ښN{n5C3 <QhzM_MZU.>䍳#"մ)De` P+St{k4[ʚ[`N[v6[$Y< "w~z~f, od.kwP 5JZ|wI/a.wN.yBG*/1z<pn]yb,gE ?C]Sq\whz gy=B;aa6BWjWUWp{'F׌hۻW7( l>fޞ"$[yRNG>qAZ3/vYyfg'>Fg)۴IH. jŜS2.O7tῨ4vtWh|P|xynb 鵸e3Ȅ,lx)ǭ/MQ}̮& |4KD&N] =u+݈I@7 +PW}YkN列ovy,Vսm>WҢ?PKfO?) +Zz`Qݵf/J@ИEGd!T ԙCL1ճ2cg-AHyZV1T e"+gpԞ +3)L#?YA,sJsoF5vWˢ]#ap E׶cuL<2VBˋ(K{:h|' )l5 qC8 쥋;sԣň,=AoC(1v9a=;uQAu +و9ZR K0[ScYΎ!!U/N) +ou/M=3pz|A$Siyb_A¤"#A$p_!kUr*h;R❶G!4 +S…ŀ6_n &R4BES?9Cx<;IƑ +DLv| h)6<۩0-Kh,āʒ4isqQo*6|Ƒ >fr#eW_$XЉQ@Eo%::D$>Z RMS,~%rIk +ImbKZTs +!,ڊL*B  M׫vȅZn.;S ,*8hSD/'I"3YO+%ͪiu(/df|Z }rI%+RJY0ճۈ8Qۮsܿ7nO{O&5 ^,Lv f{_ A\dž~{-w +!Dզ#֔{8Ef8ClqenȨ=p_*>Xq;CosgX7zP#mXWL@iis'Enɾ<4XɈL < >}d +bɝe[V?W'X +nq<UI!{*#D) ռS##O4D ^ H\Z3:p +{9чȷL7]W úq3hkmkWNT :4\950Mi.}AǠq28)Tkd{Տ<-_XX"J̏hp1v,KuZ +H&82R/E8@ l=gQ:IQԖ8> +nbac FcU2f_:t38S}qtd;Wc noܫm\iSe_אvݏ~X A} bz|iJF)M@*&N: {pe&0{Gz{^{BjyLb|z^ + G8Q/?ǝC-]6g"*6ڙ) +j_V>$yZD҇`,g,ę6g:ڮGO1gqMG.˳/8>UMnh3sȦ 񐯬$k{U [F7l}R)D޲yͬA`OK: 4`)>d|90#=g_j;_8ԉ!_G2XTC>4lB0\EM 39_P 5{}(X~} +)<HEՌ!nnr~JWwRzklFjLw+MxbT͂9d%,Ė Us~!Wq;~8ߝ۳Q"ʼnJc-^7iq%<߱pՉ[9i[PQXysD4xf,(k(ɑ2+mo\DQI8[2ڈ +^zP i6Q-%"->k{ߙȤZBWu-2c4a4p_ɗ`OF +W.Oĕr\Q j͖?Bx`%)XawF#±/$<=z(2hv&a(X/Zg7^ ,?6%PArߘVL٪{4#q*?k(;sjqz"S:M0K }Q{%$(H1~f *W}fys lKDJH }ԫ8`~grw .bV${;49->z X4:;~!Зn*$u3>`)HOo㏰_RUSO%&dm<(7[$oMz6-D*#=_+%"j-M!yQ':$+EB0 *EFJCJyǑ>_%>sf P u ^\oU#HLR,qSyVY՝XN5tRJr_\U7ZsWo.g^V4Ր3'V9(rG@>覵:pi}W*^Jlzdg;Ro@>e!\8+/M#;A[gI}n)vX˷{(AuU'~<2no7vaOOφ}g 6N`纡/`JN[Ju FK.ky bJ,wUH4_sYsܨ o:<ØWց1>(0ɱ&0ۤQ,&nxaYtHg05ۡ +~V[1)#*=sGR(;Ё hZX#k.;؇Q{[z%QkVUlVGzrE揸~.3K2#ԇXuAvu@QʜZ6>2)Wݪm]JTy3,4_D9͞UK:f(T*Ъ$e_d\Zb5e}Srn|4c@RD^kł^lZ&&$Es㑅c!C7:'\$䧔d60{fsѐ)0;?}5mW|bJy#Qsݝ({KYa-|P ]p}hTzDC3ɇ!"j1+"qc@3p20( =$J(j_dEo"h6hKŴKp8GJ85^`1}{;>= k%^n2mR7 vD:"uש?^@%CJb8\~xDVFA^꘾x &k &Hjf`>6+#)pUS~3IižښmZԃɣ J#;NՊtA׵h![;FQ;/pۍ*BHsOWwe?< P +4ATiOTg3uY$c{<5nlWsF曇1>۱bH1")_RҐJ#VyQhC~>/g*3sSáB(xLv~7=_ d5kJ{]ʑ,lȉ/; N¸OeNCߟ>2UAr^+mLŠ'6 >],-8{e0"QO@Jjej?MN_;Hu6 ĩSEVDrePmuj,3OtMK+ iӌ,1/e×u䌽{9efB +!*dd@`3D׶}~3mܣQ&$n8,dU \Y|$4K.1Au: X󏼶؊8I+HH cO5;?WJD( Esd]wsEu _+h!H$LUfuN,c=Ψ-)s{XN]6[wZM"@{MA*.*lz=Ƃ2 + f屐W 9 J͞ѻbaxڀyN5ZX:_SPiw<5LvhVPғ;/A!'DxK104f%ōE +c.,#/ +mq{Wz'#bb/^s&c+/ئ\EyOXf(@j۔Mן"ktS3M?)bFJIv2Oؖwvfʑ= +2EQ򨰣 >`7 S mq(j\/0J&22]w$Ő0,*3+7~J/SfW4SKJ~*G77Dc!]%ʹP Vc.N S˪RKyrl'<.1iY4vs`.J/燨a!ixY!Ąΰ٪Js'W~zIb*A򻬵HXnΗ̇b@!$*CC<p\ 2lQU%]awC Z`XR`="*[yE$ m!t?8,*>pfMND?v1p݂ +ݳi"D$s.:%`b-$ma@k|" @_-+tR:!ʇN J{sYNk %_ F4&ɽe&j$sە=YZ_b;i- +d )Cxe")(aoɐUљ~_$ aRL}Bg {f{j[?|}9m?ř3<@_8QGT)zvK쓕:fzC&dR)Gib`͍3aVu' j3O NmAU`>bCLrOdkI +*ϹBPcLyB\d,(a1;%Ժs˴Zת>ٵC߃X>W@n%]/}yheHeNsC*U gҀ|Ab~QCH`L39jf&oS?|:W púJ?s +_@8T0Lɑ2pM\+?m/<PT$CGFM+'zbеVwV0ۇP t-f :Z@4AW(nԝ!Iݒ~QZ,eurꚌ[x;"e'c+ +P̭.ppa=wiQ}{eGww;G T 0db H!$#Eߒ7wZXJ^le+njz_j_N/ )d TOUmU3r5ChEξtT<)VJKk85s([`:0%f^Ks>]QZ,HCvJWt[ܓ֯3OyW)h&~NNPW\*jƑ9ʸ^/sJ&ɉZ-R@[i^hFέ(Ovfi=02mCP=ap=tXbĩ{/."c;z:rjVMW + ({)5eSB*ȢvAT}MM? ?5G2qAwf jEbk͌eOv-Jq2'[!pڕuP#(hW+XoAshEbq]+e,D0#;_4&@ +,C9ˉEPtL|%#d@USD0N.m/\ ),mhxglhd0epiT)9D*v=@:Q Y{nlš;t yy'DK.h}vcw6BaRTFSԅ"%oΦ| )Xzz2e:6#uq =9k: e34cxxy뻖P+=}=^1cT;K"-I4ĊƆ.|55U`:MGhVZᶼXZYj@,6I&[x T + wٙB8`&GopmCp^@cZ=m[l22$q30G$iw`ugUz>D%úR;bN3EVe'g~P^/nQp}+ʦ5MmO!Zg" |΁EnDǎr9[@jVY}X/(xCֻ s>׭”$s +lmGč If 0&S߄vh^=r0li=5 +xPM|BAc}|З[9~$q `nu~[5Hh}M ֺsw +K#ߌ\0< IoM?߶ idCV %1Y# r_B_dV.zMČC{Xү4q;6I椪IVXXlK<w`zп2y)8^:P/-_P;9@'D5mN'd¡igBB^bgG]QDh|+]%MRؑ_>zwۗ:"eh3B}NwG۬nb!ͶW{ +`%K<1%ɾnP|x6-=B6Jϙe͎VNT4Ͽ=c E%'S=[w$nMJvv_ Vg+%_b+,k92im" _&% 9*t/So=`?fw>3kn[LtlBT({pP=ats4}]~6 aOreqtGs %rN;U!X_WyN)T0Dx V)e`X mr3aGMg݈o}.c}K<@4w*m2H/{7xG:W8d"­F!O1k/3 2bgƮ#~m]h {F)^[ F|l +V1f|M!i _VmגhN='VޙHHr#3Gu7vs@ %|J=Ucbx[ ֬p>~'Hs?y+ŮgO0YH4Zbئ"M$*SZe9.q/31HIߑGylPlzq"1ۨE̐HY3(BPӿ8@F2c{ '?k] ~JUfp)Vw6s6J^:?b'?f6_n⟿G$䟂pZ~Þ7!GL5o^-; +3/R\ԑwg͊+R7: +R: }3 +AOHmEXpRĚ'Ԇ| 36]sah +Jno΁޲/!!pdOc +m?.'+bD.$%]gT +.YVYbf&aг\Ek9|@>ηrśN L{N3^ֲk`?z^i6 P 5{^m]e>ŗUX)iF0 W}S(O<n'sZG,upiSO5Zg9=dB;Kcia_);ǽtIys] +S";ڛ&ΛCZn=WJ}JjggOv}Q +[$VW hsjya5T:ve1nɵ]<|3 +pu:Bœ5npdQz YlfpB\R$(aWGU%򶴾Y;4542X0JH(#\ff_& +/L}Kt?7N!qx5s>kRLՠW'.<SԂ%gZ]}{!ctfmX9M7Sk%R{rGOJ]rq+ayv>a :d[Og @u:\uK39|AFMe3ɐg2#ω-&^`v-CDM0۷<]ܱ*P >c--WS\=CO7 IIf@3yez[7j_0wԉq +/uIOzW 1 NP^K|oWlk^^H=t}.?O{|&4FSwO!G/v#Q%  9_6^4lK`Acodl)y'Ej.0ReL /?uX + jӂ54A9 nP!.Hqn_-;N4_AA(ʟlP +׈3"HU3kʣAJ݁1~t9guL"h-(Waf?J/H N@QV]̝}:p 8Z`Pt*pd$˓JBOh%{AxgW?D@:@z<ES*:afgxyNlYNluNE{T +q}EKFf]T޷tmco>pHeYFj;3NQW4>_ d3&O?lV(TF_&m GtJ~J]!=hE6%0.dSޡO"[yfŸN-Ei[ygEPQkzu]\.z@RKmopAd:>BJgb?xCCohyx#b|LL7r]{EHwA2Ø'uȨddЬ<3~P~ +Hho/ev{<^>[e +J x:ktmo۷%vN9z+Qމ*1t-pU`p.9x s*b +((sX.Z)|#b!84TAcFLdYQ=ԕ K7䖩YmqW4V{uKcS fՂ +OefXTj/£>% Ksku̠shP ;_ka ~'lpI@a r[:oєɧM|/+kANyA*VF#:q&SjPNCi^# e3Eࡎt>#Һ8PUs!D&;%4>>";3Rx"H^tBcU9rrzi:u(!:_B0-̺ׯǬ-z->FmbLoii3g;Xx _ؘ`$~ʼnA|YЗecph5ltgF !c. a0gΊ +^ Ϣ ++6}9$/#x|KLByoip5en—f P0m#&`+-2"HH \}Hp.-'=H3<~,?2)ٕ׹ Alt:9 ZH2Y*O=ILr̙u^%x2AAi!T#8 _Iԍee=~_ExܢO(+si}IQm\8: t*݁K_M4=0s&}H2 +){ұՆ^r3)|o7D ۄ+:IGe{zޖ9/>WŒ(ATTzHR+Kl|T U17[|$ȔzfH uwm94:0`M;̢3X4͋9-8|%#AV(lRڧ?e쩤DN=M󼍆ЮC$^ԁ /iIuSiuldyZێi05] H D;w],ħa_VǺH\; Ew74:Ɍž{•^IփR#);$12ZWNl==}ֶ2r΃r+XFRp}n h]>5~jSq IWqS00䑻}P#uUrNWzGtU4Fd:,GXiJp4?#V<-y l6G._ <ɺ4B6djw7vgUk,O"&7}HخxCnpw.F6Q7iQFh&5XBߙMcY_v3jŇT($_BARclnA8 +z6zH`%V戏3ؒ*  91$S_6~zfCgb+{ Wz *@`m`^uhqeK"ګ&}ރP%1ukͶ't_'-?ETbLI۹%EON3D9~uƅ݈'x[ho|&,l* + +p hvO6]9 +1"1'[xk55GkAFR+nxG|^ҵ>I"<I7FRpӀb^(}ˉ0HěƒH:Xd,ypO0<} t_*6O<-|5UN6=Gop4]dKU3m+|X!<"(R{d1ޙEƍq2T4`=4T`OeBK歵-]E39|c؆o[@F":˷ԉLv0 qM?95k #ty~Iίro4 2#I!֗J#o ds=Fl^)43jT>֠H 駕g閺^뜄k,Cm4CXhuk׬ț/Z6f.a`[e_bQfc F1rvd!-ӡ3A?ԯD%!S7 -2-d%LZ5Xt>7SuRñR=}_3DA9 +KbBj1.@Qidh OXT 'sGSQsQ҄%"0VAzXK"av3*AuT~M̜nޮlN;]ΑOC4B" -⅀KAN!R-5R6Rʰ"jrh2NcT +& K.Da^}k +)k쥣C~B +>`=ޠhƄ;G;%R>FO[k5eG: 39щlTfq.8gQ#%2\ATT1ƫQ7V'TcW tr'Ű)"t0,RHv 7;3DZplFܣSH+F pSd9G`:Qf{'wiwJ6$3 !FK fZ8l䖹G0M{dgH8\L4/r}vȺ4p;iQhV2f>zT yKp⽃]\anC}-sFfx ܥr"/ml}8h] `TqX{ډ?؂knU>͑ Y, 5eљP,c7йnq 4ʹu!{\>o.FUTIrT+Z`knLQӀi]w ӛĊQ}Vf5QtW +<0j;D5.զ4@N~_B5 mSS"1@)%VѾ^i +w9c(F$-K +fЙo$? D?_[>ca?/L5LLzryW 7X B-3+L,Ԫ,\#ar Iұ_ZI ]9 +P#= [AIt#p2ZG0a]Zޭ׊~_8ޫ-x'Y0ܴ\սEC#x]+qva֎9D=>t4m<OY eOن̎7(,ſniH\`֝P5 +[MKyDsҒ:qhmmyd;ؕ=L+G0V"fCC(V7tѩ7j޾"Ӆkt؈T}ي +UJ(tg +)o,Se&M3I+ܽN`kC/e[qWF&gsI<k6ǒXlLcO֝25z!-\$auaH;F( +18y-epK/|W(GjѮq{x!KLw=Thxyxӈ tZ/hz6 Y"B!|f '+QfzܻAPQX5hx pc{,DS-2l-=`H:|[w(GzeʵnJ1Si\lep!m(Zc M)oD\(O[7q``Qs w>*R٭pBZ$LI`t<ΫZ .ILTYl$sJû57܁˶+PUBi=, 4 gQgSA"XBNg0k|(E3aϰku?֝}IN(OqG~"Gڔx&OJr'^^34ÑE +&Ei$<,|w*ؿ>{t??#8暻*v|ydui>$ +3$}Ժ(o^58VR:T^).lɅbZ;Bx|'+Ii}9&{s9+r7l+Yz(g%R6ʄIv!Yೋh"PJjvy|Hw /d%||NE+&⬴[$Btvk"M@&,)٫ZzbZt(< +mDAYJs WP@Jxk#oڇxqcdױϿjmCOn;yyΆ+Kf46(P?Bx{\L efkݜ6-a'zRh8gI^a) +@4oDK IN+D@\uѣ#v./|,{*T~$(`mt,3mr-ڮs{zCcLŕM2I)pAlH%-j~aF:`Ѕ*nCgXB#ճe=|]~8>Q/ip8]dlVZy$Dc bWg!{x@g;6P޿ԿZ]J#MYaz>ms#r1bsl7͒'>¼N{ +MNR+ϵUEOe8~@+0.tp2ȇ["ю}>%$SDqp{V %iU]Q#)&L-fi"̊\戛 ۵jI2*ߑJ:8ʺ|e{h(t@o- HӇۗMRSԊI3){@F +^y5F2kVsЉa5K3pٶ 5zr!x5Ii#Qdݲigv_W~ԫK(g^v+Ö6AAf@y!B?j2]:`;jz?57ttǸFs}zN{g9TXebmF`uiCh}wjC.X, E:'姦Ou?Eȅ.zJ?WVʫ< c/FkτpnJ s9LLp< Po`MWC{FFj,UX|5J ??>O4|b߅n#EEq8W*S@:„Gj q՞Q/փu[P:l}J4j:Dp{JgZl;ʐCgO&8v&?&[HMsqT׊Ne$ I V1|xkDҚ0]qT2My50c6J#[6@ЈYs鯦2dmW 'o![ZEvtd3NQg^"ČV("c[5ؑ9l$fЧ6BGι\ n6z4Tx]nQE"kEBm`xP4<0'Ϛ("ɳr>Ҳ., 9*@{%bBN*_ЅK䯸7\o5Ͽ & +gT&:/7 saBFZo633mAWWCܕY5dk!?KQY Z|0ѻ$Ljq\ç~9pt]G֕p)-ρÄ. +}J>۲̶MњN8x \@p kkHVX&8+M~(30NήnIm2&ixj^N`W{v.&&qY'H1tWbXѻm/Z9Q鱥A/aٞ:/='lXy^MXcIH(P%.ovyܣvrR i0t]-H_lFb?^6MۤdG/5/w?p,Ns[?HuuBPI(ӫQ)kC}#×;Emƚ*gTЦhFM1@"  AĴqG pw<ҭg<-;% -'*f7zŴ- +1$y2,p,٫4CR\2(k8ӢbfQ"\p_g``qʇLѵjhmcqo(HeUVg(ҌuR:`n_0S^;+40&.V*Z_n:>ՙt˟<b9hG)G d<+#W +,Ѩ3ԒȒ`]u8*g֋lyj:whJXTb|KuY~+&# (I Eo8 Cw)[nJ9#ςe"!/ix:$T(1yWE +0K3otqqrU _4\>_Fm|{f9Di:doĢhS@7lIyUR lV`؂i1nϾf XyٸlQ{Q-ṼL~Z``XJ9.١lƉ+akuئ2&|Nmofj\ٯ[߅J<_t~-ZKoqk^}VY`-(i` ?>qLXjazZXITa:tGp`c8 bm=mHsxMWQ1p8ɑ nAxR;'v0,RM(ӏ]: .Pz QY T/Yuq%TY4RcG|QsW6:Ã'FǢΏSU#a ]ӿ2S+U-A%'˞ |ps +endstream +endobj +597 0 obj +<< /Filter /FlateDecode /Length1 844 /Length2 8940 /Length3 0 /Length 9567 >> +stream +x}veT]-Bq .Ah Kݝ[wf]ϩuv~.?VrANf+wc*;}@[^p +鸯'sgݟ8K t}DNf ڄފ'aJ3ވPx5"OToy6טoG[A84]x"0HJΪ%vhۭ@ʏw> 0X:~Pë\5F;(d.hOjOK54q›mOӆ.̨Kև:BnAz3$ ,>E@}?mhX˸*w]4a_ M? ݝkS42(&o֌Qa[.sZ ܅2 BZtl w^"?2[r=)WELd2jl$\t Լlx@;ݰo+PJ"7 xոoJ}1[acu*M?C&X t-AK/BKjOᷨV-Y򊮴v{ԍBB=t1QwOz=W ءzhF"R2$"Ǫ-rvP?ZBG RPG7(喙Pȝ̵DJMn 9S]$J%kMs7P%:F́jh#snDc QX*\BB 3Qi=UĘ=G<؁U9il6kxŵ |nS1v cN'#fu^0fWph:i +na 4=s!Vt +"Mj-)_e..DM·;j&Tv_HVM UȺ2U;V}]B/fzJyS45D[굷N D~q%-55!TͥN/˜މ,ˊQoB{)܆HVA @@!#P}l̨9H"2=bp[P>w⿰g|"yGc~jq|{1hܞʀ^57~@dJ/.F+ U eO&*i\cf'6$Yy@jtTP0:CQ$&r# r%<`BYKHБs.!V5T:?&bcSn2dI &gu^sD%}^sTc`ʘI^^$8ZPpe"-W~aRk^)8'W](-h• 75?n?=y2Gq80bOQ(]fㄕ;%. E{bfPʶ[(f\#зfG[;he4?K֩S0ҳ5Ȝ=vx]kt*닮-unb%oPg(+Lu]+Og4>#s*-8Fh25i,3j*}ímEcݮ6Nǐ=ɨ.`j SH̓rХ2IL9O)B:V *,-`bR4ˇ.ca9kQD"HON[^Z 42At5ڢ׳9vB jw t}`yѴw hjo>U)'8-ϭ{l;37O6w 0oI(Tae!(Qd[%GB(e6ZbaKN'j;6TF; +*V@kI9ϼO<gɩҕd)Xh;dj\楜&ݤtK;h܏xgXLX630+*0Sa7߇,nT֭p+BYܖIY7hIf#z@Ԋ \DIrx7JĤ;oT7zLZ;䍭̊Ȕd1ϒ41ĆﵦH%>mI#t/Msa +2=OKJzyp +L?ۊMԚ>@I .Q +DmifZTI/?~)0!Nd 9m 8Ϳ<Lk8 k0״+tY+5&/$ƶsxF;YL}W#vJC5q2K!i7HB b͊;d XI>}%~dNI9pqNqgiRW8%6!YD7?+S@f&%lRL~U-| +A1+yG5mXĈTє/(= G2\-bFdI{TW1fU1/-1V*|ÑBII-KqXq=pq*V q({\i'1~/%X4DyzfƊU`=r RZr0t=]d#+qnAI`25>&wt 8gr!ڲ]E6y~M1+ooڳ.j a>7.o ($hE}j}8 ?t/L7/|Hf@nvy~D\#q F3[52Kv8}?Snsw`.<D[VK4^o>U8d5{.YvAOO-p2upX5@zGN٩[ҔC^kXb~Ņ30SġI8'5;Ű!}b{_VzNӥ0%?Nb*j?ck`6P:f!~owRte<G5gڐ۶$~23^qFx +ق N*/Ղ92 !?jct=ۙ~S +}%S$Ed|єdAhA7ꓟq nTudffG1z4+NRp?ho T55-]IT[m1zӉ\2t.ߊL9ck@k9q*D^f6{N:2/(Lj zg4_層taiH Qc˾~!D]8 el_PXQZrH|*=q&!Yt!~1uZṈo[3 +^C֎ a E7]+?pIF'V!l*r %LW/! D9] 4rqNHCkF(ʔė뮵;]9|+K2Aߧx+ Xɠ?z2(^ޱh1#VagIRV˧M_gS3Yzyܩz\!Qmf1އ5^NF$]t17 BܵLn +"1 nI[Z{' lOH- +raP5rm"Į-'ut˱P;P_c'C! MT,Gѽ.:e HځpQf쇘|%7 gBbt<z+u<UPǯ`y`0kr95𸛢Ο#Xvy}S5#ޭ"&J1Fb!;9|{;VI(W&u'u?(shPkFq|Vh@Vm5X_N3t2a޵6/f$?Sֺch&R8B.V7T"%_[xq^f|uvn6ӵeRTŽB e XŨ8T"%b|6h.޸W f,^415BU!"u\j+\]R7Q%G Mcmt]%sPvHr0W)61&SR^CHhN$#J^mkTLSF(Вpʛ9;WخO;m;h \7".IĀ /bYes.柍 c9q!em[|W8nV4&;XM.sAaDvJWip .^/^96[1n3[pɳ v[yEf#I*4d3![q(S ,wȷݧ8=dlK--iE!x({㲅`.7锁Ao߀&=xb i9HQTYy),a08I4a7$w)r# +k26'TE,fHb&QR%onapF}/@_kWkucҽ)9nӤlZ0gjc1s|V#y윧$gg\홲_i#IeV0-dI(5<] +endstream +endobj +598 0 obj +<< /BaseFont /AAAAAA+TimesNewRomanPS-BoldMT /CIDSystemInfo << /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> /CIDToGIDMap /Identity /DW 500 /FontDescriptor 729 0 R /Subtype /CIDFontType2 /Type /Font /W [ 0 [ 777.832 0 0 250 ] 14 [ 569.824 ] 37 [ 666.992 722.168 722.168 0 610.84 777.832 777.832 0 0 0 666.992 943.848 722.168 0 610.84 777.832 722.168 556.152 666.992 ] 70 [ 443.848 556.152 443.848 333.008 ] 76 [ 277.832 0 556.152 277.832 833.008 556.152 ] 83 [ 556.152 0 443.848 389.16 333.008 556.152 ] ] >> +endobj +599 0 obj +<< /Filter /FlateDecode /Length 323 >> +stream +x]Rn0+|ly$BJI8|j8b7I0agg֋ScMO{cyz,N6*jUsj9~bE/騧 lx:W톉`XYr =k޺@ٶ` \_ )4̮S;;+(J^u] ]ҫcuZ"DBQ 5 QoHjW69]̉1)KI.)dSɑDRrI.oGl% 덭}C]p&p +endstream +endobj +600 0 obj +<< /BaseFont /BAAAAA+MicrosoftYaHei-Bold /CIDSystemInfo << /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> /CIDToGIDMap /Identity /DW 0 /FontDescriptor 730 0 R /Subtype /CIDFontType2 /Type /Font /W [ 0 [ 697.266 ] 20 [ 285.645 ] ] >> +endobj +601 0 obj +<< /Filter /FlateDecode /Length 230 >> +stream +x]Mj0 >3N2Jf^sVRC#YnBHz<=ޫ"`5f> /CIDToGIDMap /Identity /DW 250 /FontDescriptor 731 0 R /Subtype /CIDFontType2 /Type /Font /W [ 0 [ 777.832 ] 47 [ 610.84 889.16 ] ] >> +endobj +603 0 obj +<< /Filter /FlateDecode /Length 243 >> +stream +x]An Eb"8]ZH%/Tus c ߾Xa>ڒK?7&و_AqtXgVL:0k W8[{pxxRCxZ\ZOk@J}~1iDV !iF2$j +vxSgr#R*ٱ#c >dU^| +endstream +endobj +604 0 obj +<< /BaseFont /DAAAAA+KaTeX_Math-Italic /CIDSystemInfo << /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> /CIDToGIDMap /Identity /DW 250 /FontDescriptor 732 0 R /Subtype /CIDFontType2 /Type /Font /W [ 6 [ 715 ] 11 [ 831 0 0 0 681 970 803 0 642 791 ] 28 [ 581 0 529 429 0 520 466 0 0 0 345 0 0 298 0 0 485 0 0 0 469 ] ] >> +endobj +605 0 obj +<< /Filter /FlateDecode /Length 301 >> +stream +x]Mn0>" 4Bj"Jsj8 n_ᡉԅ-}{~ &P'@<]`, F&s) +J' f~'=u'ktw͞`,0ںvʢPkp]8IdݨIZsKZTUUW߭꼤YZFzFz$dSF'$ʼH4veeF(3%b1h,!$b럯U](! Mɭ˘d +endstream +endobj +606 0 obj +<< /BaseFont /EAAAAA+KaTeX_Main-Bold /CIDSystemInfo << /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> /CIDToGIDMap /Identity /DW 250 /FontDescriptor 733 0 R /Subtype /CIDFontType2 /Type /Font /W [ 14 [ 894 ] ] >> +endobj +607 0 obj +<< /Filter /FlateDecode /Length 235 >> +stream +x]j0 ~ +C]aKapl%5,qC~) }$ީ"`5f=KXEp 8ojwꢷ%h mN~ió H Nk?8#eLJp82޽nf^m'吲?= h,&CV!$}!fw TOZ!!+RwUR}DkJH*> +stream +x}JԂ(28hiRpi"VSAOHStspu+.c(#ARx@?F[V[G@`*dK$O.K o@6`O,f'˘O asx0A6vf 8{c7%opZ:u Q 0Q٣F *Ԑ(SDGACAajgrx]s PxM cvhO> +stream +x{LWeq~a&LC/D%RCr&ޝ.KIBfEx /M/N$K'4DPQs,nys?۳s;;o#(rz|ꦮ%`B^HzR*JGAKw5R-]Mx8 Eàɺ㐖m~t̎TV%=Qt auSϩ^:XU#zhts`*k#3G`ZZFt@TtsBKG4CxE]nftw]4j_{AX<3$X +iiq-zDXF{b )5X]:']vyg("[I^]0W$]et'MX.}r)ExUKfZT.sϙsE~k>>̍fJ]x%WOSH { qgTש: eh\H(y/wք;]l9 x:>[ 򺱱c9ju55PMxͨ1nwЂctLƎSF`ӋIgeE' +gH N:HD k8Qt007[hzp_XBF#;s5PzY^F$] F"KMWϩU:I.rxP_#Jit#ɣ \:㬧:!,|W#J>cr`PfnCpf1.#zm!3^Yx[#"[:Xly2 +wF5цKGDImòåK%$k%^ۈJ7[sZDw_-ưPWC2|Pt pmnxu>f1°&G2tVEcHaEFt WDoJ7OH7//(<֊b=o~L$]|S;IՈf[SϺƀ>\+],,aGW`⤖\:,[#Oboes +endstream +endobj +610 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 84 /Subtype /Image /Type /XObject /Width 188 /Length 681 >> +stream +x]HSaSJn劍 +D`+#RF & f"#XH,ZIh]i FTHc7)vPȠ,O 7G}/|<.Ξqv^$B!Bu@{HBv,{~3v }àJ^ߏr:C͟>]-p-"dkT+]5 +O>d$˰cg#~'dƎgU`Ȼ;FH|>tVNfvL_OmS\U-/Ƨr1kj_Rla¶`Nx F v +7әx2&|z]s7r_6[[j+sxYܑj&Uo}߫sI[5e cDzDu4(Cy%v +"I|=$I2o=3^ڇ[ǻ51̍I~qñQUvQ_8\`9/[ey0c8%Kյ}!в`GL_C`jϮmhIyG!u+k^nF.bȽvz;K aWiS ˣ]Z}Ù`GiW?4`7iՙ}޼ۃxv;L󱳴-cipyvj.}୛W}<65֟9Ta[G!B!di9Ҍ +endstream +endobj +611 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 133 /Subtype /Image /Type /XObject /Width 218 /Length 51 >> +stream +x1 Om +?qB +endstream +endobj +612 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 137 /Subtype /Image /Type /XObject /Width 155 /Length 789 >> +stream +xKHTqT 43[dF/*+(++dPM-4)f ED%%CE 'tԙޙL8#3s"M4M4M4M4Tu4J;{QǕseUDGyl墲!Ywy2Oyyw8)t൤ ~ٓ8:dAHx$d_ʾ=`g< 24oJ\KD\Db b):juqx,j)Aˀ$hZ-˹4k+Ksw C;4 + &y}Zv $c~d>,(c.B҂Wʒ~Ai|G+ʸGip`nFӖhod.Wh9h7ƪGxhF 1{1^!d%2~}Zs7Ӭn!3u +8֘ +ɹ4-ƘvVHc,4!Z9Dd2Z$B;Q e14:@7Vi{Q4ZXzд0Z Q-XGN}pJ (D( bPv*#ʃj2 ma7:VwsY6gY20 ``Gdw% DJ[',#JJs 5s["D{Qû{4&IJeòȌݱ k;Mk?fVmmd)k*CՍ$.ZރiwGђK5& e[!Ϟ4M4M4M4M1) +endstream +endobj +613 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 196 /Subtype /Image /Type /XObject /Width 253 /Length 418 >> +stream +x=JPk#(hg ĈHa)? 8KglEl ""VGx^[$I$In~elXSW0}9{şfOF̾=G!u>G+;W*o#{5ϲG{?٫cxxxxxxx× d%/<<<<<<<|K_2xxxxxxx× d%/<<<<<<<|K쑽gj=WS|O"v=O!=zn̞:&wsGbD'[%I$I + +endstream +endobj +614 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 578 /Subtype /Image /Type /XObject /Width 440 /Length 1313 >> +stream +x1PD8LР(%Up3on/6vz1ϑn+-i-ߚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkҭI&ݚtkuKy|{ drt5 + +endstream +endobj +615 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 192 /Length 301 >> +stream +x?+qAq(R  <]DWhRFPFɳtawO}WޯG~R$I$In&Ngƫ|+;?[ wp1GFRStfl)ӿJWƶ3;te8ߥ+c>]e?~,Ygϲe?~,Ygϲe?~,Yg]oѕL]7tfh%_s*̾ѡ\ѡnqA2Hח#%I$IlE4B +endstream +endobj +616 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 192 /Length 301 >> +stream +x?+qAq(R  <]DWhRFPFɳtawO}WޯG~R$I$In&Ngƫ|+;?[ wp1GFRStfl)ӿJWƶ3;te8ߥ+c>]e?~,Ygϲe?~,Ygϲe?~,Yg]oѕL]7tfh%_s*̾ѡ\ѡnqA2Hח#%I$IlE4B +endstream +endobj +617 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 154 /Subtype /Image /Type /XObject /Width 147 /Length 826 >> +stream +xMHTQL*)J&de.,$2(6̈EX s!Am,%BHet3s袞C.f_܏sF˲,˲,˲{ϹO ͏dWHf;N vFbLjeq]\fwR*v\;h2!erXJ+XN65;HT. +ld8\우dp ;&,ol!1%,{=ώ Iτ^cB}W4Y̮qfikBn8>?q]2W9sq[gʹQ İ#T@W,;BsNX?-']KOg7R^v"#0Yyl)Bj +EJv@ ;Bq@;BQ )nst%ˬ;fBH1ƎФt ZZve܋sfF:f5 +gmbG(ʃ@Q#Kghvpb&_ K$̖]Kg7hMt4AWx]`9ö q#; +9Q %QR >]!NZzznF$Y`B1bW(0Î$ub'X+ts=%v> +stream +xOHag9L3"fi$Rb(uujA! "":xZAHHPFj*Zl~y뛱~%,Tv D^{,)U#插SZu֪I>97 |;Tm&D+џ# UŽavǕDqؕ-bvqsTx;n{؅e'X@l+;ЏvGJFh j] jyɠs]agҁY D rKg7X#WG4trNb4:iIe@ʑS~@z>sdunPcGX> +stream +xڡJpi0ÄmIb +ZDpeM,/X=,;08z+ tJ!""""yzW!^i1زן!ɡ˼!%kr+Գg҅zrRVj0;iT14Rj„ &L0a„ &L0a„ &L0a„ &LƼI#myFPfZH3cYe,n2K7Zo!N'Kh ~g +endstream +endobj +620 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 167 /Length 269 >> +stream +xڡJpi0ÄmIb +ZDpeM,/X=,;08z+ tJ!""""yzW!^i1زן!ɡ˼!%kr+Գg҅zrRVj0;iT14Rj„ &L0a„ &L0a„ &L0a„ &LƼI#myFPfZH3cYe,n2K7Zo!N'Kh ~g +endstream +endobj +621 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 167 /Length 269 >> +stream +xڡJpi0ÄmIb +ZDpeM,/X=,;08z+ tJ!""""yzW!^i1زן!ɡ˼!%kr+Գg҅zrRVj0;iT14Rj„ &L0a„ &L0a„ &L0a„ &LƼI#myFPfZH3cYe,n2K7Zo!N'Kh ~g +endstream +endobj +622 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 167 /Length 269 >> +stream +xڡJpi0ÄmIb +ZDpeM,/X=,;08z+ tJ!""""yzW!^i1زן!ɡ˼!%kr+Գg҅zrRVj0;iT14Rj„ &L0a„ &L0a„ &L0a„ &LƼI#myFPfZH3cYe,n2K7Zo!N'Kh ~g +endstream +endobj +623 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 152 /Subtype /Image /Type /XObject /Width 150 /Length 747 >> +stream +xKHTacfi-Hn +MQa`A-MB0"C袄ZMICAA*ZT 0jd]FymnY$E;Wg19;<˲,˲,˲J]Ul_uS+m_f7ϖ5 ؒ2([_ -y ۣf6&[]*J59U̗?,H-T0,k$3)b6Y`]e Q-) 2we#dQḻ:!6BTv@[!Z5 VG>7 +@j +VyI"jgdcH;VYU6/-OKj ΰ{ }9 Bx 91BXJYmlȱlȱ*YzS`E'5gszn Y7]RyU&z;Q7 5ŁX[!j_~dV?ժu۔m{^<ceYeY_Bԣ +endstream +endobj +624 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 80 /Subtype /Image /Type /XObject /Width 171 /Length 273 >> +stream +xӱJ\.F4 n-n/$.nbKQD5;Xpz@RJ)RJ+T{hO7ޝ.Rw;.'bK[t"=b3#h 2Gle4B2zF}25!Q35#Q35#Q35#Q35#Q35] VʨPʨP\wQl&*3sRklwп!,5Ǘ)^&PRJ)mx +endstream +endobj +625 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 480 /Subtype /Image /Type /XObject /Width 540 /Length 7056 >> +stream +xw\"Ċ=%Qch쾨D=FE16{ yv]. "i{[b(ev{vvgx=>3wB!B!B!B!B!B!.(YLaQ]zw#R9'fdd8DƆl:s\#8: 16VMP {DƓkKŻBJCأ&)5J쾪 7fkB=ךT^{8x#^إ>vKy0u7% ", +c7.%s=`j/ܳ@}]Qg\U{vyD<>3斸4vD 4?U5Xw2\;RRD$יA%ZX)v ɾL EZv(i-s z7&*,/61)fn? UNگRcX[ut7XJ8.֗$n_ Oǩ=_šh_5mW/g +-~j^J}VhZg2,4)䰬7]iPU{aO+ "9Z3Eӑu yּ<[Ǯmz, 9U'Dž= ʥяNwx4yym <#4RF@'#S5:WwI(r#&6(0^B}Nl^c%t3bkyTGۋV\z_z&_fݷ>K]=N[70pֺU8j W% míaTh]/_ku{+juSh՞K?kk띚md@/L#׍a4b*YVT 3e3{Sv\&9^1<"^CFsSWg?()af||їI $ӟ]Ͼy6?+S:uaNk#>9a}+,8v<`\s=PǕeOs1]W{ӠF˺hȿy|@-2je]&WqCrkWb͇ЬY/p~n7vzw~Ȧhs#?mC wB%E|?,zTI "/IrIe 91}gd() \žDKg=m6cKvoWlz +E!ڨ"Tl}tpIE(IBa fӱ/,ӑyI=O~ +PDB*dcTn +D :fm苾lWcϕ`e>Pس%VI0s؇=["M(%`ϗ@l W3&LS`oU=eԲŞ3q`7[eBJaOQ + {P.CoVn/Y3]MVnU6n) +Ϝ3mq!U.JaNȒ̾Pm ͌EB;A/dbj,džq[ZA^ϊyO:ek;fΆ/}^s?0.kr˭?o )G'谟_4q^yµP}U Iz/WS)5uAP쾐,A{~9Ɠ#~=v"8ڋwxD ztHҁfMD{SnL* Hw2/a|DعJDuv\d- ցɇ¿ؠmMg택 zf^Wi&DFaW<1̆]aX`fv$?%ђnXٸ]:)@z0lz/産IڶF캉vcA`Uaⳡ['<tuC: +VHt_.cLlH6l.Hvwph]0n\0L.h!AaNΛQ^a?(t OZv.m)(=nڴ].(ؕm] ؊])ZbqP㧺 +-B2Z](GL:b""Iuz5r+_Z@6QXU?&8UZW{6J&\HaWId? $M; +m$av)I7T(FW/*pFaZڣR~{pЋj~]"(]"qF vDHp.kl8bWHᜍ=}7Z7 +$reN |-*rFi38FvuD ^nY=.(r h|]Q&m5h4UĪ7w=h_74h.0G4zaWEX8c>i6*h{(kSh]a$XO~ ;ѨCjT,B!4f s_rC>kFK̢Qt/JebfbCTV U.?.mt‡_K |4+ (ݛXv`S`@QRZI%S Z2-s+%Ѱ LI6`d؃'\)ص0Gk +֙O<*hң-xll;+xha,c{脳'EeF8{脳.21{䄷؆2=r[Svcp!cn yјKKz_sy٠'F2s2F_q҇ȉFmS}+'등Mn@9=l"j9X=l"9h=j"@&( {D{D:2ᗂ=l"FF4,GMDp [أ&"ȹ2 {DTYwSBM}2Jb)}6=j"Bۓأ&"8Ʈ#أ&dL S&z͚7 Aldދ2%C'ie,~2 geG`p&gh[Nz-7DcK;fG؃'\(7OxJ.+7M=)Q/,ctN~4 gc܆'\NR-'cGd?Fsul@E727w[`W@xqw+JCQ4}P7l3îpFm9.p|hTo]ad H] ɢlFꡌm۔gvr6[z_ _<Uڹ`A8<}th[h3žˢ]l;H{s dv$ -qt]aӽ;hTF[W=3]a[р`Bse"莛 Y"pd+v%L%b+!EVey9I6 v)1Ml"Cߌ)&4v)1'O+m*z/ҖQ6^v1Yecص);~qj*e sfـtM\e E/'gSY]]a~fv?3tv.cq D_+'~#ҿa56R݊G(†q(]aS؅]ab٨]aAfyk]aꑂ]Q.9ޚC6`@ـt^֟ *W4sus/^`WG0yS,CRX9EM (G*g6L vD #fnaWHj7E׊ZmY;F5yM",WgV5% Е¡BWDdU09qJH6rvD[l· =+6EFGLr[`6'=I&"SzxTaY &=,z + F/]01 zx +̆>]03Q`6삉dfU]0ǧ?uO `"vzS1bL$uQ0`"Y1h &ElJ .`""ѧd}삉d"b @ /Md4&ГjRh6҃Ӫ+vD؏]2(XrѬ7쒉Dg hK& 06j]2Ll6 *,6p b&"#/#iŨJ`ah"Iـ& hP)vDWgôRzgF4z$V^O.HUx6 aHIR {u&AȆx]8)Nw8]8)PdIA0bWN +%CckF`N +7%'./CS%~/I|퀐ӰmV lIc%cjlaEjD_*,n&Z6D,8]>G&Z4<†`O|1}îR l y 5Bhh66QIgYL]lـ <.hvEtM7_a| S)v0z /Na-i%؝ I~W*{`s;o!j v&*7Aey6*c¹. N;""wcwtw=#DZÙ;Bx*X|'$[brixqt쮐,!ABQ"jOB^d^8 ;:w(vk ޑ)ȃ珮ؽ1laT(@q1h'c' o^n!;@Jun:V{v վس_NM2Lb}ALz&Cd/vyyJnܖh=RtuzD#>Kxn{Ű']A50qv{ƵP㫆! KRaϷvƸFa70<8 dk 9kbϵ־Mz9bObΞެ\A!1,U1ɇ&k{HIT{kp:~/#lhG(?lr[9ٟ^^v#]!A~diuڊ4a*e0O՟u[/ v&d )JgC{]=«?= ZoGa6גDO ~i?qZ//);mv.=E܏of̸1QQ{E>K/hzrh4}뫿{|vѽB)3|VV9Kp쾦j5ӥ}m?hy I `}7Nbu=\ ҭ&^ ~42]zԹR۵`e{{5v:{ޓ;C1F“ +()sh:#s7ߦ[h K[j}VFРC?p5%&4G{WIN僚3|ZOU4YW^ZS['|vہA!a +{<{SAẋniTkag$H2<S%7,h3k>y=:z=4Ot=%0:Tԍ7L][ի~^yӓ68KtaHꋵ*Rr +:ʮdfS,W?L'<񋓃H9Y}ulj1=IѦGz,xʗޞoZ)1 .}4[g>[Xi ([RTM6x2"!=-d~XΟ>Oᩩ?x1'`;+8ljFJ!YJTJ=ukY߂޷2f> ,Rc]5LA'yI E vD v~]-Jˊr9TD^osP=89AϚm|oީ gK$roi]#JT?;{Q2RD/veDlW &o4 +DOnIaEgQ~,w74^XMo~%kt[ٕ̇6ZAK6CưU@EUᚗ Tz?xC}&c@x4^R/]ۣX!{Û*+!wX~rŷD%|<0Pn?xtBJĄ-ިL=4="`x%Z|me _/m;9X> +stream +xӱJ\.F4 n-n/$.nbKQD5;Xpz@RJ)RJ+T{hO7ޝ.Rw;.'bK[t"=b3#h 2Gle4B2zF}25!Q35#Q35#Q35#Q35#Q35] VʨPʨP\wQl&*3sRklwп!,5Ǘ)^&PRJ)mx +endstream +endobj +627 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 83 /Subtype /Image /Type /XObject /Width 141 /Length 254 >> +stream +xٱ +aG@b`PVb0QJ &f7*FwlL,FIdx%7pY3c1_% +-׳R~Ela_Z9UQ6] n,5, KTbvQ<LS cC 1C 1C 1C5H S0]-LT0V]03 2&Yò,"1b}LyCE$8ڻ.i|93Bc1O- +endstream +endobj +628 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 83 /Subtype /Image /Type /XObject /Width 202 /Length 291 >> +stream +x1+aGXD8pJҙmg9,Fl&Jff>I) \oOp.v{&RX/kz=wнR Yp/^JnC랤yLEO)W*Jɖ{QJqTS<$%y(CIJP<$%yJŝR^RJc^UJʧ{`U*pϬ{Bܻ{hx.Кs17Iƣi̞=^w? ~?Q]N +endstream +endobj +629 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 80 /Subtype /Image /Type /XObject /Width 171 /Length 273 >> +stream +xӱJ\.F4 n-n/$.nbKQD5;Xpz@RJ)RJ+T{hO7ޝ.Rw;.'bK[t"=b3#h 2Gle4B2zF}25!Q35#Q35#Q35#Q35#Q35] VʨPʨP\wQl&*3sRklwп!,5Ǘ)^&PRJ)mx +endstream +endobj +630 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 80 /Subtype /Image /Type /XObject /Width 171 /Length 273 >> +stream +xӱJ\.F4 n-n/$.nbKQD5;Xpz@RJ)RJ+T{hO7ޝ.Rw;.'bK[t"=b3#h 2Gle4B2zF}25!Q35#Q35#Q35#Q35#Q35] VʨPʨP\wQl&*3sRklwп!,5Ǘ)^&PRJ)mx +endstream +endobj +631 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 83 /Subtype /Image /Type /XObject /Width 212 /Length 296 >> +stream +x-KqbQ`P00 $ 6:Mm~EMva],  ~sz>)oi;Pwr{yWm긧hI+iS+cJS˽2f2-4Wm +M]Ƞ=itBS +4@S4@S4@S4@S4@S4@SJS=2-4GUBS,4GƼ I|g> +stream +xؿq`938)R-pd0lI܁dPdDJ`NO>x^=)RJ̕VS B3AmF - +|^h;xMĈrD%tBJ!0ADADADEQ SР~BJ#4Gve"aB!E41F#T@ϞXfa_W!|> +stream +xܱ*pE: [P&)ɦ8vrGhbt.@9eT&aB\o<}nmM$I$[ \S q=4Z=7s4:ޚqZx^ҏU]WfgfmDWfF6Wf"UY7aʬ{0Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8BU+n#UYgYGQ=3)Ϋg> +stream +x nH@|Q +endstream +endobj +635 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 174 /Length 42 >> +stream +x nH@|Q +endstream +endobj +636 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 169 /Subtype /Image /Type /XObject /Width 695 /Length 609 >> +stream +x֡qW`f+6SLc 3$A]")@Sm|D8~W| ?ڸ|csyxʙE{.̷su<#Ϟ d{cJXy5+aHf{#.gτo e{조o{ey>{(zb0fO>vYkf_OW,~}D Dl +endstream +endobj +637 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 40 /Subtype /Image /Type /XObject /Width 177 /Length 431 >> +stream +xc``Q0 +F( Sh&b4Ў L{Vb6ŅyBa#Y?3+;'7`PHXDTL\BRJZFVN^AQIYyeum}csk{gwoSϜ5gނEKXf݆M[صg?y^y(߿2$8߿~ϟ>~x[7]tܙS'9t`ߞ];oݼq+/]p3O2iB_OWG[KSC]MUEYIQa~nvfzjrb|ltdxhp<ߞTL0? $^3! &@]n7@(.; BL[v(`Q0 +Fvi6 +endstream +endobj +638 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 103 /Subtype /Image /Type /XObject /Width 176 /Length 40 >> +stream +x1 Om_7F +endstream +endobj +639 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 117 /Subtype /Image /Type /XObject /Width 89 /Length 258 >> +stream +x1 +@ +6);AP#X +^Sx`!"h !qMewAa| 1cLkNQǍ e٭g xkQθגpo6T.ʍ5rk$r[Zn@2* ] Zn YڮP"99\ k)s>-2 Т D0a„ &L0a„ &-7L+ +c)X +endstream +endobj +640 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 162 /Length 266 >> +stream +xԭJpFנ5 a0(F`eeoa˲`1,/YD* 2k3?S?+ADDDDD%wzbuۋYeٷt2/]UxjeGMV]¼фS0׍(=I7Es +sMM"D!B"D!B"D!B Aq?`83E^+p|C@^y|-^yoX">"""""s?2 +endstream +endobj +641 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 164 /Length 263 >> +stream +xбJF%$h +ɹڃ%1;h1B'^}ιADDDDDY鯱4 S13TIH||ԌoFbBܵ^b'5(ƾ8ň#F1bĈ#F1bĈ#F1bĸyƮ [16Ÿ5cyC29#Ϊ2p? q#O51z9_FHDDDDD+mU +endstream +endobj +642 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 170 /Subtype /Image /Type /XObject /Width 263 /Length 468 >> +stream +x?aB ,RFe Yr`0(J&.:]'x{ /n\> +stream +x/HCQ77DŁE-4Da00k`2p`pX\4,6bZ2>ޓ ?9'^s\VlR!<>§-öBUouBz +UgO!Y+8ά:S{Bv,08hmG%Fx4&C n ^X[m4?7cqPrX'/5 +a È aHa y2f 7a s Im騇q2~E:a E}M +endstream +endobj +644 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 101 /Subtype /Image /Type /XObject /Width 167 /Length 39 >> +stream +x1 Om +?8A +endstream +endobj +645 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 167 /Length 269 >> +stream +xڡJpi0ÄmIb +ZDpeM,/X=,;08z+ tJ!""""yzW!^i1زן!ɡ˼!%kr+Գg҅zrRVj0;iT14Rj„ &L0a„ &L0a„ &L0a„ &LƼI#myFPfZH3cYe,n2K7Zo!N'Kh ~g +endstream +endobj +646 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 104 /Subtype /Image /Type /XObject /Width 167 /Length 269 >> +stream +xڡJpi0ÄmIb +ZDpeM,/X=,;08z+ tJ!""""yzW!^i1زן!ɡ˼!%kr+Գg҅zrRVj0;iT14Rj„ &L0a„ &L0a„ &L0a„ &LƼI#myFPfZH3cYe,n2K7Zo!N'Kh ~g +endstream +endobj +647 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 139 /Subtype /Image /Type /XObject /Width 40 /Length 200 >> +stream +x֡q.B"FM\ŸA"L~s+i1oy:jsM:@[\"`-h]ąNMѺU"q&ۧ 2Pfn$mv+3c tp1 +*wEEzsEĭ7ec +endstream +endobj +648 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 167 /Length 42 >> +stream +x  Om7x2NH +endstream +endobj +649 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 335 /Subtype /Image /Type /XObject /Width 361 /Length 1874 >> +stream +x{lwSj=U*Z}RF'mqklk bceXHt c}Y%na˒2]\*bs$yw~i;yHLE8;:Co#(2#k3_Ύ { 큲0{J[&6RjPGW3:' -C焱> cm>1am|S75*:y]=wsa޹:75憛ل@B63$Gw%*[¢q +B $q) :Ąf9?"t <[ 8/Շq< G'pȳMl1:CVy&:w,ꓺwZ{tttZ8tW D$M7#D7ˋulCó6 "crut,t|N0"ENF~FuApeW 6H4qԼNS &Rt"i:ME'pHߩyntER kψ(tD;N 1U\$|. qt]ݥgn$J@7P?v)CB;tzuP$;N 1S}A[Km:r77B c. LѢ3#":CL^ъfF8 >䮯N2lk>CAz.^N nF8,DF(\6F'HldX'F'BJ?BیN N$i=]@D'p)NI_\(+ևf' NvHnE8aڳb̥t@@|^9.R[ xDqL. +!(@D{|q3HRJ͋ز]/Q{h]iO8dhćé<:Cݸє 38|jc $M$=sDiǻaVNj:DPdA'pXa $4 ON4 idnItz.B߶i I> + 뛇v 84( FlN)>lq=ok:&awst@bp VY9\LF7VWt JE7p]@1l!q_'tnvŚ:Cl̛.It!"jiIѼ4@"VGVd8l $4fځ"ofA` :MG'H?QAbes>C'p]@fH~SV }1omѶ]+B.F xВw +tZ2{#^Ƽĝ^zM$t t?hUZ4B7H@y":Co.uG)Ћ.>j) DMEW:(DlIn>z,.a..^. 2@bmE'p_sr>:;"dyϡd 83ӌ wSj˚OCOtݓNi&DYԪ8|.:DvxI8@یN .<{l%:CPctDxwsRg> +stream +xnUqD-Q1B/RKbmXJ+mJsGSW0 |1DR>7/kN9p6 juoaKȆP!dC/ǽ/ + dCl|^PC?w/(lhtȆG64Ȇ +fͽNa.|dCl oW j "W>s/(lh !0sɽވWjȆF9K6ͬO6Dξ^P *|dCl jDP dCl\$j dCl|K5t"^toa=Ȇ9G6 B6f j$"c@64Ȇ +ך[pOa0ΑtdC(!Z 5 l m#"dClk-̹'p,{C q/h>!B6TF{B CϺ70~ƽZ 5 E!B6T&Fʺ{B ñ{C G6DȆ +G6DȆ +׾PdCdl#"dClk'@6T& lI6D>%kj8A6DF>!B6T.tdCl|N 5\u/h>!B6TF{B 'c &F:!B6TFƒ{B dCeǽȆP"ȆP!ڛ 5j&ȆP!Ȇĩ1&4!*d#_ւ{B dCl#"ɆH׵=PP:^|dC-!2M6ґ B6u̹'0B6D^s/h>!B6TF˳ 5Gj ȆP!\tOa$n?Pԫu;Tݺ}mji磈QwC6r?wT'f;CǨ{KαǏA0cݽy̽Z\֐{M_םaF;+{Hu߈=WN?qoD +endstream +endobj +651 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 109 /Subtype /Image /Type /XObject /Width 105 /Length 812 >> +stream +xOHQ%Zs5\(FnD(4эSClPQVP(ռY$:P&ݤBI oN 9d`L7.N3p۱3A})ρ5SΜ@1a.} ey{Cݔ 9f 9+;~[exiY((NrsX^)S7)lN  +endstream +endobj +652 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 169 /Subtype /Image /Type /XObject /Width 695 /Length 609 >> +stream +x֡qW`f+6SLc 3$A]")@Sm|D8~W| ?ڸ|csyxʙE{.̷su<#Ϟ d{cJXy5+aHf{#.gτo e{조o{ey>{(zb0fO>vYkf_OW,~}D Dl +endstream +endobj +653 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 120 /Subtype /Image /Type /XObject /Width 247 /Length 303 >> +stream +xܡJa & Fݲh4bFn.6qF& &E.̏~s8?͸FI$aDpCLꥩg~_4N0zh`[\GɎe˖-e˖ٲenlٲe7Dlٲ"[l -[Ȗ-[vCd˖-!e˖ٲenlٲe7Dl;zha0zhA0zh`{LLs/kܬ[M'x=ca56Wz7 + +endstream +endobj +654 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 169 /Subtype /Image /Type /XObject /Width 695 /Length 609 >> +stream +x֡qW`f+6SLc 3$A]")@Sm|D8~W| ?ڸ|csyxʙE{.̷su<#Ϟ d{cJXy5+aHf{#.gτo e{조o{ey>{(zb0fO>vYkf_OW,~}D Dl +endstream +endobj +655 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 135 /Subtype /Image /Type /XObject /Width 293 /Length 61 >> +stream +x1 Om O8 +endstream +endobj +656 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 135 /Subtype /Image /Type /XObject /Width 293 /Length 61 >> +stream +x1 Om O8 +endstream +endobj +657 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 135 /Subtype /Image /Type /XObject /Width 293 /Length 61 >> +stream +x1 Om O8 +endstream +endobj +658 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +659 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +660 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +661 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +662 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +663 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +664 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 1148 /Subtype /Image /Type /XObject /Width 1450 /Length 20299 >> +stream +xytwwHDc `C{){)&}/RB^HPD +^JaVR5nҴ6KiޢDl:Niۮ}$tڭ{ѳWӧou!CՑQnj!ǽ6Ԝ~ey2\&o[74\~Ӥ3ONZ>:صmkԓΛ[KϘ6ur :n}{ՑȎۦ&HgjZrbŕ," 3_@P@W&T +'7lZR : I#u:i}Ͽ,v﫥ޏls衃åw6.Uϟ;O?fdS)طWΕ$UJ%_J"$Gl"%(,O$] _O$}LY2HPi-ܺ4\8wF7rؠ$Mꗔ\& 9e o~2f!9seQ䖼+VN +"u5l) uZB*Re 4_;#]{>(}?utwW:Ȕ{0@f2kWKۋΟyUj9t`_?IΦM$usHi'/%u^2_ؾMj\Z -W*DE ,R@pd.^*zQ)ܮ[դڌ.o){?"~#wX~y&앒jԎȦ[))e;0^j3nB9i6sÚ76λ>ϧKݜ#>~xʤ/_\?r@}ya-jIeQKMrԯwm6ED֮Qr-S$*Xʞ% s$(xJ%]~CIW#|kPZ}у ԓ*_-^ +'WX:d ˑϔ%$Wd+"*TJWJ4lҪTޫRiqi/'kڸw࣏;.\^f\u^{Yϓ7~6ggNk/>سR{K{IYӧL#^2Tܾu[ZR0!6H +!VJuVi^)v=truϟZ չME^L9%= 1EL([1D֪SQI)ԮSRt/%K_W[ZZasm~\W&9bS{{e/$yAǣ$6w-,NzylyQ2qi;k(,M_>bdi7zǿޓ'O%ݧM1tA>>2z%K p߹] ȸuZ%7nvA;[)۶x;K龳"2Ϟ +ɢ,*l _F{_~ѲRF|u׵e_ul +={)*^Ɇpyի}Ka9{)(ӾgM?]^Z޿~h9{ H9{R?{c?tyyRK ?VW徟QeY'%tW9C*<(DY}dGӟݾrN{ |I{"s|*ѲС>#|TYeہ?"}'ͽ{_@^ږ}++3wؾ-,]-[ٜYVmڴqCfY%szkyy͚իeʕ+W.]4L]dL2s27x 4M͞=ռ2hƌӢ2BN4iR-$Ǐ 3f(yaĈ/JC * 8 L0`@iҧO>٥e=zInݺvv:u'mڵk&jA7o,ToҤqRaBB:ɵR͚5U&UJ+X_Pz WOYgAE_3_M~7otMxoMd±k%N2?gL7J_?3Y|nbÛ+֖sfN/^2_TжUPIUrlRDB2JlYEd̔'?0QnC't̠G^7s?zsWRǏܻ G%֡cRg?kW>i?fIf͚='\/XxI^rJ4uϾC- N:i^u/#^O?]^Xݹs9eOSK?[{L͵k?Aً_]<K.N|Hi ƍ]~ g&L |9g sH{(8o޼yeآEI˖-[U,_be,Zz͛#,&}o="d;ލ;۵;NsZ{(9z,@>:q䩌r.#9wg"QIT/_B+?\ 7nܸ(?޾}vz}2m_.ѣzɓT?OݠٿQ\?~Gw?ѣ[f>z_7ɓBf=y߷"zÇ7Ծ{ߗkvϷj~4{//ssΞ9 N;q}{woپAGln#6Z^\P'H~F֔NpFRXw]t;]=U勉,R]Qj(R=C֔:pNpSW + UOwWzOwJBCUSj09(5*u6_TJ|t;]+0SR`暪\U3߫JNpeU;Tγz'KwNpUKSj0sNU겔|*u9J fNJwNp1U;TaܥҫNp$Uk< ڣ*umJ fvJ]Np.U zOUj"UvYwi)5٦*uKJ fJJU L zGUj\9N;jܥNp(U߷ Z*{;]oJ=R5RkܵZUj,U;n ]Mz'kԯPj0LUɔJ=NpRU϶ Z*5Wz`gӝYw޸m5LU?[wWz-.]ܴ zCU"*5Wz`U{M ]yz'k(5yMU׭wt@lU͵ }ENYwd5XUw *{Wwte;]SUn:z'k\qNJ~o5PU(5*aJ ft>NpU돲 ҕ+=3Zi;]+wJERNɿz'%U:YwU+=Ͷ ]}{z'a5RU> ҕ;]#Tz'k\NmܥNpW_UPj03TU_Xw Q3QL`\Nbܥ{NpW/U =NpO[w龓@a;]&Jdos+.Օ^cSU3>`FuOJ fT3NGZwWz`GU);]Rsz'KU갟ZwJ.Uo|lܥ*uCwS:ϒT.U\95Q:;J fT.-3R}MY[ JNUKYwJ];]R'~vz'KU? RwtWzZXwq^X;z'KU'xN fTRU`FU&Sj0*up;]\שJ]{q.U[?hܥ*uXwJ~~.U;z'KUZ]w};]Rsj0*ųJ3RBԅ JNUƃZwJ=b>.UGz'KUXwJ=n;]RJp^*unMwT`FUK)5QzJ fTRUKcŕx]; Rz;]R/~mNpK_{z'KUwZwJz'+=:Uwhܥ*[yN fT8RUߞB[&Qj0*uL|#.TiNpԻǿkܥ*q;wT7nNpn R:8Wz`+=:UWt RHSU`FU)5Q䋔̨J]L{.T_Np̨JJ fT?3R_KԵ JNUXwJC;]RmNp׻cܥ*ͮwTN)z'+=:Uldܥ*<3R?hGRj0*6̨J.NpWzuRWNpWxN v4.=;RG~KQ)5Q}Ә;]\ש~-z'KSsm[wiJ[;]R:.MKnz'K=_Z[wq^)uљZwiJ]HSMQj)u)5є~J vTߛ5єPRSMW`GS#(5єh";RGe,Yz'+=:Mjܥ)xN v4>@(5є|,;Re,z'+=:Mׅn`ܥ)hSM.Lԗ(5єHJ v4z'+=:MWeO Ҕ<;R_`GS꛹)5єV.J v4ϕJNj[wiJ}/K;]RwNpͷ ҔYwiJدB#.4WNpOhJPj)SJ {B=uM)3\)J_Zw)J`HQLSj0(u )JC{fŕx!'$NpԹo )JCR]`HQ?Qj0(ݹWzuRnwsj0(uo(5R:CRH; T);]\)J1OJն R:<ϩg)5Rԧ )J]4CRgƕJNu?fJ:;]R;sj0(u )JxR!E+`HQD-JNQRNp5 )J]J NKuPj0(uXfŕxԕgJ;]R7sj0(um )JR!ECԙJ| .7W*Xw)J`HQ(5R[ )JnCR,wC\)J]gܥ(u<CRw_F=Pj0(uŔ )J^\Zwq^'eXw)JwϩQj0( )J=uJ J,q^(uJWRř<CRA#Sj0(i iE0ĕxH.EM95R4J 0R!E'`HQyx%.4ߝTocܥ(<CROEGPj0( )J+DԱ JNݹI;]R>`R)J=w CR1R!EE0ĕx/43_ԋz )JCR/IK{Pj0(u|z'+=:EJ )Jϩov`HQu(5R )J*Zwq^(|}j0(TS!EnI4`HQ-) )J]2װ JNQ)Uz.E795uRhL6`HQ$NpWzuRH);]Rsj0DޛLjSj0(u|[0ĕxo$Hz;]RT`R)J}(R!EW`HQYbc.STֵ Rxsj0(oNp'I;]R*+}Zw)JEqfSzna){j0(gNpg(5R\CR`HQꊙ$>z'+=:EW*mw8ϩSB CRYR!EeUwSzO8Wz`HQQ<CNQo#)5R )J]_J7 JNQRNpW"7ϩԽ[w)JC\bܥ(cNpԉRNpWzuRo& \EoˬwR!Eo`HQsRj0(uM)͕JNQꍙyO `HQ;)5RnCRR!Eغ;]\)J3Thmܥ(,ҝ`FWn˭wY R`z'KQR~]z'+=:E? :Zw)J0tYeE(5xS6 JNQST .E95uR)J]Y$!z'+=:E5 R<3RwZmܥ+u5;]Rwxz'KQ"q\_t{D[Yw=z'RQj:J ^.RNpWzu/u~2w_?EJnNpm߲ ҕz/^$NpWzu+ODt z~3!fNp{~PjRgR%ŕ^HLS.xDdNpKHkS]S7Zw=YdRgU;]/u"[Ywq^R)z'{Nz'RQj:J ^R.z'+=:ŕ6mwtϩ[J.]SXwJlNpJ/Q$NpWzu$H|_:mf۬w(5xu+z;]\=!'EJv =nsj0+u;]R7aܥ+uwwWz"`+=:w@hG.s;]RQjڿH;]\)v\sz[wQj:J ^GWzUDZ[wq^+z'K.ϩuXwJz'KWwWzeE5 JNo R>z'RQj:J ^RhaŕxKrLP?.sZwQj:J ^G_ ":Ywq^Rgy_`w:j<3RWz'K9wEYw)/"U[w)^p Wzu/uN;]ԿTz'KWJGwtN 9FYw)JGF)NpQw~$Zw)J{&)Npԥ$K_.,FZw)J}7D +|oܥ+u.EdwC_ RBz gŕxf,/Yw)J}+z'KW?Yw)J}3] RF nܥ(uiz'+=:Ewӭw!\ܰ ҕ:M.EDH[;]R_@rlܥ(udlŕxDH;]R_lZwQj:E/`HQꯊRj0(u aNpWzuRo($w>+~ ҕ:Np$;]R;]RIwSzE1I7z'KQ$ß;]R< RDylܥ( +/.EK JNQ"wz'KQ.Eyjܥ("R`&;]\)JL.E-5R~!Ew(J^>G(J]4t.TEdNp[RDZw)J;]Roj%z'KQꍩ"[w)J!z'+=:ETimܥ("Ywi>ug;]R"rz'KQD~ RFq;]\)JݺHC.Ez'KQ}DZw)J=.Emܥ(u"kwSQ5z;]R.rz'KQi#EYw)J=uO;]RO#Npԕ+̷ JNQDRw?ANp/Mz'KQ걯\ RzwN 2z'+=:Ebܥ(Y"_[w)J=x%.E.rz'KQsE RP.OdNp=|iܥ(u"wR .E;z'KQ"ͭw\iܥ(u˷EXw)JOwnUNpԍw|bܥ(u"wS"wN+rz'KQENZw)J] .E R:0..'}w4PTYwq^)udܥ)u拏Np=Ubܥ)u5lܥ)uz'KSjh)jŕxԁ'NpNp9icܥzNTZ[wJؿNpZz`+=:Uwe.nܥ*\ͭwTz'KU›ZwJ];]RXz'+=:U RLB].UOcܥ*;]RRz'KU!wSzr;]Rh\z'KUwW RzC$.UPz'KU꘰.T$Nps Rz;]R^z'KU)KYwJz'+=:U; Rz8.g)iܥ*7JXwJ{QNpf JNU3sYwJjSNpͶ =Yz'KU[wJ'z'+=:U3_N ҽ>jܥ*uXwJ]+>̨JP2z'+=:UCve ҕn.U3 }ݏwTNwF.G;]\J|3Rnܥ+ѕFNpW[wJw3\JzNpԟ ҕDy.]?lܥ+;]R' JNWewtz'KWuwt^z'KWeݭwt.Sz'+=:]7 ҕzh.]' ҕI;]Rjܥ+u.t(] R~Z.]S7ZwJlNpaI;]\J]bNpIwtpz'KWҧwt.Npuwӕ:dNpԹnYwJWாR= *uwJ- wJ͕ &.gܱ Rn5FW+;]/JNp׿th5QWj(]Zw)> +Atc5OW;] u.fܵTWjT][w)ZwӕzNp]g[wmҕzNp;RZwmӕ+=0D;]+;]iR @W;]tl;]J͕٢ u-.^]IRW TW$kRoy~~Ҿӎ_o痤< 66뽝ۛ6LI[U+&-^p9sf:#-I'[&MjZ¥+ּ6a{MyiS_iSO|ʲ_ԔsB #*K;oI1kW-_RHyu2hCO]:K7ժ$$J +9C~F+'5h% mvJ/d{ZOnww6qzmߵ`S{+׺ˬ<~0nB Kl^\_?=e_Oe<{j3I}K*!Af +,e*TJ)qu6I#w3TZ6btd+[w_}xxYٗ.WU{ly' d˽o&ʦo.}EQtBd6zsdk_O4fҤo]J6-S25'e q%c2H$88(˘5([&QA$Q'MYz3>{ob?j)n^pɐ/|segϝ[0d{?btՙ~gȊ'Ov g\U,7{Z7t`ETHa_l=|Rrk|e~]/蟾\+JWRS"%|B%fO$0KHZ(h@)VBR^æDR;t+txaȰc3 +ɀ޲wCdٙs](!0>yן7]RD2ȡI۷lXRF^lq!y&/9{έ}=gݺ{!ܸqcϼoݾr;2/}LFm5yWj'%6H*GGO AYDB%bKKJIjf +MRZZv* 9& 4yڌ2\q|} +SϜ/%k.]*֝_'/w&@Οԉɼm[6oWX8ZA6ѡq}Hj.:fMI5%e8ߟ RpT ɕ3{H: _>'fΞ;L$XB^Ǯ=gʌ֮}Emf0!IfA@T$  +.rb*d#`Z#Qq &@V/ުֽw}yjϩ]styi+wp(msiWo_}ķ]uǟ^I{:˵T$ ~|'&VTIؽC #Â`Z,"OP!(bkGWea_V%=_][w щoHW.z0?Rcu`{9: %!$/MS^AaQq +dT|}C# +6vXS rp' +Oھ#MgKbwB>8w M-WdPJ :8 0E3`n}2>_EWc:hǩ:Ud^8_QBJѩz4p 6ٝSMb7DE7dMP ^EVKŋ14ぺ\@ '2rC' (,H3(` 02]da8-|ׄҰ.fs|DL3vWot\v})Շ!}ߧg֖LNN wojKϝ/>G副gfZ>ݺaO_wv7jw[9i?PL|Wmomn* 9 5%y~AY,5~>Sҹ;Y\f -<ۍHTfwݺyΕ/E޺9pgo<[RPck CTFRsVT>1)iYy@EM<#>[Z l8l||WKuQU[Rv2gĮcetYUUԫH+*(uZt>{N|#gGTqrjzf>M0ёA,2~J|t4ttɠI9uUYQQ~VNH~~^.(9Gӱ=33cRwJ!b{DIMH@ƍx3((p5np\\n48/]pvrrtLllll‚fff&#c./\.zD\ccS*a85KK+k +~l[[^ظ͍+F%ն] 0s r61i?۾'>~|~'ǑMqnNܴ=F?Eo{ם2-!+++seXK9mWkRh.\ i/?OF}2rz\3]ܖ{4,bClvw~T-kZtg+ͻ1=5o{w; 5KNpV^S''&l^uA0\[+PaD5~ "bTLU5ÞO|h!,6B;{G',= Ǘ>A@غuy1F! [%-i{~!)1YYrsg`w^^ Y*(8Y(ӧϔ J.\,.VW_aVojj,ҖQ_v FoܔŹ6&ʈC%hmkkkn޸q.'Ȣl6jL +M< i*j%K.]+WTW_u^GL]}E)ۤlsCCSo>[״^)8Pz޽w|K𹇏;""tudQ7#c\MLNv톧BŚs6῟8Á)lFGcU>ʳ zlm *JIsV;>~H1E ϑ W +Qћ a.s_|. νH [g0~{_Zwow!J&w:_S>v41VfKcהD}D$2J 6'̅wbgdj"HN1#܅JSlkhjE[wܑij$z3㟾!kfj䌍 XC}=8wJ6Abur6=!G`+5%y<8FGE,G ݖ8+Bu)HZsU!2*PCLQE%9])-2bB]z޾~0Y 8]8'&%p'}_9܎=Sfp>vVkYD{{-\C94u=QB˗jhz|4['Oɉoae(1G [4~=J /Q ϟ?FYW׳uvvvZH|Q">|ۈpA!H=1eQA^Cp```_YC G#wdtl<ljy41"gnjbDfD*b(83=/!ɣ-~vG=^imqúZoT?W3%E|"?7B̚R្-Q+b7F+%<,4D6~>޲0p[Dl,2624KLy)~3?jI"Qh<|TЈ +~AHLL\6[ !++'O49U5^(X,?X,u (jiii AQHkjí4fK@Q(2BP"CsMyUUe>Ȫ!d2t!-%9ba g@w $D"H$D"H$D"H$D"H$D"H$D"H$oOOw +endstream +endobj +665 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +666 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +667 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +668 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +669 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +670 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 109 /Length 32 >> +stream +x nH@)M +endstream +endobj +671 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 112 /Subtype /Image /Type /XObject /Width 293 /Length 756 >> +stream +xkMq1+W)"$?~!w2ن$)$$v/%ԚXSReHʗ[3 R.5f{9~ ^];{1c1c1vzIg[]:^ȱIT?IL eDOGl4HW#5AVF =ݣr͡-{ (xMl=%zw΍ɀ&HoŀwЃ-n^ĈS \VHRz,f>R7sV-? XDO-FJKA'^KAFA^ +U"CQku;Qt蝨˺HO蝨H蝨1U=V^ [^ ۬LDED:HD5:z%&9z%W֥t^ ;^ ۧBi"%镰M5J rz%l&Reo +ٚHމj$蝨!]SNT.RdXI>CAe#饗KAdn$識HVã'z,$#~z-ݡ_鹈Ó^F"q(}wlKIyFOs#YD{$IZw1D#zWC5`Ѩ˹D*=3+tPN=JEI4,}%mT}3}%+ GG{Lsj[_y.';Ůc1c1SL%Z +endstream +endobj +672 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 113 /Subtype /Image /Type /XObject /Width 40 /Length 192 >> +stream +x; +P!KJ4,ףXB U*$ XhG3dY> +stream +xҡ +Pfpe G1ALKA c9 ΅?84y̍G2Od^$.vwr ݶr啻lg}έ[qb]ư-kݹv'xSn܌rGҹMфpq,sJ-{Q^5q]ߋ- +endstream +endobj +674 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 114 /Subtype /Image /Type /XObject /Width 40 /Length 184 >> +stream +xС +Q>i6m"A b&_IA0m {deΟ?8gY/J enW*b J ,>6[j\ɲu9nɹwgexnѺ9N;,Żrঔ;՝n'9鴧}؜Ӟbse> +endstream +endobj +675 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 113 /Subtype /Image /Type /XObject /Width 40 /Length 195 >> +stream +x; +P!KJBtd9ڈ b!VQA, X#Gbƙ 2e.ȲOy:*ֹI:@q1Ը3*Xm-r7]ZՔܬpS +X{&nW-|<97rnȹsk̹ TI{"UIsuqsU\WXַA2҂ +endstream +endobj +676 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 114 /Subtype /Image /Type /XObject /Width 40 /Length 192 >> +stream +xҡ +Pfpe G1ALKA c9 ΅?84y̍G2Od^$.vwr ݶr啻lg}έ[qb]ư-kݹv'xSn܌rGҹMфpq,sJ-{Q^5q]ߋ- +endstream +endobj +677 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 176 /Length 299 >> +stream +xСJqFOh%d`qx`XD{ÂA "-~^r+8Ъm;=fcէx.];sέe-{Nz_|KGs7αLH tWϙ]we+Do]9NZjѫEZjѫEZjѫEZjѫEZjѫ#',&z-1;,.3wg5S w査Tną;td#ٻp.5h%s#5pV|nD}{0:=؞Lx +endstream +endobj +678 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 1148 /Subtype /Image /Type /XObject /Width 891 /Length 15576 >> +stream +xgeְ@W34$ H$ + " (# + IT$(QE $I]Uw5waT +">]lL3NjM9GjW\i_Ie2zv?rxt y+\:?Og|#Gac^x,z>ڻg ҠY:U$hySKL(pϟAUZ+Th۱EbZ >Żѩoa5I\uu佽?k}Yu+bݾqbt?<4Kxs. a DCv}@Kors]z='1u;@8v {N b٥w!9jޏa D3vm +{N bFإ7-9y.a Dv] {N b, ž{һ@LKTs3.a DL {N b޵Kﻰ"4˭"rp'9Iz@٥w#9.=]+$9Ykޯa Dv%='1v˅Ta D@ҐkUzq,]X]Z'žv\@SV`[.&=@s8\t]J/?EtݶJ/HsQc {L bnZ@<Ʀft-ʓkx:tIOa D6U@V&L?='1Kڤls38Mz-x'npͰ"摼6,Mumk.9Y&='1+ڤ;9Mz='1Kۤrsc\)9iW&' +{N b>|&.žwؤ"fF- {N bIŷžڤ7|LsiMzI~Mz=x #6-@-phg.9i&Þ:lқ849&ž{m{|jsoMz߬ {N b ք='19zۤyUsSMz=@?m{Þm709~&Qa DL:49[.8/9q^oNsNmޒÞZZ{N b,{Ѱ"9"ן {N b1,;<#9[.>%9ql7ž痒n@8?Hza DEzz='1U,]μ@-Ύ {N bN6z熇='1fP#9qHa DEz3='1΂n@8=lVW='1v˅]Nsk8q!˙EzK='1~-@8YN"ymEz@8-ZwUsc\,9qֱH=a D3bEz='1ΰW,5]s dba D3`Ez? {N b,K@-˄='1N"]@8]檄='1N_8Ƈ='1NE@8%./cRް"j9/.eM@8u6|"ƩmpzN\t9U8۞69q*Hz\s\^ذ"jP0,.' +'s=8bH;^rr +N/1t9 =@<^$tY-j!=@#/We^IԁUFz.G !0ܧHc'9$=@r#+u@#3:9?0HB5],^7rR?@z.Ge L%tY-\$=@#Z30_ˑuk:]m^IȪzHH ^%_2¸ro& =@#;\t92k`zGˑL4t9!@# 7ʬ ~#=@#>9z[^Az.Gt`zCX.?<0IK#eK}tY-&=@# [ty&!=@#Oġruz]^ߙr\l$=@ra:t9RK`z֐ˑ.7k  0ˑ4]Z3iPH΁}]V˅]HHG9)0ˑY't9Ry]Tr$Gij,t9mp`zUPH[ݞ];ޒr$Fv'=@#Nt9oY-N'=@#Fw%t9v`z;ILoa9tY79۩'RwitY-Bz.G䅸."=@#r&GPzj9rDJ:rD +JoݽrDIe\JPO\.u@#BPz'Q9(=9IljrD Jok]t9"]w W"(;wY,(YH|2(qrD7eH,l< P4/^rYtY-.Iz.GWЅd1I|@@z[žG݇ҋIz2G>Az{/(c(sDj (/cR^t,2'&=@#ұi@zYx@?< gcH刼42 \Y.QA{;316(KITP^$>e6˅trDwHPRZt9"O^c9"O Jo3g(sD~'9",Lz2By9"W9eHOҫ3 +eHLz2Gղr_$=@#Rs@zMy.f׆Okk-zҫ&ڛ˽eHo+}eH|KFz2B﫤(sDrH+(sDJ^ە(sDH;(sD^ѵ(sD +^}(sD2 H奔HPf\veHa=weH Kz2G$㥀z"=@#j@zבqnW';Iz2+'HP7" W =@ޝTp4Mf9=a D޵R]`N +yYNs8Lz2ۇHPTw~%=@|Bz27# !=@ޡyenz9d7ُ(Y.,Kz27~-Mz276enz;*{ImfNhmiN(sUފoIPf\Xenz\ͲzV457Y}ܜި.(s[ʜ^7>mnzK ԛAz27Yd6¶HP׳9MoNwszs#=@Gےenz<`Npۜ(skXƜ(Y. 40&23& 6^s9"=@ġ^enz˛ml fM6~Fz27ǘ[Ӏ67^6>7(s9HPW94hY.Oz27~e޻ԧِenz}gZenz27zלG=IPW.MC#=@rڼOJz27N_enz-271,1>fN +,#=@6k*ڼ&Jz6/fMuCz6+MEz6/[m˅IWaڼ7`hJohҫɔ^DybJoTyiJەhX.l[Az6/|O۾m^zwқӇm^zwқܞm^zv :/{M%D2@^Φv-!=@rabyŏ7gם7Yy;hJ冤h)#=@^CޘHz6/ =L , ?%=@R9hK8bJoEy?fJho=nJI?)_ =@^'M, !=@^aMz6/lL魿m^zY.[\m^z277ڼ2^27iyŎ5wt +, $=@^O&=@^4'=@^uSz+jKϹaJoTzL!@^dSz^!=@rx5wz6M魪@z6?Cz"=@ޝT>Kz6?L_8ϾHz69{tw~$5.]IoHoiq]`Hofw]dHo!O$M75m˅CI=)Hw>!_˒OlvCzOLCzxOt.Cz_u#=@khX.\a q#=@މ<$=@|ēOX~CzSOH!/:OoBkbp!hH:6w~-jH\h;X̐ H槷!-I7t.hX.Dz6?DzһՑm~z?2w!76{fTƐ\ :?[ݔm~z4w, ےOop^Sz<P秷!KH槷!ӑOoGeCz+&=@U mmHz6?;4'=@r!p^(DzOﻪ\mfHo],U7*aHoomu7\m˅d>u~zM˅䚤hXהg8u~zYH槷!H槷nCzOaiIPg\Fz6?R./& u~zkKrH槷!SBz6?#=@7 Lz6?wN&=@]r!Mr%ΗrzII槷!I槷!Bz6?M {k˒Ooy3Cz?Ez6?ʥ\^\ꂗ iʓOaKąd:?/5wUH^ NOv&=@EzʐOoXːDzB;|3,2)Ez6?E ]JEzү'k-8T9΅j)%u˅ӫkx,R6z; u~z Y2o}~`om!-ry1Ooaꂗ 9Iנ`"=@Τ!=@g z{m~zv6K =@']R./"h{J&=@]r!Na5+aJ u~z;Czr$h NOv\Yu~z*^~p /Czc\5 +t7Tm~zHZ6&=@r6_8u~zT7w'?>iH@]pz +ڂӓyH^iNM9BwHP\ =Ow Uz&=@[pzV , [X.Ksc=@^fU,P +|'hX.|OzRKzDMz:mf52йu˅#Bz6 @(, &=@r4Yu~zN*Dzf;uEZ'hX.rPgX?9 ni_r^>aHX*rzm~zsv$=@޼)\ g8,>"D2@?˅|c=i Apzܹ'9 N/5/t=eZ.jK@/tBWx>bH(@ާSNw.~zu2B2@)u~zqW\Bנ *? .h˿u~z r,OoQKr箔+xu˅ hk]4 +.Hz6?m ΀:?%mRN/*!=@{ {9}:b&=@^b)ꂗ =T޲VN&=@ޗ-SN/חBz6?Z'=@S./1u~zM^ۤ ^.]mi\]m~z˛;;u~z+^Bz6?M {[8[8b'=@^[ë-s!=@]r!O9!3,u~zI9{m~zk/Iz6? R./ҤhHB; ^._x!U~z;rP秷ve@ޖZUH槷f:Pm~zSN/Kꂗ  L =_m CzҒOo[Oo{5އ%H]Քˋ:L9I ^.#=@ވ|)6u~z+һm~z{J9Bz6? {OdOUΜryNR5/bO)&~)iHv6/rz ^%cX.3}Mݟ3ڂ 1ɵIBVC{uH{ASz9Hw0@9(h;Z(^ ;fx|Wm~z}3-x I IrԈm~zsһm~zgrۜm~zgKm~zMmoLz6?$=@rNkX8SzmHwp\+@z6?˦8]IoH ӻ֐Vh{,ƐޭΤhX.Jz6?iO/BAmۙmMVY@^L)#=@˔ޤhX.\{m^z{m^zi;@z6/WLmMz6/ M}؉m^z.[܃m^zc.$=@rhҋcJғhvΔ2hqƔh}ʔ.y9aJoQyeoJ³hX.\Az6jΏ$=@^SzjKaSz!=@^_M mOz6/L}4mdM9m˅H?̔ީh+הޯҫ۔ +hҫ˔ޣHWy)&=@^L1m˅SHWYSzǦKfSzu!=@^ T'=@LϧKSzI楗)'=@rHW)I+Szx:K2Sz!=@^/LuEz6/6KLڼ5Y,.$=@l,&=@^OM}1m^z]{ ڼz|lJymJHfiiJoJY,"=@^5&=@ޣoқ3m^z0Ry=>Ք^I7MSzy6 KR}Szk7bz3ڼ3j+ڼbJoPy=;Δ޳=I7b)N\> {~)GKbSzKwbx7ڼ:[m^zқڼ^bJCh{ISz͸|P7y)< PW)Hf\W˅Zm^z17My۔K^\y}Ô^+RR/eJhX.3 y-loJoy-nkJ(y}ڔ^I ://w.u@^¦F%=@ra9y+bJhPϔ^H楷)I楷)I楷)I/rwm˅>WI7:)HVٶHw)K7,YKH)\Hxwm˅VIJFSzMIPw&)Iw>)Jy]dJ/fy]IoJ/3:/'SҫLz6B%y7Wunz+{mnz7Gz67{?"=@^n8(ss4W )-, 21IHP榗1ܧ9 hsK8bL/矤hs+||kv;hNTnee,/<(X.dHOz2 3%%MUfL/ Թb>Venz6,r76w", TenzWenz0w%+-2wmnz,67mCz27RhKz2(hNzWiNXifNoo! |Ӝ(s{usz_!=@w9HPf\0enz ;?enzi@z27#.Gz27 pؖ(s{)sz;RٜHPf\8*:2w 9oidhs9{M.?Kz27F<e6˅M琢~enz+dܥFwmgN(sZӜ.uiNo*,֯!=@3֬'=@&Hz27ߊ3enzG 1enz'Ez27Aq&=@rᓽ(sƜޜHPw#oț(sK17mHLR^y(sDA-&=@#fp@z!=@r?IP戤K Ho 9"yN׍'Gһc9"E~ H29"% Hf9"Y Hoy,Ez2G^"=@#R{s@z6{]@z5i2 <戴2 RIPfi^(Y.4Iz2GRπ"=@#sv@zy/$=@#oz@zv49 HPTo^d,i$ lhsDF^ˤ(sDHJz2G!A{Rys`PzӒi|WPzəHe\\rD^O;w) ە]{ۜ]̓;R]HjEz.G^ LoqWt9<ޑIZ.Jz.G2=|Z[rCz.GJ^r&(sB2@#y}  ["=@#%q%i,0HH=2]tޠrׇ%r '̑"U[]V˅y{IHGz.GL<t92*10{Fˑqn"ymH`z.#=@# +LoZ.>Ez.GT Loğrd^}Kz.GLr˖U]'0IH|]V˅HȠ =@#? +L8W9T80kHȉ<%, =@#Wy4u`zHe\(Lz.GfOz2'zpzHPd4a DtpyZ>]Nތ {N b;9Z.Hr"In^"Ʃ"#Þ* {N b_X"cstoa DSEz7[='1v˅žF;{N b'_HoK˰"ƱxȔa D8"J='1΄g,sOs4lޟž©Þ{5Os|"Þgq[@8˛Y7As㬯o޲~a D$m'"{? {N b ;='1l29qH/qZs"ÞrVY@s'EzS_ {N b~ɒa Draް"&X {N bZ^Oa DL6~=@W=6\sS[ {N bvIo"n♰"rO=@m^a DLlis3  {N bFؼhHZo{N bUIͰ"nvsps$='1>bs'9"fq{J{N b[>@ܛ&b Y.^y%=@BV{='1sY~scp49IK{=9b^"n a Dsu.=޹k"f]zGžuv +{N bڥw99\.@ K\s]zNyԪyǏ;!q>5qʁٚ8ػ1ݻw\۾:-[6oZ8uݺk$ͪU+I|/\AK, Ays}/q~V{i/Ϝ9cNqOڸė'ʄcƍ{yD@јL(=6nXn4Zr%>-e?to|K7'Z7:qcG=H>7-ĐO l% XkCl*=wzw}жͽHfM4.&_nqYv%$Kʕ**_/YxxI_HB9$C3KܹrH'qٳe͒VdΔ1CJ.m\qbӤIcJ:%&.m:CL$.G\3H<'d̅ +)]*]ldXrUN$oF­mӶԥk܃zT:|-7"?Gɕm&cwaXyc'g_8K^q<ٝ|w$Mwnߺ,|#dƉc6TFٱmK?E zKwg͜IjQͤ+zv.)ܢ=w˗)]2̖9˿+V\Dʾ._K/n]t>|Ё}e-I_,2fiofƏ#xXڣkҨY|ˤI%2sONJI6NReɚ-[&I'o.RD>PLKΪժ'֫w oܤi6/9ܿ7HN=9IG%iMʿ67JӦ$%yϙ;oT^EC%KxD:-_ry 6o:A~]S={!utioO0#e)=wdK?VW^qNRIwޒoݼ1JF]v+˗gH .\@\8{̙3gޖg<}jp^%e/ݎ;vl*m?g/J?<)O?FKowץ_ttǝ&v<1A.#ܩ_'ݯY3Qw;T򥋯3ϝ;;ZƝ=geӧN%ʛo4Fq$gN:̙O/uz^rNrrrڕK=*;Oòb?}@ٲq7<4W'4Ç [UvhݼaQRt@<2HlqnK6cV +/ZTUaٺ}JGI>1q2S_C֬'~kOe#N헯޸ONܾqb69xı#_',ټi2cşQs>a>3sə ^V?;|qg@}IwQoެRI{JjV+ﺫ|Rx"b$>O|1%kL݌cӤHL>FRIjc$6cƌ$6k֬YK,Ye NΜ9sfX%˓'OL)!Pጒd2eI*U%%իw)ۤiJu((+zcsJAO>54 1b dq'dyuʴSӥs?H|hϝmX:vvIO[\slt>Y];Ȭ߬Lɜ)o61<1j2I'-۶n$TQR)7W6S?Aj']&O"%K+T]^tRսm)d} A=ɳƿti3*hܼ2i%Kȫ+W^SXlٺm{Yg;Sv8yLyK%d͛nאIw*˷׮]RDv?lsԉ#Z87AܷK{]Q>+*nݺ5,tws6_C]vm>x͚VZUZ^u'?ի׸u 6w6o޲t2gǎ"OT@}?'Ȋ,"9z8qd%+]y|wrRYuƵe+&8~H{vQNf镓i+e>ysʔޛVZyu^Ǝ5BdӃ'}]%u۶TM7H*)R@<"Y3g5Oشiӥɘ5k15W> +stream +xݯ.v@ghc'6'I +ͦ8f&AFkӰq |=mI$I$I$IC Fgr03/NOZ4[˹ x^Zr,M΂BeFgI3H%EWtIs[P4X2 iLe,`4X2 iLe,`4X2 iLe,`4X2 iLe,`4X2 iLe,`4X2 iLe,`4X4+K^Yi$Rʒ"iƳWIӼdϬh68{fAP=Xf1{i9SW[K_i{vAjڃg$I$I$Is: +endstream +endobj +680 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 111 /Subtype /Image /Type /XObject /Width 40 /Length 183 >> +stream +x1 +@%GJ4,ϣXB,*WA, X#Oƙi`eY(Idn<]%s B*`+qwrmZ]ݺ+xʻ%.ޝ]FEJڝ:w$YO)(wnOmkz_MOs'tӾ_lN{iO~9˲|O# +endstream +endobj +681 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 115 /Subtype /Image /Type /XObject /Width 40 /Length 190 >> +stream +xҡ +`G0h 1i7"‚5Bӎ9?_p.> +stream +xҡ +`G0h 1i7"‚5Bӎ9?_p.> +stream +xС +Q>i6m"A b&_IA0m {deΟ?8gY/J enW*b J ,>6[j\ɲu9nɹwgexnѺ9N;,Żrঔ;՝n'9鴧}؜Ӟbse> +endstream +endobj +684 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 112 /Subtype /Image /Type /XObject /Width 40 /Length 191 >> +stream +xҭ +`.fвfx=Z A Ӓ$ fc9_u> +stream +xС +QamA6h 1i$ ܶ`|=^kqfpΟ?YDFC:w5w*/MֲWg]ɭ[JY`^e[nλS,K.̻)wu绛1 0υ_l.B/6z, +endstream +endobj +686 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 113 /Subtype /Image /Type /XObject /Width 40 /Length 185 >> +stream +xС +Q>i6m"A b&_IA0m {d;`eY(Idn4]!s \ +`+q74nͻq%zxʻ%.ޝUFE;HR͂Rܞ:[Ww >8NӞbsӾ_lN{Y{ +endstream +endobj +687 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 157 /Subtype /Image /Type /XObject /Width 363 /Length 77 >> +stream +x nH@\ޟ +endstream +endobj +688 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 117 /Subtype /Image /Type /XObject /Width 275 /Length 879 >> +stream +xKq$!FTt".:AuDA( Ё#]Yݦ QAX`E$fk9;vfgާ}??;Ȳ~Acq|E:#_ߪxEdfE$kP>F&z+"D=IC/IޡWM耵XYMR&"!~R]Zو+ Fkmz=(B/%Ao$qh?I?L44uZYdsMnoZ?}.fަo+nO{bjNͯH+Ȥ;|)nwg@ RJ)RJ$@L +endstream +endobj +689 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 152 /Subtype /Image /Type /XObject /Width 164 /Length 847 >> +stream +xOHau-%[T:DF+FDQVPD)TDA]JHKu #5:UdAQa`(Jf>5nwvgna7ͦiii?W@cGU !;B^ ? C;$&ץ`!ecIvgטkE잩f/Sv,e'EIG)nD'~sߏj<.2qJ;d&3q/;\fėXܾPc%#%r~39f 2v#ʃ[g%{Lv-pc@F1Wuvh?1c&@G`9;*>fWw.:r8BvL|6vե)Yv#$)!sN8';@nvL(F<19B8t+> +stream +xkq H(4$3YH7+?Jʯ\H[$J!V +˭bsH[jXjsb;ξg .ޟ9;/zs{vs#""""""""""""""""C< +[aԼZc!\/K.Xм&P>Š#r+5>;TwR|bRyXTF,UR#B/7gzTѳMz%hB9]jzU'u̳|]zY*UlYss dG|>G]豶uyzkZ ԥ&gk;Nu=պ7T|PeQLW:I"UNgH*U5z&UB$U*^JUI"gU֡gH*L9JUIQJ}Eh?d.U+z)8]zNԕrԤL=ԺPr/K/v}׿2zuIΕ*_@5ʹ赦e)T<BK >j{ ICsfZTxw~`/7UImRRz|K9W^mR\jSuKȻ"Tnf=ܠ̢یmTʹƴF;eMy&@7$&i[qk +#&Jr}6$[$k#~.幖r!-ξ=3;RιҒ!-.ri^Beͻw> +stream +xKTa'-K+1DɌ@ PZIPD-E.r-$VF" + F [bMC8W9w/s{|>笌%R>xw9:>. y*/"j4c/5ȿ*w<+p9_ }po|ERd4|FvESVXX0ikŸ́KvH/>/aШ#_]?JT]O ޷D)0P `[*F~P5b‚~&透"PL[4G+8&B?q}o/@s@KiwA) OZW@3qR4~Q7,}*;^-ržmkK.ЯAX{SơK: uħ|ߞk +o6#>B3Yl?Kk8,3kO_6Ez4wRL{}^hPm H^;pFw@Ou~a' ]0 Ukt~s{rovRovr|>9E +endstream +endobj +692 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 127 /Subtype /Image /Type /XObject /Width 201 /Length 920 >> +stream +xmHSQt-4,,RHȐB4ԩ}0^C%D QAP`dFR|L3ĹN]}=p>;$AAAAbyU޶L/OY.&ksxbȄ?JvbU.+R;: 1B?]Ƶ9ݤM=p$H-SݰT3>w0vWNQoas;HN#ov Ֆts#Leݣ^ r]S54V/'Q9I(S> +stream +xСJqFOh%d`qx`XD{ÂA "-~^r+8Ъm;=fcէx.];sέe-{Nz_|KGs7αLH tWϙ]we+Do]9NZjѫEZjѫEZjѫEZjѫEZjѫ#',&z-1;,.3wg5S w査Tną;td#ٻp.5h%s#5pV|nD}{0:=؞Lx +endstream +endobj +694 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 97 /Subtype /Image /Type /XObject /Width 176 /Length 299 >> +stream +xСJqFOh%d`qx`XD{ÂA "-~^r+8Ъm;=fcէx.];sέe-{Nz_|KGs7αLH tWϙ]we+Do]9NZjѫEZjѫEZjѫEZjѫEZjѫ#',&z-1;,.3wg5S w査Tną;td#ٻp.5h%s#5pV|nD}{0:=؞Lx +endstream +endobj +695 0 obj +<< /BaseFont /AAAAAA+TimesNewRomanPS-BoldMT /CIDSystemInfo << /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> /CIDToGIDMap /Identity /DW 500 /FontDescriptor 734 0 R /Subtype /CIDFontType2 /Type /Font /W [ 0 [ 777.832 0 0 250 ] 14 [ 569.824 ] 36 39 722.168 43 [ 777.832 ] 51 [ 610.84 777.832 722.168 ] 70 [ 443.848 556.152 443.848 333.008 ] 76 [ 277.832 0 556.152 277.832 0 556.152 ] 85 [ 443.848 389.16 333.008 556.152 ] 149 [ 548.828 ] 533 [ 526.856 ] ] >> +endobj +696 0 obj +<< /Filter /FlateDecode /Length 335 >> +stream +x]Kn0>"1&$BJHXZ*2΂Wxh*uao0fEuN`z^ tW +vh,aEuPn$YF)^OtsR9NnnE%~X O9s#xik3e!mW)0^yw+꿈#Tӎ +&۴8Û{Y6 <풵[[ +endstream +endobj +697 0 obj +<< /Filter /FlateDecode /N 3 /Length 293 >> +stream +x}JԂ(28hiRpi"VSAOHStspu+.c(#ARx@?F[V[G@`*dK$O.K o@6`O,f'˘O asx0A6vf 8{c7%opZ:u Q 0Q٣F *Ԑ(SDGACAajgrx]s PxM cvhO> +stream +xԱ*`b,d÷H . e(k0+r6"%xvx{~WtJ$I$Inl~DK3ÿO\~5h>=-F;Skԍ߲a(?Y}${e#앵rZ쑵Z Gj       =VzZuNZM#+1ua:.{gc^'2w]LGF/$I$I}B  +endstream +endobj +699 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 40 /Subtype /Image /Type /XObject /Width 141 /Length 361 >> +stream +xc``Q0 +h H=YG`p?Rh M;.thdfp(+.HMN +rwu271TWQgtLOVRӀ ^x7^pԉ]t I7qʴs-\|lߵgO=[w> +stream +xMAEG|&R"}8Dz9;'2lOwUz=`z%I$I$I$I ..')\PJ)p2zk>l_GӘrr_$zCϣ'bs[MT&<\=~R&vߓMG&Gf.#S` cOt1د~d~dR?2M #uG&Aԫ!#1X~6ՏƠEc ZՏŠ]Y zԏPŠOO ՏBE lԏNHp ,ՏVH` ՏDCHH |ԏKx_#c~dX Տ#b0J{ ƩX#1~-Տ8 BG ԏ Nq "ՏX ZY ՏŀAI 8ԏǀGHg ԏtŀKH{ ԏ4ƀQHS 8Տ4ĀU0Yn#+b~:7ѓpSh_lKͳEOu-\W=K n߹WgY6??L?*cft`A ㌁ T拁4a!x∁Ӂ<\=c}6TLYl3>u5FL>o27Ltwh!E{.$ī=3NzןFݚ;Qc~DwhһڵwKߍP9ԏE}?w6iߵ%}G̪~DNB$}Gāӟ/?Ồe-z$I$I$I$I?t# +endstream +endobj +701 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 40 /Subtype /Image /Type /XObject /Width 141 /Length 357 >> +stream +xc``Q0 +h H=G`vTS&vN]lSTQ741wry |8:.4:&N6sμK\~?t/]qGO_~Wt8tHp1>x޾q҅N=7n 1ue QA^NV&F:*2b"|< %7rj{.4k@\ᘗ> +stream +x}LMqߟ!n^=/kViMcFE 4  VMۼ(DY6fP֖W?;tO߹JI$I$I$I$I$I$ٛdխ{VG ĥKUG(tKTu;+ tk↮í+ɨΚg5?ܫpx{N)f{U;^O7) Óf,uyYxm燷zǓcDP?d4?~Ih:_6??pz8Q,n-cR~K77JOkЅ01 B?UпJ ~{rDZד]$$HHG(k}Mb *?0 +?OtIl|8F篦OP(lr^):vs:7ZLjgN?dcwפ3@v>a!ѯxa:W:lЅG`<Dۄ|6p;P??xH^*{j/, 0?OW V +RW6^c6?6H-">Jp[Vzm"> 귟!ϡ+A<ԨkTLz}gqoI}Kx/11+bOPLgC#|&㽑G\4P_?Wŏ,_Jk+맑"5;|޶V:Piy7<+,"v]l\q #[i)2/> +stream +x/wq‚^YXX,d{, /bxe0`XaaŰpŠa ‚b;xoYr{|yr~8ρdsz~9õt~qsz=άp--qkzڞ{ki1?06u8.V}GBBBBbc6V}؋.gW}}PPPPPPPPPPPPPmƶp_+T+T+T+T+T+T+T+T+T+T+T+T+T+l[m) + + + + + + + + + + + + + +hl[ +BBBBBBBBBBBBB¶ۖ}PPPPPPPPPPPPPmƶp_+T+T+T+T+T+T+T+T+T+T+T+T+T+l[m) + + + + + + + + + + + + + +hl[ +BBBBBBBb?U;ٹ7\t¹_Wq>tMڭqfkn͇vc|U۞ix{>ŻOa4<9p?=|˯ܟ䟧}q6 ̫]^s:: V}]O +endstream +endobj +704 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 134 /Subtype /Image /Type /XObject /Width 236 /Length 359 >> +stream +xܡJqb aalCe {u`6 +ɨK*xgxS +bh{tᏱq.m\|4lF[R c)ZuW渏Wx+s"ze%Zi5G+hVwVZJ;ZiuG+hVwVZJ;ZiuG+hVwVZJ;ZiuGmTH-9";9n#e*zaj]PMrZK07z6íezU ^g尣<~{wP{ +endstream +endobj +705 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 204 /Subtype /Image /Type /XObject /Width 255 /Length 721 >> +stream +x?Hqq#"hpH#!0.AE@HX'$Ln0!)! Z +$$F?z.|y~ޯ>ޏr?`uv_hkminl)sx@CF'i)7af_|W/_<;ǧ&'  nK2 F%Zî}Elal҃C#cSMnxj:^F]vJElQrR7^S[Wvs:5s3xD>'_+|OO>'_"|Y:@ N{|eO,ɗE>YuV>"|Y/|e5:}ɗE>'_g jXu*/|eO,ɗ%Ϫ԰T'_"|Y/Kί"v~foV +?n׬ щ$>ݭ[s#s)Glхx6!aY<|3|seu([F_o2Y\A|IZU3?QJǼSs+C/ +g~/<:Y8ӟwtx?9\G aow/ kD'GJ +endstream +endobj +706 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 146 /Subtype /Image /Type /XObject /Width 214 /Length 363 >> +stream +xܱ*pE: [P&)ɦ8vrGhbt.@9eT&aB\o<}nmM$I$[ \S q=4Z=7s4:ޚqZx^ҏU]WfgfmDWfF6Wf"UY7aʬ{0Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8Ⰺ*8BU+n#UYgYGQ=3)Ϋg> +stream +xc``Q0 +F@ʍy}]ץa?n`i" `='5.Bfe +$a_]^. +6&rRbBd.zT  ݫg޼z̉#`p̈́h5j 6bDSYKv ϊh9tL:seo޾;,D8sͻz*` f;F]G+$&%m`ba#R =2A :v*0Arܑ"Łv߯blog vE +@ʍe0 +F(` 4 +endstream +endobj +708 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 99 /Subtype /Image /Type /XObject /Width 40 /Length 50 >> +stream +xѱ 1Fgs\A+H>'RC7ۦxuO +endstream +endobj +709 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 296 /Subtype /Image /Type /XObject /Width 124 /Length 1272 >> +stream +x{Le/+( &jvʒ?*IbhZd66oeDJj xҌmJYr{ =y~i*8la4xX0u 0=mP>0N{н8>SMg?seu,t{41Žy<*Ut +_@Lm+["o=rXqMȖ~ؓl9S [lphNJ{+h A"D'xAChNPCk/#3Z8KBG mk =Щ@6+8`Bcl +TtpR8]dE}._Tu'MDO1";hJdW"h 0}ЦANkDJ(fnپ)Vпͦ WU"o*;v7`> -f]D[1<@VD$+ix[eo<)S OZn*;>Vه|]e|)t- { T#&Tt/hVʎZϨAФcXVX+hʿclY;*{*|l]qcd\P*W0Ie|/[c*4*{z*pȶպuv4F^_ *;jUQupȶU4fRb~)-*{7 &TT`΄*lVST.آTvPr T7 +7^5~%"f ++I2ڧUr|Ù,o--}3^%z{5pn} *.̳wO\QǶ-TH'/vs~my|t~-\۩ܖ[zqi*/Q9nvz^~2퓕C.ot۬د-iEʰqV_}wn[.AzC]%M=mцcEG_tUء&|>O i01 +endstream +endobj +710 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 303 /Subtype /Image /Type /XObject /Width 133 /Length 1312 >> +stream +x{LuIr3Q#PH TުVYblYK3rkY#k+KZcMs) ]z Qokr~,˲,_Իo$ Ksdq9{N7^f] z&fD`pEO+"e_lR!^^ nḁ#|WDnns('/j8 `T^GhAK'⍮w<9oEW_!ےF b՟dS Vn~p˸[|VX[y"gEX 'jf?&H]LJD"/eMRݽ,%YXAVU#jYO}[t -CmdS1K Ap(&G "T0,ȍR 5H?j'FS8kh6fڀZY?X!=Z6S#cy\ VHC&\6`6٤6঳"[]7&Qзvxr9LmOV-uAXٛ NP,WXm`WooYܩ6 7 rgyGH-+j290r'SH3E{'D_XVX#U ޼Dwg3rQ0<6` ' +o'9Wm[$kd`Ya\_m@Y6 1X6XVXcYjr#jeuO G|WmfjeuԵ]MS˭l]6cߑq3/B`TjAlԐ, ;jd/&,*,&t\jg| {cz,ʛGTHV#7Vvܙj +]Xoz2{;7F(~z f+R~Z=jC~Y56xEVg}k{2j!6_ MЯf䞷:vɽASj(́MV xBjua@3ln*ɷ형/&)JaĮU h~Y3H(Yӵ﵀cՆ:2Fc4탩*H?+@nSz_I@N*ɟ+^eYeYVF +endstream +endobj +711 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 100 /Subtype /Image /Type /XObject /Width 172 /Length 40 >> +stream +x  Om7C0 +endstream +endobj +712 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 40 /Subtype /Image /Type /XObject /Width 181 /Length 328 >> +stream +xc``Q0 +F(hZ1H1LvdhWbL/ցv)X1n!k"y9āǖDv ?'KTCQD0'#%!:< "h vVfF2"d9ɑh4tDgTv3#Plj<}%+lܺs7Dp? +> +stream +x1NQajW`Gr 5**{k +6`yNr'y<&X˺.ϻ|_:4xKcOg4P qp(>]Nܟgpvr8 @c И+\ 1s)<GScИ@~ +43PQcp(XgPcphXgaujlhhXgP@C @а@E @: 443Pcа@E @` @C @au +hhXga @: 56  @:56 3Pcа@C @:56 ujl. 56 u*jl(Xgau*jl. 44\ jl. T43P@C @: 56 T4\ +hh@44\ hXg`ujlh@3Pcphh@43PQcа@: 56 443PQcp(XgPcphXgaujlhhXgP@C @а@E @: 443Pcа@E @` @C @au +hhXga @: 56  @:56 3Pcа@C @:56 ujl. 56 u*jl(Xgau*jl. 44\ jl. T43P@C @: 56 T4\ +hh@44\ hXg`ujlh@3Pcphh@43PQcа@: 56 443PQcp(XgPcphXgaujlhhXgP@C @а@E @: 443Pcа@E @` @C @au +hhXga @: 56  @:56 3Pcа@C @:56 ujl. 56 u*jl(Xgau*jl. 44\ jl. T43P@C @: 56 T4\ +hh@44\ hXg`ujlh@3Pcphh@43PQcа@: 56 443PQcp(XgPcphXgaujlhhXgP@C @а@E @: 443Pcа@E @` @C @au +hhXga @: 56  @:56 3Pcа@C @:56 ujl. 56 u*jl(Xgau*jl. 44\ jl. T43P@C @: 56 T4\ +hh@44\ h|?/۶mx޶i_Saß1<9jY7˲|< 0h +endstream +endobj +714 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 151 /Subtype /Image /Type /XObject /Width 196 /Length 52 >> +stream +x  Om7s +endstream +endobj +715 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 88 /Subtype /Image /Type /XObject /Width 40 /Length 213 >> +stream +xҡPqhp6LشY`iݦI6ǰiDЮCDy9DϿl_dY<7b)0#g03vɌ4s xէ6wk% 6;G6j.R2lU.*]`6:WJª xgܲ\Pw rX?\!\䘵Ӯ+N״' +endstream +endobj +716 0 obj +<< /BitsPerComponent 8 /ColorSpace /DeviceGray /Filter /FlateDecode /Height 122 /Subtype /Image /Type /XObject /Width 202 /Length 167 >> +stream +xܱ@AيL\q|`i(#I` ƎwB8'%KI%=Jz(QңGI%=Jz(QңGI%=Jz(QңGI%=Jz(QңGI%=Jz(QңJWN\yw}RoRN +endstream +endobj +717 0 obj +<< /Filter /FlateDecode /Length1 1263 /Length2 1089 /Length3 0 /Length 1823 >> +stream +xڥT TgVJQ(tQd&RBc:I&@ EV@E( -"a(ly UwU!,Uy] NXsz9{wcn+dJ'p Ac#10Jr0w +#pk![Ih;Bё|jؖg $xcb9*PJaPƑLN;JbѨHI")M\<}$:CZ{6,hԛ`RL<Å(xb+I/?#0a&L7a&M!@[O2X)JcZ ^h' BZ<}i_,4l%"#Q LL*p[* NHPH,~v4o +~ +A"Q7͉FĂx-09-pf{;0?/H +#,Q+HGq>d>f>C [@\HL/=i|jbF -(Dlqkc ډVGejB,uf2 +0wֈX,*(H +T`8K0Mi cUwօ]O׎1 z9\.Fgh,] JE i`NIF]+:ޯI\fg]+x/lCǔZݑBw/1RsYyMHEVu~B徕o̺ t"EZwV8*})1kT}MŴRa1gɀBSs.OPXQX"zx0>buXqe'Q>>mq>Dx7:q)߰e[,){rcߏ^6Jy99;=dy8HBy<<Zb4XP=[JsEkΧogUtE珟U3O"QX=)% Դ3e^O>aUOfL]^0X%_6O[o&{ǻF~rY_Hњnc3&6#싵.F@RFdrn'N7C\:ZTVtJ-Lfӛkv-iz2Yju +ǼB{)5 O1 *Wh:蜷[3cy +ӽ.Ή~ш ǑmJdcSyFGD>(Pd,n+2H%:h˙wLJ?4b['Ls-|U4QV +Nek*z5qϻ(aVɺù^jC%,hd5ugTmL׋ѰPYuxQOiHnj[0ԉTn}8UsNuᑆZD$Ϙ> +stream +xڥS TVTVÊΠ")cC1ABG 1L+CD\^(WAD_ȑ3.IPXNr̀bDpGs*#AH~RGg0L%ł9XepB3i4"2bA.f$"B _BJBH+sƣR`P."QhR21`C{ v@da)w_andӽ9Q{:ngowiNNxZZni `  堿Hރoo0eFżqmUC!%xLoF݋-Z$cN6\X 7N*-(y~?D-H>вy6GL BQ|Q+|HǷaR7>7:nX>vxv1ĶRE-huuMt?6,KsVj|Vvz KO}YTL _ٻvEQyUp^,B/6k%/C|rw՚]_T`_ѠDcKDv{V3,Y klW4Wឺ?^4mx|m>ipDګ|_:iU飆ʒj.Cye76xAYL%-\GeGØθYͤN&x%G ++=8VCkGzW%u:Ӊ}wi6 +nawX/CթLuv}m*p6kt.Dl1IS k_TdDu4]<놁wJ ~n亊o6?Вvn~Cɟ<3*>~L?o²ckw*2G'MM, وqŃ}@L֘)ne`1+E~hRͦ|PvgʳW}۷=o/\5qݟlc=}*63 GF^az F[e܈*F$.ԁҳ*&R+#Y̌Z~wDeO՗?y_H67H.)d#9OMCec뛻gGSM-A9;79{~Ry#,9Pf+ʸGukS+mE۸c,cL1&:}>FwTo2:jaыf[WO7N7M#-yPMS.}Fs?@'u@gE"ta-[YUI5gTO^`hfwcA|AB-VbXg|w +endstream +endobj +719 0 obj +<< /Filter /FlateDecode /Length1 883 /Length2 1288 /Length3 0 /Length 1908 >> +stream +x}R{<NN.#~dTf޹MqiC;e}y'3rlI-J d[ݶlmsӮ6$%!ۊDvڭ|~Z}u`T2V \ ϷVa P<&yv*CTPLi4F}OĤ*`10 VJsk'w# +x(,Ui @(ѩ @x4B D (SlTgQ&(.#, >%P0,|WsxQ` AT}BT*VAI`@ +b{y`9Ca0 c.1 +1ޯ@xH|lB>a@4Oyrq[yD?@x /~7#|W4B<,"8_< ~@T0!sl /BQ(,w +>1FA#R +^UفTVΦQ Wh9TC FL.i"d a>~/JU;TBD +`>A _'ɋf= *r !EE^x$Ќqz)wm9jf('VYhB}Z_F*Ӵd~7eg^Y}_"f8eo_"m߾S /|Ļ615ݡȄ}:+ᄇ^NQQaؽjDO~y8,ЁYsmJT\rml1R`1Oz< Dڳ{h] fX;%ZtVvHR(q7ݯ-oxZ6:`)i;; "YѣUX*1mA֧/]KyciEK5O3 fLg#7ǜq]Nv({G^(mG~S s~t wpa߈u!_?ְH Tѫxq~+|@T˘-avB#aG.`k^|dڼnYc"L=TsqP64v{WTҽ77f(QDm;U/Y&QL;v?\jcv8 v_W%T^s#CQs>smV 3S+*KK>S&v[gƀV QY=)]ƨ]Y:iK܃[븆sSc̲Z#:%*i#>)¢9""4cdWvj:Xmp*0_ 5i_nCM41۹LzadsӃCnk&e*\.NMʽ۪U]vT<Xd:n43.~9A*mvN&f՛{JS#K ]^f̰F5D}%Fͩ%MSv_ءnUl˶/Qn[75Z1۪S_w8/}ݜGH\å3g.3'?Edt<ΘtV|{-'3"nד1̖,[1Vk,P;۔QMwxe-WF`3|UurB~NƖ%JPw\@YZBSQ:'FI`q,l~)s~/ +endstream +endobj +720 0 obj +<< /D (subsection.3.2) /S /GoTo >> +endobj +721 0 obj + +endobj +722 0 obj +<< /D (subsection.3.3) /S /GoTo >> +endobj +723 0 obj + +endobj +724 0 obj +<< /Ascent 891.113 /CapHeight 662.109 /Descent -216.309 /Flags 6 /FontBBox [ -558.106 -327.637 2000 1055.66 ] /FontFile2 735 0 R /FontName /AAAAAA+TimesNewRomanPS-BoldMT /ItalicAngle 0 /StemV 83.9844 /Type /FontDescriptor >> +endobj +725 0 obj +<< /Ascent 891.113 /CapHeight 662.109 /Descent -216.309 /Flags 6 /FontBBox [ -568.359 -306.641 2045.9 1039.55 ] /FontFile2 736 0 R /FontName /BAAAAA+TimesNewRomanPSMT /ItalicAngle 0 /StemV 61.0352 /Type /FontDescriptor >> +endobj +726 0 obj +<< /Ordering (Identity) /Registry (Adobe) /Supplement 0 >> +endobj +727 0 obj +<< /Ascent 1039 /AvgWidth 400 /CapHeight 662 /Descent -306 /Flags 34 /FontBBox [ -568 -306 2045 1039 ] /FontFamily (Times New Roman) /FontFile2 737 0 R /FontName /AUWCNV+TimesNewRomanPSMT /FontStretch /Normal /FontWeight 400 /ItalicAngle 0 /MaxWidth 2000 /MissingWidth 777 /StemV 56 /Type /FontDescriptor /XHeight 447 >> +endobj +728 0 obj +<< /Ascent 1055 /AvgWidth 426 /CapHeight 662 /Descent -327 /Flags 34 /FontBBox [ -558 -327 2000 1055 ] /FontFamily (Times New Roman) /FontFile2 738 0 R /FontName /ESHXQI+TimesNewRomanPS-BoldMT /FontStretch /Normal /FontWeight 700 /ItalicAngle 0 /MaxWidth 2000 /MissingWidth 777 /StemV 60 /Type /FontDescriptor /XHeight 456 >> +endobj +729 0 obj +<< /Ascent 891.113 /CapHeight 662.109 /Descent -216.309 /Flags 6 /FontBBox [ -558.106 -327.637 2000 1055.66 ] /FontFile2 739 0 R /FontName /AAAAAA+TimesNewRomanPS-BoldMT /ItalicAngle 0 /StemV 83.9844 /Type /FontDescriptor >> +endobj +730 0 obj +<< /Ascent 1058.11 /CapHeight 756.348 /Descent -261.719 /Flags 4 /FontBBox [ -113.281 -261.719 1335.94 1058.11 ] /FontFile2 740 0 R /FontName /BAAAAA+MicrosoftYaHei-Bold /ItalicAngle 0 /StemV 83.9844 /Type /FontDescriptor >> +endobj +731 0 obj +<< /Ascent 891.113 /CapHeight 662.109 /Descent -216.309 /Flags 6 /FontBBox [ -568.359 -306.641 2045.9 1039.55 ] /FontFile2 741 0 R /FontName /CAAAAA+TimesNewRomanPSMT /ItalicAngle 0 /StemV 61.0352 /Type /FontDescriptor >> +endobj +732 0 obj +<< /Ascent 717 /CapHeight 683 /Descent -218 /Flags 68 /FontBBox [ -39 -218 1051 717 ] /FontFile2 742 0 R /FontName /DAAAAA+KaTeX_Math-Italic /ItalicAngle 0 /StemV 156 /Type /FontDescriptor >> +endobj +733 0 obj +<< /Ascent 951 /CapHeight 686 /Descent -267 /Flags 4 /FontBBox [ -542 -267 2061 951 ] /FontFile2 743 0 R /FontName /EAAAAA+KaTeX_Main-Bold /ItalicAngle 0 /StemV 188 /Type /FontDescriptor >> +endobj +734 0 obj +<< /Ascent 891.113 /CapHeight 662.109 /Descent -216.309 /Flags 6 /FontBBox [ -558.106 -327.637 2000 1055.66 ] /FontFile2 744 0 R /FontName /AAAAAA+TimesNewRomanPS-BoldMT /ItalicAngle 0 /StemV 83.9844 /Type /FontDescriptor >> +endobj +735 0 obj +<< /Filter /FlateDecode /Length1 31500 /Length 20139 >> +stream +xy|TE7۝tҷ,Cgl@ (jU6!!,㊣2㌊8#:ntpɸ8[Atfy3S9uj?u/ ig}=}vk{XnyQ6 ykVV}_=_P @74?s9ٺ"ہGꩋ_pu3 u̙es@6^h, U^qlmHϲ4RWЧ`΅=5| &Z#ճoS zB` k+fYoYumh@! V4Ԡ`PuZp52ꙥPe϶);?÷.7kЭƖGN} Lȶa!5:U)S)3^txWXؽ)e@GRfD6TxAz\C)hB q&X5{OLtEfc)V?/ySyyK+i%ח]0ܭݬݬ;߿MLqUAkP#/Jfy.Z0gY2OSƪVUG?~WW0?y0Nʞ:COdDO$|]>Lca*i>:D5cT:u1ޯQd)m&*=@84xN#8X6P\ 7JFp Jb\T,Zs8h +,|cD2_Ç?ߏtt+ +ЃNC!ڱ+ЇXU(:>Q13 _JC V`%z8FY q>aJM`+fXX9RiJ1d^S0 +>, +̓=X'A9*Cd%J9`z>LTjuIXEJ{NjW/S/,-B=x_,\,Ss]nj#5)JS{b‡XJdJ,V-Ww~"`) YBSFbSUX>Ig͐׏[SUYS++-D¡`yβl2:Fb-8ۗt'Uĉ<KFН%'I1hIi/_$tRzԗڂ˭A_̜%k v +ޮKK|6V_tڒ,ZZBv -0[-$]@ЙKKڒֶdN ms';wzҒ$iD9i*"hQIjZZ%b^\Q2gissfu&ٜ.-t[OEKKΫ~Mm>ݴ*_rsvuKKJK4<{ӄ4gKK&ՙWtu&]%>^^L8{/6mZ=Ǘݔ\i|mM3:d'55oG66?G攖la &'y +sl'[OOJI<_gt4\ZlW󧋔LNΟٶ8o$t> Aߦ$/GSP4akpC-IhmKRkРīJK֤L9]ccwi sKK ;3qzRbѮ$朁Yd!r$u3m$q3mi3F2ړ,d2)]A&Ph4yi "JF(UtBZyi]!#]8"ü9A 03-3:<IU +SȟOP/Epx|e$y¸!|x22tBE%uL&"11c;s2Ip +'sl#ɟa# \H(2Q>!efJ,1HP~*[1ɟg`f52+t,ǥw1"WZFdBv|jO{!`9n|I Wȋ +T`9NGܤJB`;+8L5 d;wz$WS/؄GRAjȅb2@>4H;\]3y|!X%)#x \#1RCI@s,vJRݤz@Mk)G;ڑB\KFO]ˆxI'HMKf+[rIDaO?{3Ubng/wUk ?1|X6QB,&^ChL,\~{^ƛx 'ErHH!d-Dn$wghh1,z ,Ț؅f:NNWV҆u/ua /ix\ ,B*ьs0U<᧸ ۱)sx U{JyQ aP&j#B%dp佘\F![VrF^'o#5-M4:vsi&l:n҇CO?1٘ +X;]6${TUKUR'OWVS_C[ԇԲF͝SA[^v=+ >N<{ɹLd~E` a|ỰKPZ2D+.Dx pI%oQwxL!C䧤M/#RKWD7a''8Y)W GZdsd D"I܅#AX:Gdx\KLj2,؆`s>1zaf`9eZY- \7:J= LVNQfl*݂ Xx*ʸ]p@"ba{`1?)r~mzcr?7O-:ˣ$GjΨ%-#Q3F(!p4[yzahQ &yfZ``Ul +C@lȆ]Or{>R{-\A+ȓ ?S^@ |/0 (oP A1 +oE JP$C)c(CT>Q"1(Feר@L( +c䣨F\>֢B>:TG1?0N6bwH'M +lFw`7A(  ih`OG|*dG0E T&4..tL,38W8 Sp6Ƀ8Gх fLyLK K$p%Sl#nt_`/0W0S{YX9*|'E-Ř-%#+e' 0O<ɟ)S|XE_ W~s$TkL+\uX.#+K?ƥ?z?a#ȇpʇS^ CJ\,x5.?5!6RC\6:l? +`3.?q|PR>_* ~僸 p36p `ou؊ 7 + 6v\~ /7x7n=E~bSpVy?xPow. +LH;WCK~Ľ;x}>}ح=x@~Am?ocIm;BJ~ O_~ Ϡ_~aM ;7 +|ix?% +xL~{^xx_ǟ&_[xZ~ou3kxʯ]I~95 ^}(K^|Wd~*>R!_xM~uoȯSY~M|7ޒ_Ɨ'A#x//oxO~ _/8 x /|(opH~cO0cCgcc%h?gӛLr[QP9T٪,CTekE_Ze7Y=M)؊< 5c+mvf٬٬6  Db6h8^m?~^-=PzIힱdYOn *H#FT +T"F6= FVnF&}Fٸ͘4ÌfFݡh6h6ki46mJՑF#Ibݏ +&({f%MGK9 +8TG(Eقt (-]bt l"c=O-8`=t L7[x\߉*z+n[`!F/N~cii;.CBALLASeLRxshA?g{&?S22?26d3dC>Bfȑ7̳WUȿY'|||}|;|.yd Gqx4JnJa%禬 334+bڛ447վRLӬTbZH-dTF1M@*>OLSuNLTqPLS 4.+ɱJ1MXH D߈Mz9 +ZWDI:(#Ch a$<*^)}B1MM4'6U,irGja&I-QL_T2o += }v&v'dEJL4Yj8ēOv[!(VJ:'XL#IPaBT;o \ITp4)U\.IC8"LէF4Mn<5߈ xqbBErNq}}C:)LNiIKiN@J4}@2/ /UDOE։=bWܱHL.+n_hC$MH)ⶅS[͑4//W<$xE:Fqukľ|C\T_X$.Xx8FJ)y/gV)uP +c3 4%)3$8na/Q,eGi ZZR*>T;W;C۬i a_f:AgљtNѩtfRQ֨8T)@9)QLB2MlND'䤮\E&'a\_ؙ41LTI>g4$:M03Md +k):'\q]Wkݍ[݄G`[kFG7M>3y~W29kr.KZwѥغm3:wtJ ZSm9]S7뚓yn76reD QE[fu*ξSqgś//{Dlao6о`;xg{KH_ߪShjNJyAUѾ(fSЕѾUftz$\$PNUU듡"m[;A濁Ң!JhL'eAzU=EӨQ= + +GۇX?T/P?T?o~ =|nd]G+~ WMrT*>A}pL9W麡]4U*S/ ?}{R{g`v&B~$wJTIdiMPfzIX:+ۭLk8tYPjvhp4on _GI":زΊ +"^]]eDmAnuNhCLʵUn'P/Å6'΋}CJ;6"S)t5}ӯ5 FǾ:(uQ&;1WE]iHsRf,jbbirT@-=$+~}y~owOZ0 /k*aRy񵷚~5G۠o2P'ՋWd\1  5U•͏ڛfJBA@07Xߝ2ꉞN#IB}A /?H6!f4觅kbk,bF6r6 UWC$X4:8w$eRg)D6DEFF4a!HȆH2wtj8>4^s(.޲.Qw,U27GOGyչ! a ?hzm}r$HD_qd.Tg&R,0f ݮ?^4NDZMyW\j,j֘xmXlP>f|B$[LlvzI>,q옍r{"$b/EiVh%<lk8h?A[]ݗG0xt|O>sݎeri̦+kT㉘04y0nK\uD$G7D/Dq@lAnQ4sΊxuC B#YO߶5+폼HY9Vt8/ܼk[zYvȄd@C(#DY=a U%k=!f@ חo0m`(`` :&$/vwuk%g#zhȉH +NwtjBY2G^?#67#eFa͈} V-rVp[6W|?t\(btԕ7ƍ Z`Nw͋cPa KU3YS]j2MZ%z^Z^VQ{/:WMbm+qUU[<|SOZ*,1[ anͲf#ZsH+cÁ`̗_嫥P(f{AHrQ]E 6PSrInU#5rwFT +Tᬬ2γq^2:PԦɌ~54)"z42BteXB.Fc=8V]UUDxZwU[AN쓸OQB~c'L eNq/ԇrFgk]P}=tDm>'1\n)Zhֺ(-&K߽N0>z釶xc8Lʘ\60_3:WMNDRʸ(gQah5dn==*kkf]0â-曅[yBٹAoq`1_ޑP ݼ+s?2LFI#g0~,ZZB𵆛ϒg iy*V[%K#1qU٘/JmT?hi@סӜc./t.no=;w*^^Y% +1n\>avqWl`qD4ؾ)U"p~$'0I@ fTۄ{_޼vIdž 4q7rݦ+ԍLV 6Ej_?|}&Ʉ_8nGQOABܕ8.N=-yjhQ \|bpoL>Wә͇QM hN7|{ GPOi0  C&:{lo\y`$\I%WЄJ˲h4Zϟ&jIoBPLwP8wd0O"9gwLJlPXP ػ2QݖwVO;ѲNd:9pIvqHY#EH >:26nKB -Tr4J qNyԎ862>Okbhy-Ok9vhKST=|䮺k7Mtpv9X#4Ar,L-Cb_t|PW/E)3ڨmuEKQ E]StʬN3b5ݘ|L_&J3+9V]jZyV뚢ֻjEV*ÂU4rJMADHŞ;N2+YuDxHS36MvZ8~hc탉CK*— +ConjJEbEu+q V3]|% xgTy٪` RAA[ۼ"e;AĞ4tµ|)vWg1Kʳܱp\Q]w <^zp^pZi0V柱zѢ+:7]XDϜt̾`jZ.Tߥ2¤;֜u +YnzT D!=tsk}ZAÈIAzeV贾||w P+G dAEiIH/ +}E2[E*\*()V.Yj:IOb(6smjof>//[ H6u1BS ր)hd]I+YGZA$e{D"ԖG\ڽ|k{$ f^.˕VO ǮZsyGΝjuee39eu]c>{ +cPOgI ԩ|B]\oF*W9v֑K>XH鳥oT_mNʚ:ӵP+l$;u +-аEukmcThhnXɱ= rsv4&x.HBi%ђp⁊=LU1'U^*^K`Tӥ嗤zzVί[[SQ8Ekt={G7<˶ђqif$b1ꖊV^ݽ[{7s%P馢%bIDUZb aлy1}~ ʧ_ zO= 5lhRCwmALM*(l=EPCkj)\>jIݡV9krɘ+am=cBHMpSJ㦤^%zBptP2eeΡNYBuB(Y-nD2c?lE,^&K !%KjUPFF27n$+{Xٛ Qp# "Ub#T~|ŕ&#U?puǒ4rIMŹy5WluY \7caݸNi|͋-}1Z8mJğNm.yW_'L;mzuMYíT͞B.ZCBzҥ }&|N>Zsą55ޛz0;Mwg>}5lqdY/)H6ğp縏؈ wĨOTbrsy(yJ+!HP=|8L<1ŵ#rmńE3=e8IKpU9U=.T_ݰn>|Qe]+l;vڔ,>R%uj쓳+9ekÆ{yk5ϼٰARle?eWZ>7d'Q> |:K}N 4)|Ҫo2ʾ,r3w6fphU!7h Ffz0R^W Bd& lJs0%U'B^ѵ5?g(%Qi#,1DhEUGAHM0Lv-Qj +g$$j omOE1:Uh[I"'v}=w,#OqdOVقUU(.G=NcUW7x5*/Xa +OzܶY~}0X˫oڇ')c<7s‚XJuEvSE;ˎn5*U:8T.Zq3[b&"6Vn(2L v;=Kr^ E +*$X 9ܕ ʆ4SIl>Qkn6cI1,$At1m[$:QpFJ7L*7^hW#(P&j^ !뵈Q48\($=Չz-:Đ CbY0 !l"XeY0ETE9G S!(ghf_/K:thbT<&#vQ!>9?W?u]py߷{{gϾ۽;IIbO'%cIDPNb(̤qMBihLHpLfaL"Ɣ?mf)BO㙆N@V= SR~~V7$d`xԋX}A* b]B̉^|{C~ Oo<V6>[\ʮ"u}Z:mE_hKɂ4:ඪhB.~wz/\bedfgb( m2eAD[`"mDr#3`VBDR8R: b#pNJIXi?R+'e^ +ٿq{5Nj~:aec2ŹMU^bx `|;nj@Q~H.CQ. ``P3dz`ɾ¨F(j +&*7ٟ1Z0hMgXo1 @^>޿˧ +|"3&vh`k^A/8D"Av40vOL7 sx[ +1%PTuU[̃ȉM<.@ 6mTq(sĝSLA.wOz٥TK‚|#Z,K!k$zƶ"ͶAb O#fUx`thhOuR XPKR/w޾p>!^po[CrZL_e؟r~)|]RlOPŗKo˿']r{HOzxє5$>ȇ]ิ9(` Y1uݏW6(RT>H{,R)bV?& c/YI%Vbo._|Cn 8С7xmT +a2k. +p +:L9۝?r88sIUqo\QD{Ԯ"O}"^k" si%m8ѯ~]=fzoU >KD啇sjХ#-52S EV˚dѾ Τ p&,] Pqq ]sAPp@e簢C׃롸˫+FR╺5m~ +9d7y墝0EܡAh'd:Hݢo3ɜhycn5R&W}+ [yFMT0rh@1mR> _ԿῸv<QI֋Ҡֺ^ɣR?>|xmw"\=[b48|D:d  Nc TbȕcgXb&,/c xcT5؂SG/D"œ[ź',ȋu] T}ZefA䪘ZsHu;$NZOXzJvG|~H(1bL]N0Y\:m^0َ`: 0]|HD gwE,J?&*/mgBAU#tJ] N#Uo + ݝ@N8/ @^$fmvgSNfQZ4̛'Mb1YSE1Gp/=CѬ\Jx>|!̅5crDɺVYl\9#ada_r +Hhw5<Ր| )Α֡ ^?J!̖L=o<$l/hj +,%}qJa(|Lb&9 ^/"i;䃗2qSm'H%@"D  q9ndrLf *Kaoap&  !W&ޒX9iI(FJ$NC6¿-8XLw6Wr;LeG!Td/lVon. IrXmX֒XL.xNNJnְ,%1ܖ{t}=+@Om1YYVd\@ps)uBoOI]iqq|+GWIks IytkZf7b1׋Z7P-KbN05%}\c:{)aW٣G^(儜%~dH/( +S-e& 5 Q}tBlj?Ԡ#Q˦fEdk& (i2U{0hh*OW؛*T~\*k?q7}5švsU|s~iJSQ@q[!{2Ї!׀{psap!!X5(*Ul!ViZRu\?#N=HctZyD@?Uy07@glZΎ,Ņi,O$ti&gg[zhI 4 B`;#! +gòFN*(O^4q=BhKM$C :vJե+d)Ia%A4KIUE}w 4z:wCP$7HDo-)^xJh%+̈㉭}G#u+?_z۷<>5 +isڦҴa#׳#t?W,E,Vl5,_,Exs +endstream +endobj +736 0 obj +<< /Filter /FlateDecode /Length1 19964 /Length 10579 >> +stream +x xSź3i )4r+W)Nޡb mQ EEQP7V0[= \i tkZs}8Iޙywk~kee`Ը*P0!s{~Y(YX{"@sjzVM9c]|= + @VU̝:!)]JfhJ Kp]Hee!# lf=a3g+* MQr虅TioH7]3KV `*kx JTUKx6| z":7=8:PCtV%$P0-jYڳ0шK5?dSxc!4:Xf@0x2-5>K5W5ީ( zLh@8Zh@@$?yC{#!A/I [G`* &Z&i$4sK!åPz=YWVHUBW mA\`i՛fg;t)Dgδ LkN1HnE%fwa4a0jwAF"1B+QB)RnKM>5]iE +120(lT^+DCJ!g22P٨5c i@PXѢ:9(E4W|EٗQw(\T5QJLQJTa.f_oK>D(DPCň:1w*Q&v(jQ"fc\\T*CF:Q +-q;,Zl^[zZ܅1{2jI씋rw,ʗ +RD!]󕅾Z/娺Cנ@5z ;KJ/5˾tҨ5od07P}C%_ƮF-|~ P'9$Zk[J *,_U[#"nk(- + +&-*!z/]3bD;&a@R͈u _+zq ߨߌXm=#9ѿҨ5榤766W$n7`Сݻ4(9IIId~^P,(A߄:K|W&DNz}Fz‡ w"ny9x9+NegDS4^ +Ww <`OYl e5_vIYӬ^\V*C(> ׄk +sh@Z)'i!ksHXI9BI@s@C ?H+8 /1pA54!>xq9xͨ'< />BfM1F$}XT{RjbOoPа~Chh2N ltͻ/\zxfQ}VA R?߯ pWے#T=4ΩѽӺ;4wM + ~﹀UA6v~4hx簮]I&B2u +E%GH2YAiXd"  E1xI?·xY_Q\AVGlY_0P^eQ!!! B4!^s@ %ɬ@^#S9b ruɧt]Fwҽ~C4¼SfymAaĚD.͏XA1|({$Y` +^7ݻSWZQ^}dcKj4(!0K?_r5Z7I%,I`/B"?WX[g~8lCHQH"_t+ Ӧ@czg Id[xy?('7/7Zׇ9+ york?|uH&i> ~5f[޻zX[l>}柣%6mMI%oa1z-Q.{Brd4 =v2t=-tX1/ۤ/Ys5[u tT祧\d&9&K +BC"n'ə)YUfDy٩秜pOb\W c}5hcY4Z+tůGM,"fCTvӮ.k… ^YvɇϷTYWZQ^FuR7bDKаоI%U7UѠ+KixU=# Ƞ{l>e4WG2ͰG8}Ԥe˙=RQҺi ց`?!J3C tO 4%WRK[_w'ad>C{Bȍlg5y7Ř%j"d;,w')#^ڲ%ȮҶ,36P%Ѝ $7n" %>N9i@LJJۧgqH2┋Su<1x޸K,դ"cbCmZ]}GHfpΥ/\HfMYSLYf4vKM|bŧcS:6l Ү]յXc4~ydO?{yj훷o%}rY`SH?iF{߽[i?mnly/!y;]TϞUUẙ˦M--2>oHWۜS п_rߤ> ^=zb=)2"<,KHp10i% YY-P4֡CjZ(+7( +PVoQ&*tux{/gYeL%Ƹ4ꑕ"=B!X^)+@Rgd:Io5_ k#J !"AòR(G|iR"jf*,QFqge,#^!"t%.\!QN4#!QEvkId +=jAv%̚KgjbY岚XV֌qh w+Ԗ]P +'Ov+tǭEGCSJY`Xӭe +e%^عH6ފ,>m(&0[Cԏ#n8Aؙ7ܘ(n)ᮦr^WȚ +XV0mUm*Jxɢq*%cYOFA1dd3Znvj6PQr@SHᵴb+z.CѪ#"sTVe/Ud +)$;-uyx](r[1"S#\ vB TK5KzU^CCRK;㬹c&ιͻ)ax֙"MrYc'U)-ۚU^0cQf&HQU)׬fj]V%^~CClX0Cz|-_PKbcRR7ߔ{,7OܼI7ٲV9늬Z*{%&%Qb,(#)xj/iĀ%ecUV]q7ߒWoHoc\dImF@^nf{b1m2ZjU rIXw# 6P'b/Cgh(V4EWaq]֠ثuxuzQ5Ǝ2;BSL4SC+^R0e&jYD]y)T*x:x/#?4$IWˌ<;AEŊ\0*-\`-r[5Бvf(Ԧ~ +K~cģzYգC]ЉrOT^o|-G:s +)VL9[v=~ +wQ4ش(MJ~ݥP+e5E=SDO"nW =[6L+E&GĪz4_\W4[RTUVHۢP:n\ +dq[,&YƸ-Be-T7VG]X?mQ1Y<&E疧Z 1l`[ZeS eEU9j$*Uvkaz2U +)8CfʲZ< +^"U׫9S +d ջcO([-QΖRurԜg#Ǧ:*Mc):/w8E✧oɦ̲+4l:'r +d5m1eu|Ztm:Ѱbkө#FqOVBraRG< ݄(<0(}zSӺ6XNF#;Tv +U ADSVVN4{1g6-݈0fj靉;MI-i8aAqւRMq.X 53(3P'M1IwAn %خFCPR/{`VӲ) ( il)RcKʖ[ + *z,`뱌f0_%j߰ dk֢2WA0Ce60G +t-aOID^d+f{Rī;ϰN*0;ؓ0V"cg~j:5٘֝= =D$dl\l$[`VG̎7XMaIk||(l>|cl5y>6 +f6v'ZY>$Y dD},,m@a}USQi^\+_P-,P @pAM zPf@죗A`!M/ ]+|ݖi/ ͥ]={'Yӓ,I\Դ$WtjZRppe79|pM ^hOLqM4FD& LL7 nj-t3t3fͨt&ME7!nIݳtvW R&;nlL;*m݈|F0$ЍHwGj+t#Z~T7_V2*;&uJ3T6 l$H&MTgafQ&b3 &l"D7эt#wt5#T%8HoL3R ,B>"d1afxh0Û4i0BZ\~i = iF ZU/)d0b͢3 ~$+&dlxlh6 0 y҅.I3᳃|L(it4{)DDx3%^rK|1֕͜\$95}g-i0upύ΅K$ f" -ʭ&9B\gg^Lc\O>f'1{f/YhS;8˜D!ŰQ';[JrP3pVcZ*SmK2}^CĊѢ9sU 2 +VQE7XEM8-b}uKpPH`o2f|Nr7oSѼ7Kl[Zwߎ x4w{)bni3+H%[͛㧙7YuKϼ0?cd^eRh^ $db`ζy i4^bsS̓$iOWJK7l5i3Zdk~NAGj]]nnnΡuQn.`Qz^zxyˮ/5VJH)C:Jà\;.(KWs:>VhUp7GhWb/AKZdRm! xޢU[$+?zIdMG!A3o! +:n1G++sǹjTGIiΣ=ꍤmtFFƪzroiUV68HukBh$܆ 7̆HDr[iZG]1tZh5uo$PTQY7թfl. lYc ¼lv'ÇP}c ΤXIz;UZ`*P) W %:qarZX3[Us5YybWifc0ZiZ #VƂ[T@,Cmk]-̹yVV::Vt;ݠG'crGD|3&'=X5DL5 /Q.L4Gj@[L˝&5] ÎjUګkjjÅƞX*%R\AmoͷTZ+mqk5lFդZSmqk5 [B\Zjk5"x2.N^'t.p.svnvJ{i~teeѫ7GkUdVsu7ѬcLڬj5[SvT0#UlcfR($/onԥU֦]Ok')f>mVZv@[.׮,.*:?f\~$VZ˨f|3͌f}fuaX}js$s{\{KWT s0GVxy:JpԔ/i%5NMꈳFvδp/KjLȒ@%a7K', Xɒ $$*؃jTۉvFbWDRjGݮ5_ZTW#<դFhbj|-T׈Z0KM|?6i/K3`m[̦`JF .>x uHh؋\Xa#fPd#HX8V!I0b*b?Pb1߆EC20;lXƛ8?)04 81eL֓y,,(M`l$̕lAK$4bbB#io!݆(D)$a.ރU;L ؑ|, ،Chď#ϑ dO'mkq/a=6bC00xa֢MGr4Rb{*»/8G/ +7"I$j455Rҕ<}؏C8S?^9A D2?%v3c `*1wE4-ߒKԇGjޖ.'X#0P%  "2Cd,FKTK-t=$`aDa1Zt%A!rHLI_{q-\jomnUJdғL'$.fdV֏6m a6`l[b$h i4{55G4$%IKoiMdxt3:nH7OwP6l»x[nzt,ATK]4ioH'-t?dM+ A i&ӧ|H5%Ir?B^ybV=@V^|@ibtـB<*G񂐗z2D20 9|QHbiW2^}!؇p/a$W;T=`GFIB8B.[:"-xc,1A +)! ۱ +H<^GJ䥠H ~B@Pu|%_n""iFF<[(KP)x F0@#?q w"'H88&M< #,m +B1)Xɧ)a.>0^K:Akq1%hP|BB/H;P9qHq*a0_fmIx6\czn&(7V'a]Z hD_y(I@&KaS~{="aG8"a= /d `0 CldccFaac"<ɘ)(&qVw0G|q q?xXǰ+³Xa#ЄbNM|o|]nxb?>'q8⿪AbPy+fC__!ǯ + B ~F2F~!0K ~ ]/!]% 'ѕD& gD &3n'XRc? +XDq?'lGB,v?"q8Г@x~Ip+d2D= 9}$/)ǿ`!ϿmϿ "p!4 H`~p mpHv7*dt5rpd1Y~ u3,B3^lOU޽pPȏ!488"QߋOw!>3?q?@ 8|=} q +qZ/{pK8n|-78wGn||7'|.S ?#]Z{YK8e!$hw/g_ +L?'~N0`Yg +L?+~V0`3g!`_L?-~Z0`iSO LoLoLoLoL?)~R0`I?L\0s'OLoLoLoLoLoLoLoLoLoL?.~\0C?WL?"~D0`#GӏL?,~X0`aC LX0`AL? ~@0#?LH0}`~ L/_0}`>} L+W0}` +(` `{L#G0}`L#G0}`n݂w L%K0ELߙKL??Ϳ3rC +endstream +endobj +737 0 obj +<< /Filter /FlateDecode /Length1 80364 /Length 36813 >> +stream +x |T?̒d2}$3da a L A ,a5 =HXE QĭVQ0UUZZEU!sPy?|sEGv (ӼQ~~&z"Gt|t>,= (S%XJah/-E8^)O@y x);3] ,I;E<~ {@AFh@0n< ҍG4 +>fハ 11Ę mǘ>gIsh= 䚈5 a|O->fBƸ?TUsEbՅϩuȑy|m |>9)bx~P[tҭh-:_K`O +tgT~M$S.;;8̯Ho?2S9@w +TuSBGܕvۯqO 90ikt "/X>QM@/aY͓Ɂqs XX e?*qb>i* ׯudi'_C?"/u93ǒz-DŜ+ =1'i:.8o +q[ʔ2>.s09Ԑ-#o.;6hLkQ4siZtT,9$Ky;(Sh|\*!u9#'~-x[(/|eA׾W":"c~sGry|Ar~aE%T8$a@/I`_}ol@%G}QWwPow +2)Zo8/l4Mc.j[iz?G>lDKWDX9ʗG d1AF@^|MnZP;!yB~&J3P Z-ߏtc\}5M(7܇ $1_^A7c B܅2L4mF6Ya2V%-:XDЫG`^@m!߇{<.Z̈́4n;C~]9{WNmiݐ5;^#y>3b+)V΂{uNݔ!S'K;~N.υA;>eb1u )ua g~p+ۼ{w{~=]j]¡"ru:#DXC:g? %BE;7u)OqBХ\z4eNm#1 Q;\wZ(e=X]y+;F4Bk{ߠ~q(?_Y$zToB2-[im/Z``F7{ .)%DzGdtW9|!Tah8g簟Kz/wKB8ÿ 3XmR𵅲*Cy9 ;ڨ+!rH[`rY!W5-hv`w(Dj-υ~6|&^~@ (|퉼?'\eM<'-%ða3$<_dP4=>zZ\<#&+˴E^J)lYyCtEސi=w.  H}=et +~R=d +%).LUJs*㜪|kYwJAa<" eN"yC1 ttLy\21d7Bթ=tL4<|?kx;!K/mګHwv鯤J9&ur7Fw/K(/#? b>Ӿc8p~W*47e UmE!,_cܜ||=H@6g[@@(}E"ا;ڿO8!_!oγz#?}ڙ`/Rę(t1FCW:t {LnG5-rhX/Ś8e|) +Bi@&C4yV-)|_:j4}N ,w"ys•9U7m0)G$߆2V|dE7 L\/OF#O7T֧z~"o^}^.#̣FO*=zEOhE|E`"`o2I|?Ezts/J4 cvT/x.AօuU 򭔠n)LȄuG!/? +bOP#H~24LYܩʘK`b] Opb?_+thOW=UB? 2{iNU}3=NػѧѮS3~bX㼢;=ʏgg~c`NkvOŚΰܽ;2Iг?NјOݩiz,WDNOA9LC? P\r: >;ĺ@@{ĀfUPPC9P}1Cs :)`O ,>vc]B7(m?oU*{W`NxӠ^Ǡ)xV,|>gǘ +o{*0wm;1PobByy+>P<q{;\O]sxop!|"_yދXO`:rs4 x7@ +>"~1 j^te{J@o۩*xd9 @_83(IFV] |oe2& b|ũ-{5Nh.xUܣ{G^K2Qn PHxqzs4u+kpwXRxA?݊0Ѝ|9 RrS4t(?-cAKpSI6CavAcv s0ixWa\igTL(M_?t[:8׍6BUM4Ckal%:r Ss-\d2C0USP:vdMڢz|t\S~cgE纗q{gyJ{qN{sanW bOߡ=΁qCW;Oڄ )ww +|:i;hC$t ևbj1xN~#$9/c+@ $1<klVGݬ-rA&Iw >" d:ĺq! >ӝx0Ț>;0ҕ :A =As]lsQ31/\7 ^Se?>o&q}Mr φhg}Cm Y*e5Xo0NX7DvYqb!?KepY!/7mf1|>q㷔)1B vcOXÕwv[,J'i]SM}(ĹuAq +夾*7(xwnާ"i*_I_)Cn~?AA ` SnV+ncz؎y?A)L~>mT$πM8/)K~dyF(KIn{ƠLy5dQ")4ᗢD0ԥpCW[xppVL<] ʣ$i_rx9"̴O=?]vF{ p$T.^]!¥Hkyц!޶]qF v +Q@>yc|bDy^z>ЇC/$r|V",-¿(4D< {0yEj_TByv#ą(ޅ+YHK}E_8?k8ei`=ejDSoBxjA!xJV  x$Ou7 Ϸ!Ͽ89J0/_ꇴV_ɤ\ N @i-@v&y/rfcs`u=?׵{?f~}s ]!C&SyoSdRC+\ VXir;?oC2CW_ kIܛ5ק hQ0m̼Fܤ}>ibW=mJgw6A&:~'@ ';P;㝗2zDv = <2ƨd|)Up6\Ht?FS }O<6CsnyɛЏ +^#t!dvgL?T_e3!7׋5}0=NGG ȴ3hE3o(kXv)gEH/mX:wA1]Hﭝ6xRqHnlc,e6+<-u~7t>F$jXDw-ƾK؝TO,B hv2ֳhr~E7ץ9&Sh01P&n:g +0C>qms,Nwn_j3iYzE)p;<ns)? !OnX3/ζfq?Q?986/Mo +e4zXwqmAR\n9 OX*E܅oBL,hRy C. z'"zY51, 2xX/T*`TK:q ,,boI|?G)׊wos0|n>h,2aOcM .:|ۏvf~EYf#ܜgNKv^."x,OKPҾJ+Pc"'Ǵ.LWNqCTDO_?c$;_Fqp3Ghv1ΡAQ>Cspyo m1[!vNnKruhOwn2%3ճǐA;,>2=˰> kk%9OF$Hz̍Ϥ]z֭ދy^講3Acmꋱ UӾ.g5妷@?8 rNX}YBBzUq@ G2w*/^[/[5ūnFIb:}o +r5~&qo"߀50"2؝ +u c\} h[CbMƢS]/Cՙf쵂r_ܩ5ks}ա#{LG~N:N^~P] l髿C'ЏWNC-< >nrbcyOF1VN/Vhzaέq>:=ߺxZw{X{(.TP"OB *Wk/[qE.?o3O4nlty% п_aA>sr2}tەLNJLr#maV٤*(;̬ *#zsu]jn8 ?=L]+O@9Bΐ.޹ +;xngSWü[1|0`x]8d+jˑ\Kx0as%,p 0 PQ"ņB$o9/APUWW;=޹A6lwfCvBD6AӰYdkC׻[r7zo}ݴ\W#`¥OYxu]}rsE|767sϚ|k#ЄKZ[Sdk׃Ilow]ZCԢc4a599W;DI^O魩+Oi ڒ}z8fm[WN?arBg2^"H {%Nc@j5aGZ6;u7C#.&F>J:CՋ0(8D؋zlFGUhۺ<4û̈́%4ZiyT}|&sOgZ/.u\ВgwT+WNVZh5ڶri6`abRieRu l@8080D*"ܩ.v0!9[ΚZ{EJMݚ:PrKBOS5:xh %{"f6X{u^u8>A08 5 uf b>E9j16ͪs~ஞ A.WUhJfgN%&!kǫlrָk1YjLTwPuρ Rק + +y":4cͩ;;Vo}7 + 9A"#0 2GrFn6WCp-d.!Q\:<5gS O%#j&jJZ?Z"9=#{:&9kJ-&]] 0j_e[}\b"R2/X +b0,gܙ7 `T9ylwPdpJ=H0=\Wuӂ1~D) OaJ%|J@R]6,N>L{00ko(ښS{&A~_zZ֡C CW w([Ֆݧ2|#mAA@v[Ff',E[mQH9QF`n*eX0ڏA`#O.Ny'ʹ x)oP: Fq˷ +zh2zn1wwzAotڰWx U^֚r 2Lt6O&_-7Z@ @uy+򶄤h.G]N +V¬W#jY0*2䷌_ ݃x +k mh:yAk ln۠@Ac4u@ӖZ"hAeHFQ`fC Ёdv@7kJ x\D 7k 氭9()-+vGF?9ʌыG-h Mq5)`lJ6-{L.(۵lY0HT/*+ِZy&ʼn?9V|ldN+{ys^mg=hgSZ]s-x2]b]-h Jlܹw-%]Nu都ue5!%;]} ށds{y`;ٶ7%i3(.WIEdf+)BsoۜjN1Z-K%fXL"Yۮ +ǚT!,fO0F*'eJ448_n&+_U0vc lAi=f1DZ'Kt 552UtODM°UC)~eibiA5SDObʉխE;v \>V#aTl= F =fmuyފ@mRRϗY;sk2?C̿a`|,_OgbbVвe@u$«7UiO;Fi'ԅXc 6@M2+֗PC|zHZ^D{z:msA ҝMJeH}g%"T >&R#\6j)ڛ>bZ c:b ݌zA̠[>XK+fm! i Rߴs3~=zJltG[iGn%7ey4{bX_9eiC;}-ge3᧑46=h0tpVm'/7QJZARJ~?>D{Y_WJ+94~ioհ)yQjq'F%BO!c,a./WҔj+Qz+(ǻho?KWHk G⢑xJi%]LC>M$+B<^~݂ͤ(8G/R;Q̍Z dc6mdX;{%$DrP~Q~G鯪Z1R hz +-vzgq,F^G`{wFzmǡ/:~КɌQv>a@+ţ 9l[>DovɑCErSYMnoA(;B^}9<<GO:YsHUǦ:>Ѧh7AIסwb nwFzBo~$b'2/FC.zi$; T|L65]Ůfװ {궍=(ۇk=1}-aK2FOʒA0|i4߹b|JRWz]}uNIUe2WZyYyEySAu/A&V?S",H)[&lk /tV_ɾ3k?e+z|i:lw-4%)Ѕ9"d*A=J.Jg^wwW/2.G`W)#y7i PQ!4 }7AZfhhӰm]RZHSTH{5+owj7hQ#-4R0-/n#T1VjцˍPxh8m3=Mnؽ^#壼9:M"!KiOϧ|z>}+Zkh2}t~=`6`9A +g8г;Xc`= thF36+.zJȚֹ +:|MЌnk{=- +vKO`-X7>s^^*oߡKA:cLHA"ԯ~>DgG:"E x 8(SHi J{Gǡ=QV_cXL#5D"{ʣ1&S҉cjхj6*/]Ii_=AzЃ=AzЃ=AzЃ=AzЃ=AzЃ=AzЃ=AzЃ=AzЃ=Az$B$CfjWH1 2YzT]zbLafn(bRD2!+[.Dxɒc%cNP)̎xwn~Qb?ϟ߷R:^qu!(ZymOsҷrD6vӶ^E]s D<m3G$DHa)k#Q;f#A[cejvlaJ$ 䄨EQZLͳDǏFETzWϖL7~s7EH20@̢~ bͲGb㥁}bu_;8eJNw}inxp߬;}<h" lJ?W5W=oDZ>j#"6>KGQS^UpQċ|Z=X12?JY/դQjFJQ*令k#F,YGW)v҅ӏ,)q`-=7622" 3؄4IZ}f/<֓<žsnfcvQ#yRM%JPe' +Ŋ!Ƭa()3 eaB'G9\ԿtQ>R;foo/2ƓTZU9疎_x]-|`LudwП)2eH5  Id+#-Ѵ':\qMqr\;wg%{R(&f1'c==EE' eeK## ̋*+M^5]4wlE-?tƎblXkqafmȚj)a([RRQK&X-me~I4= /-- y/'3QRO %܎n4`[ +eҢVU3q| .o pV2#Q^[Fy>}ʞ>y^L<9K 6X5PBDi%]KRdY ߶R:87[`"ͧx:v*1jdǾEH{ (߾~}]'%2a;_2q0n%%Q+7GOo~1'Lwb.`@b$ +b);rГOf>2'Z`_ &EWKӛ›"W2 Lܖ-bW򣙏e=Lx31MJf}6_d%]m[ EbVIld i h/\52:keFd*荱P6Yn3z]c,"? lf 6@-PDr"w;3ޜRˬ̎> `s* +IڢE"쁰@M rBR}K?c;꘾ǎ +mت@?/-=*^GCC9X] dLk7=:H.Gljw&xCvƿ̹.gyB*jL͵5e3\VGfl\ jllSr65MH !Hk{$y50_^~i[ElKiʹD"eGz\ +GƄ0K\]bٞaÓdTpz9lQfTfOe2?og ss$ajh=Uׯ>FO8-Yҩa/)=ztnĻ'*!$M )/<іJds*HKe!@ ?$X4nIO%W#kbcdR><[qGXq\Or%n9Ҭ*)HJv:SSM|EWGٝ;' ¹5N페~6{8de6Sc19:m}c^JG.پα.37i{{HޛǟS_HMO}grJJc(5--Ƨ$8-iu]fwi))QبFh{dd| JJ4W6"@a-%}GjƥvH *R|YhIPG9{cP}NW/wi>u'(s?qJ%ZZiTr4rY9!VA[C•B}?FQBnxB0@W +80\=1Vew|JO f2N׌DhJ#? ^!*MM֧]WxgoJ~(ϳ>ɋHf*m; +L~30]>1Q)8 |Я0#)_A3_yFo~3+g+pq&grlZ|F|v\~Q.`IS6IQrOfS3j[ ,9#-;)O1IrZBڸweUhvgIZTk_iZ٘#bw9tfRߕ! -gYs>|ݔ2).d('MmempAp=#H)k`n_(([8 +w E#W\SL497 +*6v4VAŏWCu,?Qӗ/]3 Gk>s.P ~sjɂWf'1s3)Lى,ܷy2 ~o)djBRZ\T_E{ jtRZS?@zQtk|!>GH8L] 5|Juv ͧH]2y=E m-@2pw_<к0G銗fu>:T8ٗ5Pu߬MW7c-}z_}%!NiWikg]*7\%foKbTtljlcקIItixz dħyO=TNJ)+x髩|<5\_sv|f,%T1|%B3kgiV̓?K~YaFѴƼҤ667Z,0k/쉈hgiNu9<&4oNSi.gD($1/%7y3)ۑ-eK|YYR|%˟0"kL&3'bjwE.)82=-UtqpL]]| =\fX-MdX.:q9Nh|g@DNNfZ,  %dTֆLzswC(5lL geaFa99Yׅ}ٽNgXVE0,5ܗ?)3ؔe&W^J'\Q4nc\ORt}ܠ;^Qw +_#},fbO'[.JE'='㵺QotВ$^HORxԤtkr"koZ٢`/v?x\:1amN~}l3U4c|b#>IbR~l Verl55]^ v6ㆀ[J՚o X6ZXOZ޳hhYh)E'ރd;kyk?;3 %%Qΰ䈔,<40 J8Z +dsWl,}}fUfbtǜjaөmx_DC'o I\waVB3O%;0oׯ$o=rh|Im=E%!2ɖmϱROcoGq WuL3su٣CsJ3#idƷl#O+re-prreskc| 9 䱰 +N$U3}OvwUWwW_?f!\XPkOoSS)[[iyP:J+WJ)mS%΂ Z5VKkf6`rOa`Ҿ?Dj_ה+Wj%}%\_^ߪJoKNUk` Rӱ*dcqgǢ҇(*`oQ))RQAݻ?~cYJT-mE -;üV{h]8D0Bn#L8 `WEg|vQv E64~"AF(z:4WbMQ"5*5乓5S#\ Ʒ"ͩ> +a,V)i`^Lb bFP|&C8A4.!=q^l/nBzU/L6}3a飛wD[u7oIS͏qx~oOBB`ʻܷޤQEF_И"LqY E-Yni_3\B  2G/)$IK!乷I賊M+.e]RjT]xcR j-D" +2y Щ-jc_kQSv!%Ԁohݤ]&Jk8 cK6iNV zm`|\]~~> ?q0KSx:")l<u9p`ZC W_5\rM5JEQA{ us,|S #z$覂d.%p,0CD4L;&&DQbkLb#BLfUL>L!jԷ`뚏٤w[~_s"މ,9c>MʍCwG +_ +1)GϤuMMtR ҥ–uxP&:ꯃ''^K~/a pubA&Gk 81v/2 b0#k`UAjoap@r@9HP$P@c$tČ+>IӖOb[ބwrF_Od!EO'3~d'BhR[PpHFcXZ"ӢbA q1LS82ìua!(HY5ܱ)y%Vf-o=Aį6ziW޽O1{x4]a\[}O:WW7? ڵfҠB%R`B/Z'{W I0<*'rdGWGZ)mmAau֩nˍ7:ovDF#őn^..ݮ]',OEQvTp9\ W*o"Z4ɗETRRKR[XYX]Xٺ޾ѱIܘ٘ Ga +`ze>0~Ye:t:i=3qֽ Sܻ1.=QJRvLb+SeH2GBh0y&[*#VD#ά$9RptOc4"-ؑ X7%I7%Zv~7lD X7VThPBR\jp9q`vtl$gh"0apb Ħ8Dw$J;2ȸ@wSB|^(˦D$]L.#m{DU3r )ܕ׈Wf-ɂQ@P`W\5KU+\4  wڦmxں0fC*pAZyy.f8odKeI*H6cvi@?˃߽ނJ5z`N~ kۺo~6k;/YT^O|GH?7ts-b",`d#w-ԖzbY >a}U8V͞7;8hwU'ʞ{mamfz'KK#T$"Qe9{x8J9DH>qYnY}C嫮fg^hҕ^&)Z̾>&NepӫMӻ,=KS7{X{VeLm(!hV~/~erlƐpqBD,\"T0a.a/>H,;VzY@qd`Ng0.e, dSSod_L:Ek'#<-g`G^-,x…XؖiZtaw2hYW쫪;tZ~nnk.oSClTz +|YMjB^/PZ FУ`#"c)۔ +]Tv*ɨz +=H~j&8;M͖{]'YfYOʱ,U}@;iZ'ԐuܺJ[|( ADŽ2V4pQB)I@hO1 ^j#|qfp4cu_#ҪFۙFG4PbVӠj@IXts >Tl.H\^^j$8gڐ),Ix]juBwֿ4?/wwӇ2 Gf3< Y/Q1SAZ +\r#]3Azm1Hr{ #XohH֏Ob:szI|;6Y{eum=99\c:c?NjoZNnT6ŇX~ iByOﲧBBFʛh3qFν4DXa| Pla@ Ex p߹!pW!A`mfնe1s&љS}6==J)H ]$R "z]"6'DFw)I.ќoaÓl0mKl@]Pnofx&m"{W;m/j%_}ŝx@ۛB瞵b{s4{Wmد)d}<56/%$)Y i-#"gװ0i&KZl|z cCQ CmpNjts9 , h ^߆}BGWTmK +Kv6\0kg#q;H$. @2kaSzh kA4k՚\b%{ح0B s$.$k=OdވrTϋ[S?Z2 dPʻb`>{`빲y[YX'7"$G_]:R?WLk^Gͣ4CVPIF[2"ZJ$0`>!2X&ϽB@\ciۉd rp?Sa@;!!GkDMWdk/I]d4Cf}k_A96Fs*zZiQ&3#T%6ObB׵*Amv_ߢl 0l +V*n3]=SO*ݿ^s.7햇!v=nǮ3 ҟn0??vp˩\_t=vw}dsQ$sFqv`l|^|n&oxtN"dڍWr ˂/|pooxqxJMYEO`bmw^ ؍rɉsJi>N|'z%% ̗̄30b("V_ccH`;a Y< Rhdh™K6HQİ!1 e`kXkY`Yr0hn}--lN՚I5FF0{1e%/d~{xKA%1v$=8l?&odhRR%MGvm'o#4 >a+;E-7^ygC{w7'OaNπ,xTD ėBi3Y8@ƌ픉0C168x6G2I<(pf8U?1-NJo +ŗ$O!9SpƖ@=H0Bb xհ.&hub8\SŽW'NCۂƢ'Raz}`} =&>&s ɷm۲Tuo"$xdof_jȒex1$=ȟ>8[P4 ]y &0?=4T!ewYfh# *NYcs9<93fNRN#'|*<5F|V==3c=d-YOy|Iݛ}Hy!WDOiD$ %dwd8#"U*g̈́뿊Uu`V CF셏EfȤ/R|xݕ& F4f\"^zeeߴ4>v]E+JX85,U`!>O˾w /nnnyZmۥhzg7%GWwVT_tײ=K}cCCO*?Y=ַ h޲XtDZvԃGwz@]"z`9pedt=B]uZޜGf%:pc影ctq))1tL^7Y$ApogNU$ [=. A*WUJ}g`CL]Q/rߧKHFк%#*Ҟ]R>/ JZ_gs .G=ooL@ ^aЮ?Pٓ}Tdڒv#ͼT!cAIZjl݇޹u+>5d*[{.'m|#r<'C"]G$~m%ihh*pW_w\9ng v8f" LgNQ3+*k}D8PB3ocT*kPG#t؛1 +饱xXnQfXsOXXkn_{Y +hMmJm[KF}}DB`uu.u ,C?6.ͮReynHm +AtxpX֫սZa?'/.^2h`I׌|4V_+ćG*5h*/w)xL20"UDX: ,BfmFGo%̛UCj6撜 xb働~?}xaBkx;R5H32h6 mWo9ﮡNf][s\4V +bYm̶JW-bYyRUz\yř/ wܰ8V$kIc,s\:z7w(~we-umh[.Sz)hʊgA08Ȗ]"/DMJo+$`<{H}81+(Iˣ!TSy!FGgVTMNFӆ@_"t1-i@W %s,%06a/dzf dgoȘdNqR'''oxIS×tuii9Cdށ* Q?$z +ۈEh]CpxD@T.:k]C]#d"$2|&m37D(īJj@!?IyUIp<IHR$1jmC缔pO/x$v%cvhR{  xN([?;s6^f|g{4%Hp//VfCH/4Mhl/ZH &c 91[9Ո~p VbK.'>s(M-*9`ηa›vbc;4hَ~mbs#d +R4'7$3v1"|shFebb v"Qhƍ#DAAa&qOfr6rdD|ǘv67NO 8IL$%$Z?AxJBʮn̗2( +b'wo#Z$TPz- r5 .pիP L(_Cʈ2S(V` 0 80mBnУsSj1?llQ#=% 0]Lc7=Gs3 ϴ.Xڳj%K +ABi'X?w.hk=ǾTWbѐE@I<͑ F@kG֨G}x;fqȚj#U† y1Q! +>B +MQhjA҂%z\CgH*菎NTDѿ9 aom4mSCr8i<ӄy0sբ5J%~>!.7GF+u1<} +l, ?{'-hvI2ZX;[X]N-mhG\* 'LiUX+:Dz2f@ ]B_wEPc_Q^׏Ѿ'HH 0dkĩOЪTV$Ll$rm})s1klXDZjM Y4|TwDbM*Q0*Q @dD_4::~Z^][P:# >t #d"Lo Dlx3n&B32!~ #„O8#XyJ̈́j ySD!/2,^ ?73P]5b^/Q%d쇋7vjGWE4D,< 6C%ѥ kJMRŸXb/eQٶͣ7ZNJᣖ/^bBBx4[Yk_ ݬɒӳl'ɭ ">DlZN'C:%҇Dp"هVj_4ZIqQ!1IZl_m >mؾazFۊ6Eٙ(b-lk*IL`8.fIAs[uz3VfҶB" i$He%mdk1Z=a͝X͝J*"{;;77i]f`s,y:8VzQ81L7-Q&tf"mb[d"+z[LďCl&R3b,oH]>a)\k2i0sogl+by[I~@GMªR'ӄ,)3X" QA!mNhkw{D KL gf#څ }MyasyGF] hcG#|&Zard40yP;2,LB(jV*ju:RZh8:PRp5LU0D\S($z[Lg"q)Ԗj]\- x{8T~o5S 4Q'[  6W 7n,~:05fk'@px?>.Ob* XɖȪUnδDMaUd@n DZh@xX ˺3I$ےˣ0 hǡ !j0H׀jE#ƈ:% &&[}7)7AsdZ-^ܰ -"Il '갍>4&1g+Gn(pΗofUh?V۵ƿ?|nY\۾l/ Qbfo[qVFgD5aO~7Ijt:+[3tk1)""E2!i"/H_BxA&1~ØPyL$t#t&R׭ZņoFF 9SNt3v|7HhZ8s“TI~暸J*,\}ǥn`on~y/QZ0E($`+VLVLW Qa +SF~<(]>֕ͮ@?u[vxE;hlger+/j},}Z=SyfLI5|A@NJaal`DNLEp?!K<%Doz}/%x\3ϛS`>Wn$nŸ@ڍV/WbZ5Cx]W'gŒ\848$sаșIwpXqqˡl9 ԜJGtrl+Q޷ԆջRWG}?zs{HE{ ?]kZ(1S_86U6QDi|V"mQ"p:Dae16xXڑ(q@D ˞EZ$"Sg_j{jgNE0<-id7@8.(uНA,K_ bo\!UhkfwXjM +Ā؉W[2#3W;E +Th*U&4U60 7Bo`M =s5E +vư)W"g6+B^2-xeFt-#*ت~|p"*Q1S1!lz'_|ckiE䂕jmcnTy^oJYVfi^1BEt`rIlT8οċx`] =}\# 횄 +( JS*3PP{juhkbls188P+"{DU2Y246O3τq8A eID +稀9F:DDX2l l`SY`@@0mcdfC R{GމDp"p˃yA,Q¹W/&=x4g^JkjK5h5 +-A37@b|rdjN )ZL'qrK6r2\i2>r\5Zt8pg ف@ǠE/ŁᠨB +1*jd>4T;sm&VJ@Djrj"칛Ǽ~G,ze>6owt-K,gҙŠ++>Kj.m}r⹺Ўo4Gj&$kzǢ kVT,Ig ^火<\@mB+1kıd%6]i6rDqW;X"3'f6"1@7Xm(~XzX}oR}GeۄmP6ūU ioqtM}8ӐZ.T@hX~7wCS6PMf\2+,o6neoQN3NmG S®ї&" +&sIGnܴQ,IMک6TlyFgfFV#aM9fyfW ݏz57p]/"T<lSx.r\K g׺mm+6 9=Le.qB$ N^oVJW`m1oգzqIP4U ts5[Ɠ*vzUR~L] +0G{[ `}MZrT}&lpWʇ^| +)mfɘfJe~K; +V<7j Rq˽F %p j,cZ,,n7` ve""G_V~.> ;8B_\I$WBpSȴ[x6Q3pϚ-f9ʜ0bj`z2FR5Pڭ + |\9 Sɘi2Dr”8{m +v~K{ ׌=p 'f`R%'Ŧ:%珝+<8s_q%[Oyks! S ӔSĬ Gtylz`ιq4x|Hʔ3M؆q<8VZ'_j0)3ogC}=Ճ%faWI:̟㺏ޣVUeHA!e1V!+%PwEʻE 'ɂ|D2SYwxDZǹGڕUyeUH=!7ngBזKnu+YO*U>OZY\^]17NioK^q-diu.ɕQ)7p[:٬ t߷u. 7a(3D +pP5`hK$$eϗT]0T+.%(U0 jF8qU/j2TUXZ]eDUTKJeS. +/tUL*<-Tj ](& $HZ0D¼@>+Bw!UC=Q-}Y9X-co% [©tdR#+gb/#OSf_2i;yĜZ>עB\ &Ϋ +endstream +endobj +738 0 obj +<< /Filter /FlateDecode /Length1 61704 /Length 25451 >> +stream +x |TE>|.$tg﬽$,t @D$H$(; p˨* ꨤT!}~ߗ$=שSNխ#"3!lKY"Mo!PM0-I:) 8^6wfy9iӭߚRMӽN骞62-CZ3DO6u2A'p*QNrH w!|k)2oA2N)i]D;AcxPgБʽtBpP48>7"O- %R˗^F'C0G0Q:$+ +ss?+YRnOiMT2i0dl.JwPhT 0&a< cJC\6~>h_LN%k6P?s>qlrqmt +EVQn.98E%:Sx\N\>l,>ctى| +10tX6.b`.pye&B| Nv1Cսt*AՇq*wSybfryP1'5}|NDh/?|p*^yP.l6 ϥӕA4F^HuT-w@x}G7v AiK?&lM/˴ԣ!7nn.q8N9Hoi._o`AU%+ 3Mxa`\P Dn-A(G +sp?W!'ބx};, P.Kry^{WkR_~^y mg9s.e?-\wy#q\Gr=u\$}ڛmFze?_#˅>^il T ++ + _ . ӻ*4XeZ*YGՅ\i|-S6iX? <|9x5K_QRAs'IaDRR5e(WAJ- (#S%+ɪ%ֲ ÇDy|a\@=G t}dBDQM08h=L^9n(G}>fz:ZvP )Ft:NQ.F/G[uS} ]Qžq¡llC-ctJ0d&GXn _p1#?7\ѷ!u(-3J'2,JQ+hdkX;^( +ʗ tT.͢aU} G\ާ@#`="Hs?r͂4R~>7E5DS;/ k'>[^O|NR?(w~_D c 8HPe,xo?IT~jT&CqHK Fz~L}@_>~'"nH_ѧ_~y^_ODڔg9@nӾsL77/s/sW끜36@ب`y2&)#pkD@6"{#0Cs4ڬە G]Z9d P]U 8ne_:J]Hs1@VOHӣ9>>#B!#^#2' }Y/q"c ؔnnGs[~PoU `ψWPS?^"H* YD k{-7r%c@EйO߂ &2sdMtkUF7:C$|E:'[kϬ}?G`c|K'O>/품??^4{& -~?eq|oG7oXM@C)z$3tYV*3J:X[yh{Zd_n}.C}#0U@<8wDJ}| -l-=[;C?׀fFA/s1\)Ҍg˃hiJ%|$NgUu(8zKIgC+h|xzش&:gD~&Ϧ`ǐ~cZ{T .r~܇)vVJX\/jM_WsљnDgmxK1}3>C~5OCEOdTU_aR_>Ǭa,BZ5[13~vV.W?g Ellld QBwqŪ"|?-quD94}x zڙQwԟ +z%Ow*(zn$űw ~ƱH^>Ib_ ~M}׉Cy8q02R9L.q`E)(0ݤ#I~c_жĽzr?v}YxR|/t"6o.Op;|+AVrOoz89Es"cݿ7b1> _:De-ú5 +̣&@þN>|#^[;tHܵ-VDsu{E{T=X~RwK脏iXuWu\~1  0ԇ omBA-{HHU~D!߷n7 =N*zb6W߫wknGkT׳]6EFdo>&Uxn X8t 17p +n +om94IH4FDTЛt<^Lݎߩ+t>A5ܢοXswz=3@I<]$M4Ƌa[u$ۇ4T@>mߛK>/ {g ܰ +͗{di5*mϳӴ0Z|El\DaG(l+{5sacgs$e O@;/&d| z}ٸ\v94S)K`?D?~.w{wOp~]eq}J{'q2q_NLYDmd5a{- ßA)dȋtYδfq<)]!8hwg%DiVD䌖ӄ6' Vqwz%I?l#G^i~&-N71:j9 3M BcDW+]:_C5T>GB:ľqS #?g=sdm>=Mx|~/߻{{Ƚ-b1^DY81N⎬xO`ϣևNN)XI]ߋwj}ߋ>c\_  m +}> 6A&?w:> o4wCb~셠[n؄W]S!E@lm{P4{zB犳ӐLFh,h@e!3+cOJ> ۅ~E#>[?}߁ ,AFϧ*FJȉzM3{)%J(E[^Jq_UB e܃! RzMG 7/SnN$#x&_Fx9|+p^"}~У:k}0їڧ{_vGq[>WSG.Gsy%1/ԣbUB]U˓ÑV!-e Z<ZJDDexCII$SL"^-ɓP/gοޮ>;ʌ𮼅 !GƠL`2*b]X~hjuT )@@G50 Z*2=V軃t7hJ.`0pg%nK # )@e5Ɩpm)li!֓`m ?{<ly^qq2N`{=ᗔʝᗍDJ*^X+ 9wԔcM{z #Ϋ|yRT7VMb 4ȔZ}])WcbL^{J_?!:77P唭?5ϧ\)8- (=s.Dɴ8*:|_+DrV?Ћ$h)M@@siN#=Lsdqp5{$ۂv=E_K͆"q3L4L} +77fW0aƿ#jQ' +]jx.6 v2^ + teo{ 0~ @ ȷ$.6V53h ~~MYIن`{9f*w^ ] r_-؏7blHC(W^[CEEriVvr+1б9Br%*/ (WS9V6f.+TQh*d0;d3^@ d2P[`/02P12du& =1 s~8ٽݳo_O|?@Wd{i_TKw2SSq~ ~( ;]/}>o?/ " ڗzRAzybJdن$ +ݞ(]W#yT?MǾ5Z莏h+G- V_1sIbc;3gs7g?/Bur8>~2ЍRe6r?ZD>o<]3gQAbj3#[~~'",46S̓E_-~. ^UK Eu"ClБGӂ`Met ^ +H?DRǴ>~WnNq|m 645}3l ͡զȜP̛FeMR1:"shR; :^=R|?]%?K!vlnA=}pYLUiW=Yg3? +=:&4 kا4ȏMx</ŝJ^n+ w|졶Ao]Fc`=n:E{ o#ͅ"S`ilK`3((]F9 D޳h4z1G^Ay[ߛ׊s~,75Cޏ6C+ޭ YmR&7zRgw!NO 2?~ Lg~I6OFsCwjPU/h>ݘ/ =|˅)Ch0sۗJ6Żs߉ៃ7vL؃>Сc={/ybf~֐>{hSz]Z^(2k_-(tDvEAXAQ@tGa>ڹgeOyd<= ++螳]+>oy̹:l$PBiwpz0rsw%qQCOU)?w5Ҡ=gGk4)S1 #?Ǹy}x8!gܓfO(J!ք7o.~>"n[8~N3gPϥhw{K~N`V^b{`i5ëU(/+-\\TXf{.#+3#=-՞gZb&AUdQ~kP|Q +33w8xlgH<6e)KRzS2 gg:a +{ZY7 +n  ylh\5oCC{=53rNTA>팊3ϒ, 0lD& H7tz9a쎖 S]ւ6rgfyFtX" tFvE55tsg~kl433{ƴ)V^Gw\p~ԋGNY76]` ;;N7ş(y%ocFT} :T'jֵN`PJkOi_0{FxmXЎIAw)a6:>cg"mx~gjzlLAN[ֱ;c#17NDrj۳s pr)i(Jf E22꘍aپ6qn _2C1xmwr95G~G^H)xU]%6'Zз3Z].>Wwh&<k&LN@Cj1ݑx̚HLov$!jKNh7%B-TOӄS mtO:FN%%"B971LP3e4A*Es6vGi(Wf +9MgcX1c؋ a'5MaC1q@64z7 +q<vɃA4GF+kZшylU;= ;SNe#r^9iJPb;7e( B%= A$ҧ +Y]D)hVٴ|IQ@$0FK6!cv:Hk1Z .&)I +9$&* T wB!Rb:cbK8 &t)ѝ9NΦ@"+t@OF%>x^i dFidF&G1TV;S%<3*d &3JMT[JEt3/%NB;\#=RG]6N ,vG0O +iEiRk]bCM݆D +*& P(QA:۱ ȝ S_(h0'DD2J,.b(8b2Tst_įH +)r\9%!2C#D&tZJu6q>,Q {oi5 !``^3*ĆF2mœ LHƉ|ef/iK?/ȯYσB?EywV݅wS./!/_B";Xw9y%uQ}E"ry)-/ 8wsy4Gs +ڒJ?R"R@ y%]fKƺ B8#;3 DAy %#y-%n/ &9ɾ㥠;!1tZbK̰ !? -:| :#>_) 14_ch_ChI>HS$%{nED윒]E c;K:ͱ "E|>! [(3 g߈l촧  cujc1xB^#_*zb聎'˗ΘI.:` $aMd$o錵X̣'Qh([In)CӳJ*`#!X/ v A3:1yqAG & +GcЬΨ8IHbE_r^gbJr:L/rCSyR*-P;q `KhC!d:@ȃx +"^H(~XlC^ POR +*|#.^vRA-al5fjeڶ:(G!Qn^b^csYf+4+ Er|FFJ] }7L0|6l+5j_iM}{+R9М>b:[V`EgtyZNVr\ YPڣD-Ѳ--m'Z0tCaaaa0krn:u+Dk(\6Ӎ(x.-/rEwE:Y-s# I2فlɖ̖(f{gKRw0mmm oto!["[H]' +kspl.%TXR[Pt<2vP-XHF4!HgjZ:~t}Ap%Xn Ȱ}uXE9+[h x<"$@llsٖ-wf[c@wQer2mඌurI)?ٙHqZ8-8-/:-vZ~ﴴ:-ÜH5nYhQf,7쌠̏I;,rT0%\f/HF$=<$-ؼ $!|,YN,)4q[hygJG < ?f]pnȷ8@n =rs0l^D>A4KXl.Yl.Yyd~z l. Ng!zސ6Ө\|JwI#/Գc#NQJ Wd5<p窃s C9cV̹=7D ,l3eǩ MsEF׈Qs':"jy8\ kac`|qZVbX(4>a888k 2fM&)c2LbLI]?ZDvxŸp$ձ~0VG$5:e Oo01e'cieMݳiS=], +;n3u7QӤv$uҔ.9֥c]Xk9m\wmk+%U6֟Ѯ?G~1̎MMNؖQ֦\~ԵK:[ZPKZI]ltvDշ"YHF5B$fNLF5<§Iv"~gMh<aҌjFM$_FD#E۵ +* pdٔ'*SQOA?sF72MG힆9@{ի;t:w.Z]gΚ9^\k `\+Rͱ\+h_KK#lnnϯO!mor +~Bh0mH.}ona +`?,fOyO~ݻgrwrã+W`?挀i8x#ݰ\D-\d ovQr-rȢ.?*ÈůX?G E{_ 9Ա((p,4&|D]HЂ~9*;[a}F)ja;6BB +S*{%5 SH˱Z( cf4` x8l跴l>mGYX6*&PKQATEfk,b^nz4nRH4zMeg3h4-K&==Aؕ[QTN3i3#_o?C6=JJ+)@),<"@]Dw#>ry$v9q]frlnfjy[roP:HѿY.jKՒɇPh:Φ{tt ̏+97g@ؚӥ +NoaY3{SDT_~hZq9C[23aJYZv!fHG"ϔwׄ_K.JAtG, 9kU~VzE>]&oR&erDSڏ^S3~h.]3.Y:BI4eFv=žg{d~'=&GΔz%ťdѓz*M8:7o >(F!_K#!] i Znn=@0?1) bhl +;m`7svH͓HciҥW9Juyf5G|ՇCF# Qh~hSp9dK+1ٴh>=~+$g13ޣsa0*3W#3`l ٥*v~oaߣ_e>m"@NAZ36NfIkWM=CG&y %_)o;o(>N,TW^EG,j]_jpNC3cqU7M O>+o`g(EF'T7TwOH]QP~PHá^%u%Iʳ3'ݶ!rNy Z|yO2g_b4i,LoЧ^]z6z#liwa~vNn5hϋj viaFh~^ Y[G?B'q0o2PBJ: +͗E@j?N(٘A.*1E=/e;Ù"4xoz4v@EČ^fnwfvIHaϊXz1S+CI|$t7JX@Ci(ɦR=bFQVp~/tQ <-EmUW6%S^v&:aGMXWJ MUFua0Y{ FC:Uf/!#0{1(agK4胩(R!C̆]6fN +,oB9MtF}9 xDlʢ<ӏ, V>g7Avh+Ua<e}2jB-PJY/DS6Vw#_;d#2RI*͗`X c!UgK$6Ce-=X}X$t4.VҲv^~K>,EGxi'G!_)x6< `ukbha!Îv"JG9( 8oeWwQDwo%*]CR蚲EDCПC'Űq0 `0 `0 `0 `0 `0 `0 `0 `0 XdIK6HU)ʨ<(dPYM&]uO8jvŮ8W}$OTI|nu!P*MK2ɔYF,JĦ3bc.vA 51/N#EJIKZT+v?*nk>8][sj\WY++93yd_yِҒ$smӇ75%s54IM5Q^pfh-[3xؔ3{o/+]%>QT$|tYeO%ʼnV#~%0l69a&]mg6Rڃ$q$H~z_MemdԅuSz.vkNcy/VWa*xkM (+]Ve|Yw ck3:usKqīa`w C*g'sR 9Hc -lzMr)%xO!%45)Q4цh%bdNY|#H52>83f`e44孇RQ +]8Ų?r2{qO7.6J"BP2{JBH$b&RM:ĺd NlluuUY5Dd# ?ƥ6ۃGط~qɥIҒ!C|>8+ݒ}_꼟_ܐ4:>[T݊eg+?Je)*:*5d)9COQѦ.H|QJDsY n%g?t@:6URߏݚ`iYVƞbffSF}m)|iាT[{mn HԚ)xZTomE'3 +"WD fՊTS-2^ 6js)L>%40Tn9![|ۥg|w>fNA=.ϝf_}z85HiHZ +pw 5>01㣭hsSI,)ޫ>Df"r8Yߌ)m~ŶgA~el[{mj]hHJU!`,YT )/$򤐫20}Ah3 wMϒd/{إWL^o[a?{CzmY b +0&?POf(9UѥQN-V8bCӮH:2L Ɵw~Uqܓ|ʋQdLX.3Uˊ*skXeY~En"6.>;Jucr$3^Җ/޻h+St䔀划SS7ޖީa; atOSYby~ ;9`xMfY2,Q^2dm9M6_-L|=zk4d踊$ET^]Z"iN{5oZ=v<"k};:P^׳ׯڼ[\܅d]Oi5E\ ZԱTfP'oŕX%(j4mYdYEfve.DLѪG(~pf,L)b5S&U-`g(jVXh1enw37]Q54L͙4pfz6rS]l~i}s-sMY&\m,kL0f3UX}-E*'WiZ(1}U諘ܕIR&  |U=5#M PboTP<@!;þO>];erCK +^!+9o[͂2כϕ/W6Mu6VK^5wd"821,4:.̒6 izTov.qn>c}313fC%G#h˶yUk5H51D93%;͙v81 @3 +_IN{NFzbFF: BTElHf`i4\bϛg"nKci5QDӆr0gd rbNJ.)*qq +P#O('ZA˜K\kk\֍΍!Eq*e.6!y\bsqюd8=>6uPٮESĘ5 +[͉i9,GWK|Y5݁ +;n='v[hoW.5a椇 +yҖݔ$_956ql%|A{>ݍi]ot*۸Z]Ĵ$o/ǛWO.(bv_CtIm΁b*3 I5LSHybbr8dZ 3F";솋Xc =wHPSk҂G/4*oMv^i珥;@.rcf|U˜ۭ!G;3mnٙi#YcZ9%DKXif\j3ž.۝6s:Zc7#N1mn[Zm0XSl:l=WF|2q0ps߾x/dtamZ%;ΝvmQ[7A__@o_ހk7vE4E?+^OF(W(3C52N!ڍ$@|$H&΄QD,_*ŠH6#t;RG 3Jtx~Xo3^%Q42InU}'90 w08:B۰pp>BO< +/-8Rb[Zgh|^]3Zwsoܹi;Wso_{]!Q0Z!z|7/;re3|NjUR9i]ʈQFigtGb-__ \ +&/#Qsj׍]~m+s1x|.ߨ'@l +<.F9ɓ돑4-+¡$2N*e uVse2w),dpf$$ҧ VxTvIgGO#q;قNgDERRLFr8-x'aS+ymk+PyycFnu6=~ +Q?>{+>;|ˑ[{Ez/(⢑Lrr-(֧,1Ey*uWB8~U9]"]af!l]GtoZh+t<b2JikÖ́\V-=eoP5*;^fxĿ6`P,sNU K(w9nbp>6r8Ý\2gt*4YLo-ƍ.D~+|X=C1< 0Κz=&7TU +ʈGCυ4"iՒוet%?EڮJ4JAUP()~H+߈XVkigZzI{R 44@#͓GP.r2B DWnCJ۱6hk۬eE&a("a4"a@"a$"[ۈB("+-%r L LɅ/Mv-14궄"]ټYJֆl7 M`LamkfO]&?p / dǴymwH\#cEbA85䐕!Rc~.X] +P]RO=7<_9zaþU\nY,v$EnYGzd o.ZqG_ӫ|jz賽UMu&uZӇ3ns=RVL70%R%,B늈Z Jh ]| 4Y|syAE!.ÈAڑ*sTt[0& !id>u2NXm8ݘ%~Le ؿ2ڲնM2Yj[\˚s]n]vZXgM(xY6JP<D xP;pbxԼ+d̰`1Y3M֙!,- bf*2ĆxPO&kmfL^c1y~w|%~yx|P~4E +]>>BQJ gAԚ%ҧD4*!UWJS'4uJ/[m hW-{[-_R"A z%jRG N~B<4_I%m%z0\de%BV^\de[ߥ@]YޏGNeUs[~H~L?zpx2JQ}Clx^LrFЃ-2F=e)=m2bY6%#.h02JsMԫ\Sk6D% 4(|}^xB;mi9_c==}Cl¸1 %=)DԹ?M#`Asys$>dרVx?_ٰh˞׆LIR5[73/閼 oݻiŦ[G<Լz{| @;aQe]3oz=-ojcvEvclA5X9\C9g)W$wL"TU yztF"` ~0~G5@s9W{\no<9щvCGt >/tR% @]:C,aې0K%wABu^Fm  :0HA5k“9kR0Jh/"8])h/ڐdyH'.|ҫ8v~mst6 }6 s6qǸu0]6kiUSM "&{XZzK՜f4,m=FkhͰu)"ah,C&}O:DZ紕gg4f)[A}Dy@9'ӌFh)3L2bX(e*͖P +(T/Q!8NU3vs1fƒAa_e%%0(H &|d1> Ls$"U$%cv +볈{~|Î=['\ +JB—~wn{ +^7zmR]FK͵n@aƁf"s8:du"4Yiv ƘBQaoH(Nu[u'[R'qXp}N׭p}- 9!p rT79I'Buk9I=k6lXrք,TbК(PQPK[:^j65L$&DDr,Em4Qt_~\7]\E$ςSԂ棋>,K]z`h}`šEk6_إ˵Vttmrf[4ossɽ9G,>RSܛYg[4Df/<VnYRj +p + +wq0Fr$ A$B$ cUGNWɾ$Z0ə04B/ +#Tc,|C:IN%o#"Ig(P.zd葡G"6ߓa n s>3iKxU8凃B xIaNA>=q|yv=B` Eͭ Es-9vpijNE_եK*ݽyŘcyA}k}ij˿Wb,z;tx{JR[A& Q)t$Wt?Zbi +YY{KMC{*Xt$E,^RoFd:2qE)]-ų~j̝vCx2N𢨶P$'f_/ +K䋎 |瞻nMo_BFc7~ƹQJE>Ct@D负Ksn ᾙy,3q}G4=TD^t΋p2AZi=x~6R +PLApg0g0g0g0g0g0gHAwUKHj#:D`3耕iF"%"w<I 'Sً=3وmmf\ސO(N(q[+ȣ01!Ħi]S\:DQ!8 +eBr3B<%.|B:Χ><Fa2/aw;oü aDt0~ɾ&y´V2e+țϝ݂{Y`qU[g񞕹ѳYja[*lt-Aۯ(Q^dEe2~XZg7vN=)OO|c~ڈH8={7B4_k}#hZhn_K pL.\|.VJt,>0b+7\gmd=95]KkHjKҶkSצwݙJDZn;5@ a^o|NdF A1Waɘ<ȃ +KD*R^ @*)RI.L/[! +J卖 JLJIoWym9*xnHTZz> +stream +xy|TE?v'ӹ$!$ DMHXe"EqqgdQgh:dQQ@e# }aaw}vԩ:NUsn}@ 0Q[/B nqNaNXDZɏ=6ove +FueӺ; +@}!+14gW,Ι5m7P6gάi)?%W<udE3=H~ԓ5 A`57pϽ{~=땶ݶ) R>bM{{3ٜmYۤ.Bu6߆R]eLP ֧(PWu%܊\p+ѢzATa&?6[1xVY9DHF#DXndù1>Rdª4dy@pp'KC8 &b#C +N j1c\\Y8rL"\iX\|c`&1 as !(B2!)r3dz+ЉYkJA3bcCBErH9_z# +%C,:,E\\9X4Qidfai^,L⡾WZXIiXEX%3f`:O%A!!x*W<Ӱ 330w4aQXbelEl.b:(m=ƤEC#Vb1f(=9g1M!a)b= KNhgRoJY +,\銶g*PI^I)VV +KN֓`%XnD/Q=82 ?/>Gܙt#͞b +h(A^od voԨ|MbigtĤiRl<1CaNb3䌞$T<'hI 2yBOkb +ެkx$9Q)6rٜMydAk`CB5DAj ֔56}1.4mfeBkSiϋ1c‚!QiK^ڟ%LgfN㦵6s<<ϋZלIuqks%]v4L6g~^~^FvN٨1HSZc-?Ob#aJot̓b:_oyӤX&32hNjyb._۴-X;qEozTJ?E&lB3Yh +3lS%Gcю4Cab/F ̪.H~ޘ MscB+gc~svɴu@ eRi'X8 4l5J4?oYI5FU:<6$LhM%Lw- h/dS;|NOY9U1?g%c.0UjZ11%hCX,s!85L92g&4 " MaVKINWf*|v~Yg\ˍt̤kϢXvOc3' ۹l.{mgSMO⊍Y[LCPo&l.ܺ]&) m[ % RV +YFb!c&ƩVwm= +W +R=YF0#AeBP3|=cFB,Ir qk1#!0ʓPɋyIg9DX? 18Z><;t~|;,:D + +8J*1p@Eȁc;FĄH\K{8c9O2IKF1-p c}k$ރɸKI-dr.O *l@4$pBGx0?RQ|y8X ')`F Wa3q$W͜YcVQj갼6dOx_'I&q@EMXոf<9\IDgЏ䐹ZKSEG b,c$Hr%.U6#>?$WCBDq6w>JFfU(_  +1`;#*#FJ$RLJG>n꣭tng +VxG;faz܀' k "BRNjI F^)ܝ|?|kR9EhF31+qHy|H8"Y I Jf:+; F^&ɗ:/"Ewsn.5rr;y!L3m=! by|WWf!Ԣ1=Xu~o] (5I!EGI%V%wKcrF^et4B㜞quzm'~*U=:> hw8os(f %(Ř.t);X`36#x; )e/5`Q-r C+ufrl$!%|JGGz mt*AЭt+}GOOY9暸˸\{u(~>2*:_5U5Cu귪ߩS:wS'_h2Mff5;4idm6H3q3;%|!]Gd^|BA؉SoW}i/i:I7߉PN;jf| I:_CxTkIIGKt4ڱ؀MEn&Ol$ n|gVUl'i5#qS [8HoswΕ`2H%(C7܅5c3yŢ Qqp*s~Xld<1%1P +2LF#.(dɗA<(yMƛd,Y%N;U~V +{1܂WI$Ÿ~2:#gTo +F'8BD"3DKꑎ<V,mh ;A9тJypx@!8Hu 1xu,5~"fRA Owb3+ '"23[&)C PqhvKatpR)'Wй$ 2؄IpaB,hdwΝ; zYgs1iuZu:vײF}f5Tj5 רPx +j5k|4IgM"_wJ֠V;SV&峜"b1*_[`NtFVࠁFlljJVA3*ӝ%_UVt|9h4`6# X :fKFS,ꌎ[lbf-`:Oz:*T^VA=E8K- ̂0 75E͕V1H9kћ-_ZaPa%j'2 LNk2h+~֢S*Op#CHuC(2)n4ԖeTs/*BnI jV?Cy"+"7/ƌ:kч)ee(Ueș} +IŽ{un wznA\Rw֢/mHU>Tcccp( | pǐ\HB&2آB2!-Id? Q>H#'l=rG#Gyȕ"!( +PEȓ0!;P%(#(CD>rVX>JGP2jT@kQ%Q :֣Z;0BQ# MycG|(4ɇ1V)8'x//D +f[\q~\Vhq܏Kp ` q .UT\,_c1]30Yf1+Bf+2\*9**y&.!1C=y`O/Љх/sNsHK1_ ˰@qP +,?JtʟJtU +t*,,Z,:,zA܀WʟboUW˟`-?-X%GZc\:cjc܎~knN,z."ލ[}؈qn + p/na~!_p~)ï~>Gxw!l? +=?#(ţ)!{Y1V[p!zq<(mxHO!yxXރO*pSxL~OxFCL~=< ËKx\~/c.^xU!!I؉S;؅w-<+'w'mY9m>^(>['bW^w/xMލ}x]ލ +_T~ ?*'_1U~ **d|)___O>>zO?__}}|zx;n| +!)j(%xcai<II = ކZކ  -~Eq4⋓d2/L*>0rou8#WoTcyk|rqN[qg4R+K66%k'k:?o[|"=[Dbo[+9Ʒ6V`4m  y<{CevL^Z^]>vzr%FB<#34 Ȇ($R(!eI7_C<@y9Z9VR#7kIDGp?"mAﰊT6󇱆ͽzsDCC&rC)iLqU?^TDzg.{{>'ȂȽ̽ +LJ?:;nr!rp+=]AܜlxN(RRX.@pQ#MM\G#"ww(*#ILuuF.M\"6U`{;3~ "v#uqط^a;ʤD}qm1KzMH_v7XA]B>E){iEܷh"[Žܷ׏">tr{ۋ{}>nE"(YDsWsW#DՙYϮR>ͽr{E.\ʺ^Us\g<] [iNĵbpױ]T{ȪTnqp7 I8M 7 $L&Ck5["dqs47 +QF^ !%ʊ< ›yuPg7!n(VTEvp]dq<%]AFuuЫ4*֬L :"bU)-f?9!q(+l\DQ\b\<XA •aՉ̕C'W\s50rpۉYnpàRJ;a'qE +E W+D+DWNB +..2 qE\Vqyqi*!ZEVaYEWqUU*6ZȋD1P@N /Rޡ"]Tע /*N}Q毚jl4tWXXqXa8DnWC5ܮ͇];ە?P>mvUEʧEd")$dnaEݡTש7բPSu t EE݋u +&(>:%ASGOPZwv&ቕC?:>!X~@?A'hS/)䰟ӘO{{^:(+b +NUAGEhQ0 "rKnTҍO7C!݈Z8FQzwov^'A,&d$uz7wc?XNw>!x7G$utҕ L7b<݀{n@mVx,1nS*O;Ywӻte@۬Z[>ktkӏ>Ӌ>C>S&rX_*p^7}77ooA>&fxM5=5=55M&xMcLT{g$*03L%ǒuddjLUNO,(pK(1%&wIJa!-\jS7͌7׉ 7O4#޼XLДxbZ@fERc@Lg!&ȯ rw5{g{B D]⫡ +1Cmcg+LG9 Jq]>`}\q k#o ^+.mC\|r9rXCqnh8'$g;LC(Uz|i Re cf+#:F!=QLPk!j.#;ĂLG' o?E !=АӚk454BM&h4ZVКF-Zk)MMajvZyC[3+@w)RF,C\P+Ih䉱𘘶-ROSI6c&;c)A05AdV{k;ɻVKGpk[Җ:km5ʑC1|rgܱ;\{0DvWt^Sv:%mi"+'sĆ+l%m06)g-ijRSdO0&W&'dn! +Swd!5eI[vC +m 2-`-Bp$!I#'9M/ &{B4^?T"#-leufu:b,L- >cK͊-j-5J[F#|[4uKktVc|DtDoZc[k+jSmU\{a2aq] w18Vk5.:Nikz2um Si/5m .O[}Ypד<0bF_}WHuuC!C$5='C$W4incwww%K.]v& KM +{ɒ%M .QXٷ[)/W8EwuKsssn+!wpp)f?Mi6fO&VPv}Dn"Y\_5\_\߾wʇzj#KN0QFu$^QTCBX(;YT w/IKOϥCURJ^_4WppUJ<$8m4*yz Q)/kY3n+?zf*r + h]2yrw5g9RV~ޚ fnb|{;^$3LC)!DtcUҜqc-nηηl}Hˎ]zu iu=i78nt|ߑ/ nLyҢ)7[m~7&S7!l1{ͬ:2JAn'uYӏMbmQ_oH?CxuXј VlhVDij1huU4}L}9]*@bSIڻޥ-AWQSmlVj맥%6qġ.;|ת?==.[ n~umNI +5 X~w7t޼t?Ȕa1W-.@ե_S2zta.K,pwn"Guunz]ig5^VrzK].hmmh+Pu3Sf +E\jvmTۆL?N*RѾaKPuNvnv(SQuaw 1HvJ& vo s ++iM,[\TΚf*d]ZCe>_>c/];B+&HUsJnۯfyؾIAeݤY'TED5Ny *J !hύԏ7/ߨ7]W8%%ggzYqnmz4':/hvέh&Pڭɒ~Oq'g$+  &Y4Ss5YYT{^O5+YA9?k " 8aV Bj Qn[B0^]]Uu&B7kf+]Uyb,>kjZZqSf挙c젏gO^<~~2`xp&"}y};e=\+ +^ ^yj~4ssMj7!_f'bA/cXnX='O>7FFCkro 6ׇRN빚fg%v:I+nmR]B*vx}g7/&>%A49N4̙J:X+֐RV_gJJm9%O Br],:QGuM8fvgc$J'}_ i+3&f)@?SkF.G'h!]lSTm$,T3 Š=v)ɦjz'a9JyT +`7{kqҚc ?_'d y/2RU/|t>?Z{*h) *џ2zsQ! +1ԩh Q[$Hn Ngsyf-s$3A?x!SlڪhyfJ\K`W$hNIMHn JlsOJU$䊹s9M%w4n-\rsNj5ZzZ_K1ZLFSG64mj5M+zipueC|Co:z2ÜvPŅ3PNPUW lCD}^2)-pOḳ/U2,T'q)lI|_}'*j,-Z6,'W:uu^rS7Crz }^! l + =*Hjcmm$hZ%ƠOeG7esl:]W92jaH{ue%:*5;8UccP;U9}] -$ U+7ͩ4޿ fj,,`5d]֕Y!U ,&s{aWΊT)b*͒bI]ނ*w;E +%wחo6) 'ӕBYJu@ܙH2PVJ ܙ6(p $F˰@ 7*Q:T|ΞVR%0 +о "A&zF,s&H e/l^f7jeDm5k +ªk + +r|M „UN|Ě+K+;sݤUO/r82]>Xdcia"orfsΰca~񦙎Hm @ĒB%ˊlջUUc!]q͓*Y]n23-Bm1U1'bNK/sXic}qKn $ nVãj44EGhb"SMtukN&\EZ䢮C懎&18遒֘ k_)OqEk_X#@,SKȱh8RRG܎uu:Vlxl7%%R!!?Mw!:)3&2`Ʈ0;y\Цjv<9k5"!YRk- TK?pO9 ֒젦c7S-`Q]v0]J}E]AQ==oec[:1 pO˼4^PEصT2nN1TΣҏ^>1JnK$31S +l(TبF<ij#'4.mma]ɛI FH[Fj:|)kLXnnݒƮo$ 9L H_bJcs62k(R'sU:}Iq`Fʨ6}Z0vrrኴe[/ g yOir,O-1p0\R+vMm冩KU 2+j5oCfNYYzr5'95/({&[INVe8!~m*8qjOXX]}m)kVo,U;4_d]8WA/@v14d #@)A{B'$.v"k]VZR9k#*, <; jVt4ȽS/r/rp裃H$w>7pW^ l0pvDp[$jRD\0+U7xK c_}!\0cq]vNsdywZ ԂJPE{ot]ݐҏe +}wb + 7AߍV2t%+M|L{{ n/KP 䓨))6*xjF+yDB4ϑh4!/`40^0b;IW2۫Y,ȼ0“{I\mZԺWZWogn|S6Jy@z0'*$BN/$`!2\ђmGC` k{ Ivy(Gh9Y|Tr5"ˎ\*22-ppL[-k[:op+^#\ΣX|dwyޣJt]éDgфEN#o B畅—ޜ{s%Г%)"vLeɒ[D"&ёY!dЌsmbX2 ~eYF /X\JqRn<98y Bl5J[jD*+HJ-M`?s G&CKK1zZo:XcnQ8g`xaea¿kqڐfҒ#!h#^0qU@ 7Y}p+#ˤ?XȮx&-3GRt儢. +:iрʨ mWufx.J$oEިh4ǩ8%$q<-j2r QE'az &]p\䜒 IlWD^|U|p ],P8-zwjב6݃Y/}{WkI ^,]&.-/ZXc d궬'}/f +XA)̙7" dewaviVEEbNOzgшIoԢ&A.yB^m4j '[t +O0U,-Ն)!u#2!$=0Sʒ#J +_pw!,TH FF)B[k tq8>'=}ѥ]q]{3 ܂+ R_mSsFF&ٖpP5ZiLvb}N*2*_g7:eZ1G3XnR6mNPM*#vJJwJ,ܦiiMx)cqW)/Vk+hdIs0gXTf3PXi{l4M=ʒ:1?͝=H0emrpC"VmLj{9Y!}}ccIN{Elnw9{=w+v]H+Nf@rv22-Ar)Ud 4%צEӽ%is,M6Xk04괖"[=Rnr,OIl4XT[ Bg0[,pbG؞HA"ڕ ŭY@2ϴYHM"YVWq]Yd(g!WMr$1#ڊ#ijdi+͌ьF#FI3,呜˲N І amlg̫yh.U\ +`%|o% +4Cl]wmV$rZ9Ԕl +@s{ +o-a-J~'-p\;=h \.p9Sv&ͽo4Hf1bS\]WZOWiq ?iY^ +p(&ͦOBGlfMj0Q_W/9:QQ뎗0ǔغ|WZyǭ<|[F:JDqk4axcY_u#B,$T1ؾ:I< `lo+ +"+|BLש7B=DUSCP혽yO}v᪍޳>}dkKcf@sԍ?R mx5\6>jWT6{O۞/"z}e[&Ԉ-A +X?BqpDq@ZSpFiEphH#4-EB]x+΀׈,s<$Q\=ըc15OFO"RuN*"*ri3kkǵZ\vRitfz>a:.奎p: ZZCnƗCcW]UW}obqVBW L+{@@}6M? #2]I`z';G0ǩ? ħ?eB ŮdRꖨZOY.Wme@!XԻhYt3n0tꫯ+K8S^i})U JL&ia M 0iEM4]DPM0S3 zC8eM pm&-g2i2iQ0XAugڰ%LӈBVJª$cB]m;> @g|B+fr]//ϕeծ~ bI=zAkqeqyqyٖלRˉE^BTK @S׼~ K`3ؼ!.շ,ߟg>FV dbS^yg"׋כW{N;lL{t53FJqcT  +7ͷi&(Θ&B 'e%lz͋߾bsG1lxmП"ςIb(I: +iA=GŦ8IÁA6w3L=j^j?x@pԇP1n꓆+9fP(I9])ԯ!rwSS)EIA'J"J=}4RxU^Q!QhA}z&\?vn8>vrl~1uTl.7}o ^Bcsl5}kӫu1+I3!纫{CȌ[WC{\s   0H8赟uѰw[3HQ]w']& -n0K9zNxYTZ6uMEES #m6C3JnF.pH"L [@ hWCzdse4Rdߐ mh.##j͑mȨ(o:2ie$FQ2rBؕF3 5O|Yϗn_j'9t'JEȍ.$>57pC0)zJTCp3,QC!qz>C~P(s?,Mqnv7mN΃%%O{0loT,ջw{o۬٩Vb!IO;= ϾMooimڢGgebxK[I,>R%;a@ Fp2vNuRFD$ZЅLX,2J%Wph̙-Ԉj fjkIKx q!n8Sӳ~  Ci\J:P/p/u.YAW  #{ZbЛ(`Q+`OREl WaCCߗ3h/>'?dM<=s3i!=wf -4Ò_pK~S+?&@m]7=}oDஏ PnϦD@:T־矎|g?kGp6,( +Ev97MRcur>v^?-D%9<>lG!4P3&iR׼TURUX2F_@ZPznAi-Z u/>,0i× ʂ+c}}{s߅3ғIXY}8 mx 㿒$h1V؎#)1]2PLIs +?ˇѯzmgsIC;R0NXrCݗnuvZ9oz~`6%ALW sn3`3^pw"+m ʜ}Wl~N2f2^䩋e^R%޲.p@BF]k"~B%&0sKBE ٛK Jw _):/bOVsn0[z fg "_:| *G+7,,kb91_duyc/MO͔ZwMȃ#=Mޞ[+~E |G9Xmx0 d^Y{ +f (Cd +@tz؝EF}#BI]Cs&nI\nweY ^ +H̸u a?XC#>3{V^3?-ځ'@>rkϷgR\ q_I?nnή{wGV{J3rtǹl=Oqr -;atm9y@@ H0Pv+3ڋ9`)^LEL83*)1=^{@:J'Qp!ClwE-qS0J<04l7U 3)C]/eqH`ezp{)hÆiQ'ڐ˽3/5ލeYQ[ˉ%p?AN[%)Cp~/X p mmGC1 Y YB]p{)y1,+)]\=i%"?[}u(T/H+!߻~kiɮB! +7~ %P䱕'@Z<ɴ l🮞iRX֋ıWjwA"& %Rgf 0ԋфz5 _QKD_MLXuF$uլts1DCOg' =u}Zho4'gm!6igm|A^xB_xAf&,J«^CI1+ʳ7+ +nIv(xԃY7:y~E)=ux1W|Bx-<7dxn@îA:P7 ZM0txsI4k/ +(I|Nk i uC5@ &19%VV[Y>n=Tfg+vYaˊ/n_ +'OqL`7Ƹ[u#ޭ7oھT/a B1Cr& +;x:@/~n%-+q pukEgofA\bnp#P_dEh(qۓRoF2ُl`4׻Ȇ[mtSN"'FV5K!LG/GELs`#BJ(m U(nVY${^\Ε2Ģ-` ?ܝ*ue߾}کRhONI&&&S*m4tL1̀N.Jݦɤ*'* >Nxq9;y0 L|)*io= ZLivvFz8/w;5EI +1;FڐD +U.gKNiC,@Z}`[ۗWZJv]TeH?g6qb?2AlzYEbguL^>{}r{/j?1\C} [xe7mrG D{Hթp@ԣÅ^PFu_7؆&,9{ +endstream +endobj +740 0 obj +<< /Filter /FlateDecode /Length1 8004 /Length 4562 >> +stream +xXy|T7K&$,Lx,o$@0d2a$;!"KJ"Pm)8P*R[ؽX Z1** *;(e{w9{ιsϽ%`rtAc;z:=-M dM kDw4 Wby@zН0sg]gF``.?c.UXt΢ؚX]$h} zErRNbs| +aj#1v`D5@SP a^cӕٷyK  н V|hRbQ[0i pe>0`B&,H!B:Y 0FӌAj2KY{kzǁd2+#vlV1n0PӞu6@o @SGSJ~QQm&]fyJI:yJ;id|<!=>CXb!joi:E; 03|Uy$P& ~$I"_ zPtj~Q萂 [Ca)-l7-tc TX +nC (yl{Ӟm_w_jN]1e`vyojRO CJ[󌎞o;f⼕q;] .-d$#: GVZZ%=܀lS<75 n-;<bnn' :ťNT +:ŝ]tnfIR'x9P1E9z%&3tÚ,|uݧ: M֣/Fj6d#z+Y|C#aBcQYp 0Q܂:G#faBan[/]vfA +ufڟ_ݝ]3_·wt]0뜆tunx4ݖg:>uƬUCh|ƫ4^j6^Y5>A7]@4>VcZaY~kL㣓Zome +e!@f4>R7᪇G#4>LnPh|K4> P@kHn蓼| =.ȏ=j]&? ? tA~p^^(?ZǬ?_׬ffͫYVWdjzNnXV-5.1?XZ/1?\dq6j3ؚUb5XuVY{'钳z:2{9ȔzdeYͩifcɬ`yTYȺeRdX-9 GtdyR=FOeI 9jx. 0ȁTZ95`/뗵9)S-PQ/׃1@xOB܍G[R(;kD~.K2ȍOiQII"UYqeuYT9wqR9e AUz}!MUBqvl$BǮWt +U!IZ +B:{XJZfІ]*~B>G` Y7:!lfYcX.a6 q,acxg0G3 ,v`F؋X%xpyl8ae񛯾m = Xzb//.j"܅{+qzl+؄m؁؇88bz2Y.n'\l F0/E3t@ + Li0ら`B*]A*2`H. d%d4Kd?E"."V+"a(@!]MB_ D"ї%]@wHt=T*']:EဓΣzy8їΡJC_s΢?t2,\(/F1}" /PA>@ ϕG%Bg0C n@) 03Can0\#0NÃQ)nh#QFb|t +^)Vi*|Cm(Xj Om$ƪt&ID:n UtPM'0Q7c}J'- -Q}ɘBL-Lt!|c +tScoc:wPK]Q1"tE z::JG0fbF s}JQ:[=:٘O0tsq<,hPb>A4v:&ApXhw~܁{h?ď#܅%~%!>ݸ>=!J`}>ڇa}+i)x +ڌm|H>xhvt&!ڄp^ױu|BCNҫ؏6zp^AWqi#3ڈ#8CqFYzq^8Oz'q^FA/.%z %a9cԊ/@8tԊs@8 ".0#/Y +,^?Xm%NpiX&୚:%TS=qcTF{G3bҡCn\2h"K߯o^vGn].|k^nN%+3ÜjJ1:l y/͎rE执'svGTRrkV sx^e0oi寛L:匝#g(NH]U}(v..r8srs|DrKeЮ(TdGePyZ`](D : r=@_ s"[B_9G^Vt)8<ãbp˙uGS(nK;Ɂ?ZGgB_4|-Kq)^.v5TL@"-+7;<`O0e] BŽR_bl:iAܮtNw TN rӯ<ᘤlwJ͓1)(Wt09ʦ_GcuaLXQvT_pU9R?ϖyg~XYҍǗJ|MU֮P(dsᏅ.}%WMqQus#o^Ȋ-|.v=h^ >,_/ŗשK].n%}(c21ڄRu_ʊc'Uvq8 9TAK1e.Wnr J~D:$&Kk+y^ wn,RQ;E\㔥\vn^qU]Ճ_r8Y5vu:xxXYU%l٤)@ZV\+0. %zeUUQAAPHJL 4Z +3c -B$'N䅀}Rm֣ŔĚULm ( &o,db)ZR֙Y͂Z[Xs"+&-ʼM6)ufd0QPLi_LT{K]6qrQG + +endstream +endobj +741 0 obj +<< /Filter /FlateDecode /Length1 19488 /Length 9830 >> +stream +x |Sվk$M'hS읦)4DRPJ4b mQ<(u@8pPQ@= +vSh:1 $BA =*+~ν޻纒ZaM{ovhS"٣=c,ifjQF&$ lnܣjϚ5 F ;WWx2z޴9wtYw]; +_WKi0y> $VΪP2>4|Z/M@7"c?ؠښzrT{휊ꁨ'@GQPצ =ZP0j$ ܞI\#íkS? VثF~=V}(PaíqBib] 4pC/l2Fc54J-e״@(kk0g1-:h)tt5ΒᆌڇsI3y 2(gWoin8W9!= v@z57:=L woNUY[3ݖ7T=爺z͆K 4bjI nk̄^:o8ړctOWiLZz1{GMŹwI}68G/O5z骡bFB5$!"Fc"v1Qz_N0tјN(t6D 7kb"b`@LTW]ŵ Fwc1 wWԹ;=XL{kFx+N"n {o8i G  Hn &a\B~VbR0~dħ59Gbr b;a^'-3Y3gTUNVQ:eB17 2x}S>{%Y- fY٣[1.KhCTȈP}Nac-Ed1"E[|FQ>YɽGK|'+;I c-rXdeI++_hQ~T#-^9.9lY!%r;)$;Nò,Ya)v4gYSP,$n8LL(,9J%[¬9r`''d6{S +**d*Q6,эRBD7r:,M (-E[}= +y>mJ%[j]]}g!sNeBbsEО?ޒ?#4tm~u}U[G +*YrUUA֚kɩ*59L,QM)1YV<j[N@~'!rb(0_ +sj~ 똓f>uËhbE&75]g˵45Zܦ&_7Zdi30OSmNɕ[܇-ZQܦf" +{6E)YO7ޜsQIV"BkvE= 㧄fdzɢq2ZjUZ +I~Oߴ 4 +F(D,@ t+: DGnP4Ae}PN֣,`P-[@ Uq vtjj&?؝ZtbjnYB ܦ͢F #.@]Ӑ `Be8҉IHכ&YEk,,-Si%ed3c9yHKNBW *reQwzC'r=xaؕ)̪0k2jj*Q4־D$iVfjOi,d%Mi"fUh7+e>Y,:Ri,=&W$+M|/ŮhfI"YVu:Jc\KJd&Yюi>m'HAp>u־5)!Ey¢ފz`dH= +LMM&x5WmKS3mRk*ːiUHERn +ښ)b*jkI(kR/r5)Iق)@T6f19WgESka)cUuTVLRfٚXӈg-O 5]9`07D4l% +='+ o1)ě # E[= opAb_u=3(agQv Ie' hy+;֒tر[_{;7οct=zA?$,GAC>)ΣaeG@vs(Q@j(PKbs՛lƶ\mGF;3o^e:,Z:E;Qǖ-nml α%РKZldkA[ r-XUֱuA[T5l 5Р d/Hf[̞_=ǖ'$,{B:f˄Olb!:'0Ab2>5|5 =p' epepe7lUY5wì`[]~EZ❫3]P]hewa7 _IaheWcup:'"VpN^^ZlzCbd`[鯱iMk\[-4L݈ZB;uQoVFڊn=yh'̌N `eʐ0!A6pwlfV&1o2' +JS]d YzMJa`?J9622 + +7U +0nE +- , +2Ea7BRaQRpYS¢Pa]\Qϩ^6 넑w " EE" +E0kG/^9:%"t~N\Xzmڛ(Jp3111 8QFi`-~)owsF7`*݈Ut#ҍ`FҍHQC7BC7^KtR K7`*- 9K7{z~%N3zZzL뱊C*]t^xK^&c;W tJ 1*;;ĊΊ1C]lIjl$6 l6IаbV&It@UBl,h!@#0E!!!ӅLtw7FF>027h22I ivgD96#wFdHUH*ɗBb9;s3țj\wH] W%Y.H!R{R$)rIV D"SȞBTI%*; +HJRBd]R]7KrZ)ȯ~"-ORFWriw$ s$!"VH/~=$ y zYU$O{o/~'yuҞr]*& R='~M{MRs IL(yUh.mKwʕ"R,(K_WY$LtK^pikk ܮä4ik4$@ZIP]'d H'LDipCCJC& ?$%D=>FowGzNS=]mS3NJ(*iFJ#tf4|&Qb_ Bx2Ė n4WbSA'@ZhR BR.1K^!TV~o qŊ֒iDtcz衹DtHoh6{d*{_~GWq2=2IlZ֪׳IgzrghmNfLukA"F 7愄$Ou"ym4=V"Uf^fDSu Pl,"@DcQuW֜fUuil)4[ kp8^XE?VOrЇP}k?*2o_Xr*J,9%⹕F\~{ҲJ5U(-J%[nsjYQSi.sWd}n_ŗmyaAVu}=tjcYj_/sj~A+_+_ L_i#ӛ59,O͌50n ȋyKi դ0uR0f6m!/v L%ڒ 9UWuuuuhh7ޖ+K.ŕKϦ=heuj5K&ffeul5tK5=9}&ՒjMMN],UJhDfy܆7]\ƵԵʵѥ cLؕ@&$,HX*acN5LvJ65xm6R___-ې]]Vԉ<ĒPfCЂPm [2&TƲLե4|u;\Zuums:sܺ]Vר{TZZ^ r#^B5jh0 !l)<,[b4[ +gKym ,OFXzKAg KA,Z^,?hpe'.=ٯEհcVkSIjd@s!|&Fc@&T1T8H:~{LcS} 6a*Yl>$ $ϐudCwI6N4XͤGh(ЈX?Z/i%o5ZG{:»/8GxN9F $"?)0Pxx;rһ.ͻ;A2(b~>HL!d )$RArꨙΦg¶ÚAZ-OC bO`*Qc ^ĻJ$Bd9H~h6ͦ][Ȗj.jhokoBq3zugelkdl%{@pSԎ.֮mIRqK}.y/iG{[ڗ|<ǰb elīx;_{B#>Ntr3#yd4GƑ dDI1RRI*H%Gx kKU*BOQr%SPPF㨕t(͢Yf:B:ZKйnt̬,l>fhƮIո455ivihh.h%mRRΤ{JQwZw1DRR2?btݯܩ]NEs=adXE=>N#L&VfY.ՐM$m:z_h"z$k#5,O{5]5iOt?]c#Mծ{ khg%A$.G3@{UKEX'@ΑDz+Ja.F-#5 Z2F%Dd0>df1 W#I]I=G'u@BNˆW;n4,`CFc/qˆ'.cxQH88+FR.E@* G4~F! Nd'H֨59x%c11oi<ۤ݋$+q+rqNt jȀS|2c<}01jt֦#J|J#p+1,k_Cf?#??AW$#+P8Y'1sY-ib_%J^xkBXFԮ5ӨUGKRшUl?NS{w~O{%"q0ˆn0>T810C0inpd"Q /&c2` +nT?kUaoln] Ј{p/xMXG$X<^z"xo-] ۰;;a/>|mvGц6)/CFԤePB`~ފ\W#oG(x;„ G8DK"%D!_Q"_DgtA4.Ѕ +#_8+ iA!{=ogX V?2'>H?^'ؑD +z}H?~p"G! p0H8{a Äta7a1t 42!9d!7sM["by`$27GͿh1kk9#W(H Y|&`1IBz0 / +(8~9,`LgPI4Qb~4¿tL_SrS~J?ߐm('E9?٨'1 a:?z!PO`. ܎8rq܉Y8/]ncfch~ ýpx\ކ GQgXa a,0ă"?!3l5:^w?Bub=oao o+@](|/C3ߋb۰x"?Nl{ [r^*^绱oXO&ߍ}h廰o]8 A߅CxħBƻ|'>{|'}!!ڰc?wฐ'I;pJ/oiq{vK>5>b߆sϷ;=mVǧ|+~'[g!EpB^q>qߙߜ_ +)~V0`YgL?#~F0`3O L?-`_!~J0`)SO +L?)~R0` 'ӏ L?.~\0s?L\0`1cӏ +L?*~T0`#G/2_bL? ~@0}`~ L'O0}`' ,`ǂ{ +L+`G $G0}`L#G0}`n݂w L%K0}`N;w +(`;ӷ oL.]0}`v;ӷ oL.]0}`6mӷ +oL*U0FLߙ߆+g=v +endstream +endobj +742 0 obj +<< /Filter /FlateDecode /Length1 10524 /Length 6255 >> +stream +xz p[ysox {A@D$Rkw-@$%CWڒoks7{'634;nMfINt:NgVIs>$QN'S_?G/|(485 24V/777K+ {K6.^5< zbz[tҥZ䟔K֛H^|q +򅫫/UkXܼ3@Ҽ' !~}[@/2|pk | <ď znib}~@~eYG[?ٚg1{Ľ1ނ'.So|w뭟l,F~T N/@6mޮ?ױ[0UNI8K~A(~@9{ʟoO^gKg%6Ԇ#vqVmQ\)WfW$!I> ݺ5DB䣭wwpo;[6{[?#[wmR + +*T<'a*\{`ЄݞSnyH'k-/9~ +-+dnBDmӦmZ|G~8+߁2_f[I/֢k-C|}~?z& znRĝy>ٯ̶Yo_=Ng`>?ToBF {[DFhrq42gCέΝ\xlZ1O +j^ZmPs-/Qh]67w.1k)JX^8furm^859TsMRu%egZK:_DO{r7'UN3qUmSmJ)Όry^qO8fcbS-.\foiF6Wt^irrU .e,X>uiڋTۊ sL|G~;ۣ`VP,6P-) ir-;47gSΓ5\ޏmrҹZ<뙸=f9֜4w:5{J>S~[$ V7&VfC^[o.l==Mw'jڍx=v{_LU96+ܗFIr<$U5NS˵gfOt/XHnswu{ NW#o# - skܧW9+ܩsҨh w~/T*V/ecŃf Y:l#f:j{̖$^%:fQ-Uf!aSq|xYf'Xق5-"٢ulIN-YԆRD6[̖CԦM٦fZZUب7Lnei #47'u~!"[|tG=#h^<ܝ7q8=< 쾓vS'[y e,^0)mPmNZh"S/]PIO-ᔖr:/Mv>&¢^]B 3499Kq!QͮotMڜcZ=gzE4^>Q{hTC [VMF ΪRCġv'ƚi\ڌqZm OS4 }9ӹZ$ժ=KCoX!d/\zhT Tcmv[i\2:2Ч&-~n^4m^_ mM"HNrڔw5m).=}ۊς;хꬠ8*M] ͟iS\+gv6G9l쾃VLXvSӦlNᬚ㹬ū;-]a\6pfKeYX/Sd,GzgsBtC휷Aa yDQ bL>ڜM-%S#_Zy`6kۀx¼v˲y3I)i9#F5D]`YA A<#0YL`qN`A4yĪbM`n;b >tmj&kK=rϚ9a)~ɧvWć jSaS@zݦM K;FJ&on){I((+32Jw$t/&߃cE"חk@@&@^"!c" ԢKEn"c˵rA0Eznb@2* TgTO3ˤLBQbY,zBE #4NLx>TzHłh-#2ӆ!/f1% &4:oXLHguecIyjHO|WK痐/.7_VCŐ+ɛ/=ۍ!w.S^CJ7oz ߄gэKH ' n[Zz\+NH.2)M27 7gRmpu\^N!׾k/;}bqvx.쏆= +|ïY3('t[-$GP4zBBi4Q:yQu YlA͠5*Zy[ϧxч8Q( 9,b`]2$BdɊ*5%L=g]%X+oT@w7 cT,$2E/qI '፲d$)U1=Ҏc2#Bu٫c/VxT=GL(Zz"TNqN$vt訷dA +$ߎj**uΧ WHqȹR,b9DEBa{4[xp$[َd:h/4 }HuDS'G^I2F%NփMݽr߲DtyQyf{ ʞ +iʻANc)-a., 2 _]RSƺ=q</vy?uK#mi%Jp>J'J8:s 7war<N + r+FD8VnvqΌnO 2Pp#83b0h>b!-b@ H~"mvG(_vӮ2?oИvȊ(==d!9"yl0?{ӇLdqBedk\a5{/\vW^*uȑ[ & +wo:/zYbH_9'\0Hy>o=D,Bp*dg"óif`]61O=9?;3}!# \{ڥ7>"m'hDžBfH0I^E3Ij`wdbv^;^Mnpy$#_v9Djx|%#=>I`G}2 \CFyšu*䷩SDOr|KK.b(.^|NH,=GHYS'qC<Tv%@WUER@ҋ(HӋ2JDB~ǨBZ/Gġ5u`$7d$=a &p¡v2v^VttܹEpeѰ q2:"*TFCOp +eF{oÌCnudOkyN,'N~R=~ҝA5DIQTٿVC[w[=0aSDcnSm,cvYI"+@Y:<=fғ  scNȥ0%COؙ8 PV.ͥ:~ 5 19q 8 +*,DeQCāT'r'zۮWGTs>]xǣ߇?WRK软/a 93k‘ywO (PqP". .-qpx@u$ GJTnܶ:cϬ,L؏$7c(!zm,iq%9Tz~?hٲ6O e +Kdz!+vL>DqH/'?KC6'NziAPmL ++8X<PnHǯ811o AO DC[w0G +.XO/t(2U+  sYY&+*(R8P/*]~lʦg!p(l`!T,!d0Fl1-}!ҋ==Ch$=JH4^cC -K*y +RP.ϥd??$JדP."n=r贛!&[wIރGARc@Ѷ XOJX`6b5P2 +9Nja" G}*g0*3QqeK^g}aۡѯN׸e  t%گ$'S&DXFbGB0FY:"8m=-BpiȊjLf)^H6>v^M92O::f1&^<93t]8xH}7{ON=CKG7MKeRVv:\u $ }Zi!Vj`C}qr@E3^8q Vu/FqbW=`]ۑ@$G}qǰR> +stream +xVoz+NC:a8N N03^ l~љP`&$m~'I"UECA-O$yKu)$Pj+ԿRyUU =k$Yiw=|݅Dءƃ_:v:Z~<}4^= 뿀/-X .o<`V{ 8q0_H-b0Sgk~O<]X{~,D7v\{s[?8 &x}a>3`@/ 2R@ {?})1o~̾Vy`^&f֯3ɉ>Ɍ^yէ&ofzyg>ͲOofX?/EL`-`.Z{!xuӸ.Uq'vYIYX`Ӱ `@nX;ZD-~tXx0C|hjM-/$m}T+uHƔ2E^|;1$n݃~,odAm!F]GZoFdh(.M>MLxD\:%?6(<aM]Ap?#7D'd,60Ӊ¬ӻLt!Z0m붭ۼme}|"TG"6aԘ)i9Ey.%a#)!X####tz-voW7 evզͣ{<}!>!^l6~kիFwv۸zegԇ+ͮ`|%58rYtgWX..W:X Klpwp;}FcNG883؆؅2&;3!a4i<'q\)1Ա\>O^ M2wв.*YGR|ϫ|KV$cV|Nbޡ~FѴCk4PJvI} G}I В&jM/P*~lէo sC\>d~1DҀMJF($$Pclm1HD8^-F! ϛ~o6$Rݥ10IxKBV1a++ԫ\Q$BW$֮XmMڹMCYcvhHǂ tlGۯ8vls:bugޫnkY"C<㐝EӠ~+ >!c6c&l-#:N-8vT]l78vj-'Vs im(=K!Bo%MMNѡ.PQY5\>phl> +stream +x |Tٷ7ْIf2LLl@ (jDA(jEZW2LqIҺVťloOs^rνs޽w  i666g9{V vΊee|K@XhK:Znn,, Ų^79\wO_٧_rT4flU*+ehI  +>v{*X&Aj*%Mqd#ɇGY<0˛ ABG;˛7\&Pļ%Yd?HJI$ըxsoL&i yJY S¦@-zImJ[1mSGcl1 l h @!JFoК%Mɜ@cS2;ZBM&[65z|hI4 N"PD4($ IRw n)[{uZ쎈qn`InVIIy{G%I[C?zM^]rorr} %ђ$ X;.)ͺz|dIzi[k\-񲞰^e7/R:&ugu&s&qJ_*'G&ANwր/Y jbړVfKh-fPha`RYhIGFNLJIo'44yv(ALLΝڴ kX+f,R޵_ I:O5 _Q$u8DLE4 I5AH[]7Moi&Қ$FMUi %ꩭ=)HH[v0NaY}s${G-OYm;?j?:I {^?ĩ[Mk;va P(yPz858""$J!sL +K +3MLiy?˥ 539:2<>fX|XkӒ|N6}Z0޸kvWxm\W$]ݖ:ђ-[䊩[$r[ i)JhCG}ۖ bj6/ )D&'Vl;\py%AI(iis4&d* ++I349im&muFpHZ9iqe;i ZE)D$?@~T <Pr,ƭ؊Tp 0#hb J܌d3go UC%}ԧک/ +IXEscB?+RH i-HX#ߏ0* '4,oIHtr>C/[|!O%?5bpV<#H3y^.χ 1*\; _#/)'\r>#o<lnjy| |0gb\''9KbԒr "M q\#w ,|tpl#rNY~SWf!!Ԣӱq %}xUV؏I&'R@JH-9sZrz}Jߤo8r"W5qgrWpk$w>UqxՉ9TVݦi^N}:P׌ԴhiF]F/i&R|#2MM46. U˸AZ +⹧0o')~Ԍ|E?zYnµxs AWS3ϱ "$q6W~v0 +l28/;q'$H yMdqQ"7T~}xrz7 8U+TTS=zN=lijxx%ђzd aƣh0H2 +Qz`rZu..ոwa !f2.CgoVYҮ24S|ld$Z@ފ*LF#,C'~r;{`FT({8!ÌMƒn AbT^F8o:To`Rܤi^]*?stZѠ!:U'vUmz uZTBX9 k0LY:B"׃F:x`2k0Tz|jy kXm[ tq88灯D7G8PBV}jWZZA蠗WyFlYE,w*AY-Ȓv[Cp"nx-\ OȕRD䃈) %(@9bHL> +`$T*xB|1R;(1俣F- CQ- +Fȟ I'Nޏ +> ~L@$$7cqSpZpS1A 'aNV44˟L?é"4EG~O1OюSOqg4tU& +2c͐?<Sg cLc,L#YGXGX9G8s'aCtLtLC,| ɭc>V`8W+X~K~[~('X* #UX&X."# +{q)Γe8_~+ +\ +Ņ; +wp5VO|-.:\"Ry~pry~+=WʻkݸWɻA7y6n܌k]u.܂ɻ ?_:mWܮ;^~ wF-܅ [[M[7q/~)*w~[7YIZ~[pRM~ +˯~)K~.y'Ҹ[މm*x;_CЇ-CJ~W~OW~O~ [Wg< 4_ g] SxH~/ae#x/%/eA~ Q+^Kx O/a'_xR~oO_[xJށiyvyv+xwmS>!?xS~-Y ߱K~[~_( ޖxG~_]i|oW~=i|10 #>O?>O/?}u}ks>Έiބ ^uM^9h*OBhxV6PXxjyf6p +&x[r)RK3dq!ʥx&\m.w%Ռju,nMM/t*rVrJLjocS&W}&fHxtXVH]jXbx+d +^1UʷbUJӖ[X>M GB!r{)hy[XIWg3rilҠ@p% N 8 rpbvKE +EJs:w+ov7<*0w5ʸ?ܻ(eEq -gX}Ug jG ]ܛXǽ[]>ne"(C2,o~]vBWgf- eR.`^0pBv2\ҕun;?3rwEHQF wb%kWٹi XMõ4a7 <,4ĸiMk5[t%)s07hO9JO +'? J8oթ8u;7MN475e('/MVbrJ dSYJ`\Jѫ^Qf%92d\qnrN^e\%Vsd{[n.WzG&.$PgbFbա ]HF2WÍ@-7r#8ĿctupeXǕ@b:Vs1l:bŠ \2=ʸ(VsQ$(vsQX•`W +ŀnFUXEVU*~jʪ*B%q,J* +;t]:L'ZthZKiF>ɦ]F4suuBXXX"`7 5kp/4n̽k}IUZ‹$Fjpk9^b\-7; ] Z `:&CgaP%}=jUCݥ^^ޤVV#u C~o}I&(V7N +J|oKK8-J(p(w 7T67ij797P :XG_KBA#'H Mw*܉$ݩr')2;@}M{ I")uХ$ZP\ RJKnLЍM7CnD-݈%JLdt#[P_7# ϐ\fgYMބ&p`1ބZT#ݐ!wU JS6`3)tnQB1Jh"c9O أl:obrM&n=O2P8lV-ML-iz_PӴ7CReQg +oQ +>M0}0=00a"ϒAӇAӓAmAӭAv.077ozooo7M&YQA09Ckkzkzkkk5ߐ8$r+⁄IL)@Rr:L>U\#9B/47\''|9bftB"5-ZF5b2D*>CLSUJLTq@LoSyb|`3!RO(H21MjRa1MfF:dTp͡LJ|C$be {4SEzlrHP6(*:KokԔ֒eaZK6?iN$%~,[K]5izd*+.NDOWbosHLv7w*%>Ļ ӔHqS$∸>fmygx9C/ +7_'.kR) +*d'♝E3Y׉J(~N]tK'&&(b mtjj-ijRSB0!NQg~(]E*EW +"VH[6"+)bn&%njf"[ᦓZ +{ w;Mp+5iac[[C&;vVZGi^G#yՊ^+i w̞3Y+-c[ne챁-hmֺUט+m +jl라fTCשP[hjТySw00U>s~|Ɛ j5!GoqO5ZaeO4g E-Y|Ac& MƠӚn>!s}`8vengZ|3-,ZѠa:=z3 +y.08`Pt aLzKF/E/9(9&~qś=vT1 {(jv 0F:Xu h eȏ78YGsޔ3gXluH/dY!׋Y8*恇(o( gAp,葥f1OG؋a#?o(w ˕ev.;%v؏e?\?B8Y ;-9>u gͮ^]fq<2xAӂܣCp~6N!fxLYlyz=?B8 gdq,b`ysV,ȳ /p;lyS$QŊѡh0Z<o=Db.w~ x5qؐH ;:EPV 1-J w@B~ ??v5q@`-rڑ9h阑!|a8P@QePp̯-2' ;U놢Rp~1BbuysKQT|~ k/;-HUE痢[砼( +奈k/';-:x0hq*ǑQJ +|Ǐ]ǚ8K_b +נ>JSkҜVʂ^?FUC:"VTOT7TzZzzD곆|VNR[x>`! z4KYo>M*JzuDDŽ%،}eOLt7hێ؁~a`o,s}1y{PxP=K-( UR5. ) [mB8'ppYS`fCs : P_*حV!@>Zhf ]a&fyV +Y͔',!{Ip.Si-N|\)^}vY+:)#' aoX +Wׅa͵g#9pV Bumu0ZeCͥO/7Y񈵪ZUO naD#TWkGir*F$ę+GV$ +MUz{M,Ѹf3TܳA.>RaF~wHTBN!ߖ;yy0 +۪T $yWt)-t1G՜sk=WjZVZWڮޥtOgos+L+ˡ#3cVb +-xieCE=7gghӛ M7EmmD?eO^%mX)j1 +iuU{&>u>.G"D"őȚ5Dy~5<>ҡVAZ.%IuZAWeDo^ʊ_!mޘFp8W߶u ]W.?gɸɌ`YPu&1H3rVCC̚>3tSQPuuA|z۳so~7Z_@kZ~0_,A\ Uс11A'Xb U%@ՙ0zpwwff=\MVvf C5 9me)c7-%0TM ޯ|6ěLrh&*|^î֨9+sgpyf L9ilSHpu>Yϻ䒋iP`bv|C~7q`+ٍf#2S窅p"Lxgx:rb妫,W +Wڮʺܡo$ 3CL ;xo?my:dːq趔?D!fZ>fR6N7TFue+u_J$yRj~DOwJn5a(Xg>5p0< ++,7TФ +U̓%hէ|[9knhLuJ=*WlBGPOG퓅HA`Lܟa[Z~bUUG";ޑư/^g D܂'-"~&1! +`+DaVθZnw8`UZeq۪ܶ*6Үr2o`70|^_ +{b?0ߜpper#M \|?ua2dGԤ\7hSrpw/﹃L$~^T&!(9W=%gi΍Np&朐{VԂJPe3=x_xL`GLfii}*fiTŌ7࡞hoWhŹ>j^?'[;?spԓ&HFKlח&*IgDPLw`( yDL?)d+jd>Vo]v{i{[VEh'ekspa^#91qFc!sB+`hU,G (h#t:fhJ8k92/4L"+]2Nխ=㽺U: +4. R*tК%!≠Rq%hJR:T*{Si2*"};v@{{Q[kڻ#Bp_*EBt~}FwMQѹP^2|k>^]>FsrMD7Q|(/P֜Cڿf Yݎ;C?\`zlԑq˙Y+]lS.3RqzW&.8'7|S>ҍ.n6LׯSˎ׮_0e?=ʢܪ5ir%F^ &eVvUbkbeZ BhceXH F%ќhѮئap)$E!Bv5#ti$9+ 6/|=™jҮRؔ BTm gvq?+\bS!X"CH1[Y6J.SWV8닩̫l+\]EG/shZXrޒK̫-WZ)6nz}J^\b-+M㸜NCU&Y)^Rkd\i0XV!w( +['&}v5_jͦ^DY-v_o'6\; +Umho/wskB)EniW&*KYĩ\'1wcӇ]VvNajrv 2Ι&yYU :jg:l¹̂d-Uz Z?ӿE%E\<24\PxHvD Yikhl0aT+Vx`;%Sݙ5?/n|؝$'.' m7ز2&2֘lmq&qX\4z( O/~9 ŃNn8͹qͦ5~LsE[y^QfH1ԢFT 8BhdcP<'AQD-k=f9H|qID5j}Lrk-11;\˲= w~_$bo쎒@РwXfb~d̵٘Jxka0zeuMF5u(F1+Ӿ\fl+b{o0ЮF!2Yh~{DV~F0:+̤*Za TW$ẉƀ_p9d{+ /w9&ƪy+ksp`x&+g7Z-b9Ix8M1a,&BR[\wEmT/r]J=;Y CE"q8^Z{iF]ѻ `T5E&aS_NFUHD/&j$;QVCNԤ9^rPyƼ؈r%#Ad)gQIozǓݣFqR %KE {J%^2XjcR/>% +J@,(P!ͩ${8QϮ bI |H OgזHEԲ,)k)QBKiu[`lօa<]_ID2\=aX?[| KD#P&yjض ,'&"6vT [nGDbgN2ʜ-a+3&#WܹbaK2wD{ɣKc+k~Ҕӛθ謞ݓl0eꖨwyjmWbݼxD1s;Y5(XJKKO7bie'fKK.*iO,Ob[zʾ*V˯Ϳ6?ߗ'_7ڻlW!Q?t@7D6?22s$\7tI>҆AMM^HI xT PJ>FL5 SzU#I1s~2\Sj5n &'eϲSPS 'rrl6UF%/.H~ib) ZQ!1 x'x胙@GABP(- ,!//7?L7zA;s]((~Պ:`bP A4T>3!|`k˵rmRujR {jiR(ӡ]*#e.WQOХIWoy 1B%gnK|R+gB/++Q/ʎ=D80:-T7cj`ށ/n찺:Y8p=)^^aA;UnA|LðV_`¶^#Zl *|ZJ1dNG V=η.'y۪ kd[ jT䕺OP +q>nXܽB ΅ֳ24}RYl$mĢԂ6Eb$(5E!M>P8|+704dh"aNF@+4k @ 狶Mjml{l*H05Z*Xb[9>bߛoW1<ϳ`V1D( j U:5*+ZDRCju*JPWQUCm3|f'D6[h'-id/Es iH z->SOAIK+J ֋:+v8쒯.؉ڥv.UNإ T6"a"ф]*(L%0agn$XٹE&4ҨTS[5:ЊZ6"CKJ30CEJ,G";d^ae櫫&p_xGcI +P &%3;!3/&Y9c@?s9,#37/[jJEvcE3,֢HlKE>{Fa)[k/eg[pr=6Ik +j *\-sꄂVN>aĪ[_ČNʞu-@nTWjH-%u̴5=xFh6۱=L]rCXS[ b'v@e%1(q<` 1T˲Tmfͦhv_lTͻ7w;[#xˍ}m#EHh y`"7ssYi&K 6֗I$cٮJPf\ d \Qr h>dd钾z6gx؈=fǬPO_l^.]byȨRIӤG%DZ~ K $^nKI|,ez(FNggKH?HlT{s5~Thq/8:Bxd%6x!z00Qc(^ ==.ZIC}*ehv +cjNOWJ|%( Q/wOŗdf]2)(%Nt^^lp6;v^t'-I !"q\嵞UrcE}Z73۴&-_eSÔA yr 57Hb gW4#+])> lDѷ'51c5J/'\O';Hu[ 2'|{BPWE#K|¥Cb)-Opk{]k`~b5ݍ]T`/z]{oy] ->R lgN%n$\t@ЇXPH#bo)CN ~`F%=u9 ޲t9)_'7JSﳿ ynD ZQﱰ̅hBA7|@3tXŅ9rALo> w7Vc5S<;;;<; d !L&fw}<Dot<Xܙ60m4 S61L喸B '/P,c:i@ +ja5Y?%p]5暟cdjƬ3dxhs5rU^w7+cKi}XOTF037qtY2`B]i`'db 3#( <-J ( +T>L$Se'o67!q CB`)TPU`RHv(M+DIE ]dQ ޕ0Z!1%i%`YI#.tO")6K%x?4x3'jlpO*EG:%p\TMIxF&KA]4/qYf׼\qEv'[vd 4A溹4 vkN{݂\2:@:B>$5B~ٴPRgtlGEm1eK_nz/kցoڶd2\"^*X$#z"X~2͛?--q\9-4 *>HDq !HD$>W0~8L{,Z"$bEâC(p(X l fj󳳳IJCo`sL)% <39d03f0197fbfb674edba2fb80b54380>] >> +startxref +1245342 +%%EOF diff --git a/docs/wiki/references/parametric_post_retrieval_confidence.md b/docs/wiki/references/parametric_post_retrieval_confidence.md new file mode 100644 index 0000000..0cff4ab --- /dev/null +++ b/docs/wiki/references/parametric_post_retrieval_confidence.md @@ -0,0 +1,64 @@ +# Reference: Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking + +## Source +- Paper: Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking +- Local PDF: `docs/wiki/references/Rethinking LLM Parametric Knowledge as Post-retrieval Confidence for Dynamic Retrieval and Reranking` +- arXiv: 2509.06472v2 +- Authors: Haoxiang Jin, Ronghan Li, Zixiang Lu, Qiguang Miao +- License: arXiv non-exclusive distribution license + +## 적용 영역 +- confidence-based reranking +- post-retrieval context filtering +- future CBDR-style retrieval routing + +## 핵심 메커니즘 +논문은 LLM 내부 hidden state로 confidence detector를 학습하고, retrieved context 주입 전후의 confidence 변화량을 preference signal로 사용한다. + +핵심 수식: + +```text +Inc(Q, C_i) = Conf(H_M,Q+C_i) - Conf(H_M,Q) +``` + +`Inc(Q, C_i) > 0`이면 해당 context는 target LLM의 answerability를 높이는 positive context로 보고, `Inc(Q, C_i) < 0`이면 negative context로 본다. + +논문 원형은 이 preference dataset으로 reranker를 fine-tune하고, 별도로 CBDR을 통해 query-only confidence가 높으면 retrieval/reranking을 skip한다. + +## ranksmith 매핑 +- Strategy: 새 `ConfidenceGainStrategy` 후보 +- Algorithm: `confidence_gain` +- Public API 영향: + - `ranksmith.confidence`에 query-only/contextual answerability input type 추가 필요 + - `ranksmith.confidence_generation`에 answerability confidence dataset generation 추가 필요 + - `ranksmith.confidence_training` task type 확장 필요 + - `ranksmith.strategies`에 confidence gain 기반 Strategy 추가 가능 +- Error 동작: + - confidence artifact/task mismatch는 fast fail + - confidence score가 finite probability가 아니면 fast fail + - 문서별 confidence scoring 실패는 조용히 fallback ranking하지 않고 실패 +- 추가할 테스트: + - base confidence와 context confidence 차이 계산 + - confidence gain 내림차순 정렬 + - 동점 시 original_index 유지 + - query-only/contextual task mismatch 실패 + - scorer score 범위 검증 + +## 현재 설계와 충돌 +- 논문 원형은 open-source LLM hidden state 접근을 전제한다. +- 논문 원형은 confidence detector 학습과 reranker fine-tuning을 포함한다. +- ranksmith는 closed LLM API, hidden state/logits/attention 비의존, runtime training-free reranking을 지향한다. + +따라서 논문 원형을 그대로 구현하지 않는다. +ranksmith에서는 이미 구현된 structural confidence layer를 closed model proxy confidence로 사용한다. +runtime reranking은 training-free지만, confidence scorer artifact는 사전에 생성/학습되어 있어야 한다. + +## Do Not Copy +- 논문 구현 코드가 제공되더라도 그대로 복사하지 않는다. +- ranksmith 구현은 public API, error policy, metadata validation, optional dependency 정책을 따른다. + +## 부족한 정보 +- query-only answerability confidence의 canonical input schema를 확정해야 한다. +- query+context answerability confidence의 canonical input schema를 확정해야 한다. +- generation label을 exact match로만 둘지, 별도 evaluator를 도입할지 결정해야 한다. +- CBDR retrieval skip은 이번 strategy 구현과 같은 범위에 넣을지 별도 spec으로 분리할지 결정해야 한다. diff --git a/pyproject.toml b/pyproject.toml index aa992d8..0bb20fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,7 @@ select = ["E", "F", "I", "UP", "B"] python_version = "3.10" strict = true files = ["src", "tests"] +mypy_path = "src" [tool.pytest.ini_options] pythonpath = ["src", "."] diff --git a/scripts/build_qa_confidence_raw_dataset.py b/scripts/build_qa_confidence_raw_dataset.py new file mode 100644 index 0000000..6d50710 --- /dev/null +++ b/scripts/build_qa_confidence_raw_dataset.py @@ -0,0 +1,498 @@ +#!/usr/bin/env python +from __future__ import annotations + +import argparse +import importlib +import json +import random +import re +import sys +from collections.abc import Iterable, Mapping, Sequence +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +NO_ANSWER = "__NO_ANSWER__" + + +def main(argv: Sequence[str] | None = None) -> int: + args = _parse_args(argv) + rows = _load_rows(args) + examples = _build_examples( + rows, + max_query_items=args.max_query_items, + max_query_context_items=args.max_query_context_items, + max_context_chars=args.max_context_chars, + seed=args.seed, + ) + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + query_path = output_dir / "query_answerability_raw.jsonl" + context_path = output_dir / "query_context_answerability_raw.jsonl" + manifest_path = output_dir / "dataset_manifest.json" + _ensure_outputs_writable( + (query_path, context_path, manifest_path), + overwrite=args.overwrite, + ) + query_rows = [example.query_row for example in examples if example.query_row] + context_rows = [ + context_row + for example in examples + for context_row in (example.positive_context_row, example.negative_context_row) + if context_row is not None + ] + _write_jsonl(query_path, query_rows) + _write_jsonl( + context_path, + context_rows, + ) + manifest = { + "source": args.source, + "dataset_name": args.dataset_name, + "dataset_config": args.dataset_config, + "split": args.split, + "max_source_items": args.max_source_items, + "max_query_items": args.max_query_items, + "max_query_context_items": args.max_query_context_items, + "max_context_chars": args.max_context_chars, + "seed": args.seed, + "query_answerability_count": len(query_rows), + "query_context_answerability_count": len(context_rows), + "query_answerability_raw": str(query_path), + "query_context_answerability_raw": str(context_path), + "label_policy": { + "query_answerability": "gold answer aliases from QA dataset", + "query_context_positive": "evidence context from same QA example", + "query_context_negative": ( + "TF-IDF retrieved evidence context from a different QA example " + "that does not contain the current answer aliases" + ), + }, + } + _write_json(manifest_path, manifest) + print(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Build confidence raw datasets from TriviaQA-style QA examples with " + "positive evidence and retrieved negative contexts." + ) + ) + parser.add_argument("--source", choices=("triviaqa",), default="triviaqa") + parser.add_argument("--dataset-name", default="mandarjoshi/trivia_qa") + parser.add_argument("--dataset-config", default="rc") + parser.add_argument("--split", default="train[:20000]") + parser.add_argument("--input-jsonl", type=Path) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--max-source-items", type=int, default=20000) + parser.add_argument("--max-query-items", type=int, default=5000) + parser.add_argument("--max-query-context-items", type=int, default=5000) + parser.add_argument("--max-context-chars", type=int, default=8000) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--overwrite", action="store_true") + args = parser.parse_args(argv) + if args.max_query_items < 30: + parser.error("--max-query-items must be >= 30") + if args.max_query_context_items < 30: + parser.error("--max-query-context-items must be >= 30") + if args.max_source_items < 2: + parser.error("--max-source-items must be >= 2") + if args.max_query_context_items % 2 != 0: + parser.error("--max-query-context-items must be even for balanced labels") + if args.max_context_chars < 1: + parser.error("--max-context-chars must be >= 1") + return args + + +@dataclass(frozen=True) +class _RawExample: + id: str + question: str + answers: list[str] + positive_context: str + + +@dataclass(frozen=True) +class _BuiltExample: + query_row: Mapping[str, Any] + positive_context_row: Mapping[str, Any] | None + negative_context_row: Mapping[str, Any] | None + + +def _load_rows(args: argparse.Namespace) -> list[Mapping[str, Any]]: + if args.input_jsonl is not None: + return _read_jsonl(args.input_jsonl, max_items=args.max_source_items) + try: + datasets = __import__("datasets") + except ImportError as exc: + raise SystemExit( + "HuggingFace datasets is required for direct dataset loading. " + "Run with: uv run --with datasets python " + "scripts/build_qa_confidence_raw_dataset.py ..." + ) from exc + dataset = datasets.load_dataset( + args.dataset_name, + args.dataset_config, + split=args.split, + ) + rows: list[Mapping[str, Any]] = [] + for index, row in enumerate(dataset): + if index >= args.max_source_items: + break + rows.append(dict(row)) + return rows + + +def _build_examples( + rows: Sequence[Mapping[str, Any]], + *, + max_query_items: int, + max_query_context_items: int, + max_context_chars: int, + seed: int, +) -> list[_BuiltExample]: + raw_examples: list[_RawExample] = [] + for row in rows: + example = _parse_triviaqa_row(row) + if example is None: + continue + if len(example.positive_context) > max_context_chars: + continue + raw_examples.append(example) + if len(raw_examples) < 2: + raise SystemExit("Need at least two usable QA examples.") + + rng = random.Random(seed) + rng.shuffle(raw_examples) + required_context_pairs = max_query_context_items // 2 + required_examples = max(max_query_items, required_context_pairs) + selected = raw_examples[:required_examples] + if len(selected) < required_examples: + raise SystemExit( + f"Only {len(selected)} usable examples found; " + f"requested {required_examples}." + ) + + negative_by_id = _retrieve_negative_contexts( + selected, + max_pairs=required_context_pairs, + ) + built: list[_BuiltExample] = [] + for index, example in enumerate(selected): + include_query = index < max_query_items + include_context = index < required_context_pairs + negative = negative_by_id.get(example.id) + built.append( + _BuiltExample( + query_row=( + { + "id": f"triviaqa-q-{example.id}", + "query": example.question, + "gold_answer": example.answers, + "source": "triviaqa", + "group_id": f"triviaqa-{example.id}", + "metadata": {"qa_id": example.id}, + } + if include_query + else {} + ), + positive_context_row=( + { + "id": f"triviaqa-qc-pos-{example.id}", + "query": example.question, + "context": example.positive_context, + "gold_answer": example.answers, + "source": "triviaqa", + "group_id": f"triviaqa-{example.id}", + "metadata": { + "qa_id": example.id, + "context_label": "positive", + }, + } + if include_context + else None + ), + negative_context_row=( + { + "id": f"triviaqa-qc-neg-{example.id}", + "query": example.question, + "context": negative.positive_context, + "gold_answer": NO_ANSWER, + "source": "triviaqa", + "group_id": f"triviaqa-{example.id}", + "metadata": { + "qa_id": example.id, + "negative_context_qa_id": negative.id, + "context_label": "negative_retrieved_tfidf", + }, + } + if include_context and negative is not None + else None + ), + ) + ) + + query_count = sum(1 for example in built if example.query_row) + context_count = sum( + 1 + for example in built + for row in (example.positive_context_row, example.negative_context_row) + if row is not None + ) + if query_count != max_query_items: + raise SystemExit( + f"Built {query_count} query rows; requested {max_query_items}." + ) + if context_count != max_query_context_items: + raise SystemExit( + f"Built {context_count} query-context rows; " + f"requested {max_query_context_items}. " + "Increase source rows or lower the requested context count." + ) + return built + + +def _retrieve_negative_contexts( + examples: Sequence[_RawExample], + *, + max_pairs: int, +) -> dict[str, _RawExample]: + try: + sklearn_text = importlib.import_module("sklearn.feature_extraction.text") + sklearn_neighbors = importlib.import_module("sklearn.neighbors") + except ImportError as exc: + raise SystemExit( + "scikit-learn is required for retrieved negatives. " + "Install dev dependencies or run through `uv run`." + ) from exc + + tfidf_vectorizer = sklearn_text.TfidfVectorizer + nearest_neighbors = sklearn_neighbors.NearestNeighbors + contexts = [example.positive_context for example in examples] + vectorizer = tfidf_vectorizer( + lowercase=True, + stop_words="english", + max_features=100_000, + ngram_range=(1, 2), + ) + context_matrix = vectorizer.fit_transform(contexts) + query_matrix = vectorizer.transform([example.question for example in examples]) + neighbors = nearest_neighbors( + algorithm="brute", + metric="cosine", + n_neighbors=min(len(examples), 64), + ) + neighbors.fit(context_matrix) + _, neighbor_indices = neighbors.kneighbors(query_matrix[:max_pairs]) + + result: dict[str, _RawExample] = {} + for index, example in enumerate(examples[:max_pairs]): + for candidate_index in neighbor_indices[index]: + candidate = examples[int(candidate_index)] + if _is_valid_negative_candidate(example, candidate): + result[example.id] = candidate + break + if example.id in result: + continue + for candidate in examples: + if _is_valid_negative_candidate(example, candidate): + result[example.id] = candidate + break + return result + + +def _is_valid_negative_candidate( + example: _RawExample, + candidate: _RawExample, +) -> bool: + return candidate.id != example.id and not _contains_any_answer( + candidate.positive_context, + example.answers, + ) + + +def _contains_any_answer(context: str, answers: Sequence[str]) -> bool: + context_tokens = _tokens_for_match(context) + for answer in answers: + answer_tokens = _tokens_for_match(answer) + if answer_tokens and _contains_token_sequence(context_tokens, answer_tokens): + return True + return False + + +def _contains_token_sequence( + context_tokens: Sequence[str], + answer_tokens: Sequence[str], +) -> bool: + if len(answer_tokens) > len(context_tokens): + return False + window_size = len(answer_tokens) + for index in range(len(context_tokens) - window_size + 1): + if list(context_tokens[index : index + window_size]) == list(answer_tokens): + return True + return False + + +def _tokens_for_match(value: str) -> list[str]: + return re.findall(r"[a-z0-9]+", value.casefold()) + + +def _parse_triviaqa_row(row: Mapping[str, Any]) -> _RawExample | None: + question = _text(row.get("question")) + if question is None: + return None + row_id = _text(row.get("question_id")) or _text(row.get("id")) + if row_id is None: + return None + answers = _answer_aliases(row.get("answer")) + if not answers: + return None + context = _best_context(row, answers=answers) + if context is None: + return None + return _RawExample( + id=row_id, + question=question, + answers=answers, + positive_context=context, + ) + + +def _answer_aliases(answer: object) -> list[str]: + if not isinstance(answer, Mapping): + return [] + values: list[str] = [] + for key in ("value", "normalized_value"): + value = _text(answer.get(key)) + if value is not None: + values.append(value) + for key in ("aliases", "normalized_aliases"): + aliases = answer.get(key) + if isinstance(aliases, Sequence) and not isinstance(aliases, (str, bytes)): + for alias in aliases: + value = _text(alias) + if value is not None: + values.append(value) + return _dedupe(values) + + +def _best_context(row: Mapping[str, Any], *, answers: Sequence[str]) -> str | None: + candidates: list[str] = [] + for collection_name in ("entity_pages", "search_results"): + collection = row.get(collection_name) + if isinstance(collection, Mapping): + candidates.extend(_contexts_from_columnar_collection(collection)) + if isinstance(collection, Sequence) and not isinstance( + collection, (str, bytes) + ): + candidates.extend(_contexts_from_records(collection)) + for context in candidates: + if _contains_any_answer(context, answers): + return context + return None + + +def _contexts_from_columnar_collection(collection: Mapping[str, Any]) -> list[str]: + contexts = collection.get("wiki_context") or collection.get("search_context") + titles = collection.get("title") + if not isinstance(contexts, Sequence) or isinstance(contexts, (str, bytes)): + return [] + title_values = titles if isinstance(titles, Sequence) else [] + result: list[str] = [] + for index, context in enumerate(contexts): + text = _text(context) + if text is None: + continue + title = "" + if index < len(title_values): + raw_title = _text(title_values[index]) + if raw_title is not None: + title = raw_title + "\n\n" + result.append(title + text) + return result + + +def _contexts_from_records(records: Sequence[object]) -> list[str]: + result: list[str] = [] + for record in records: + if not isinstance(record, Mapping): + continue + text = _text(record.get("wiki_context")) or _text(record.get("search_context")) + if text is None: + continue + title = _text(record.get("title")) + result.append(f"{title}\n\n{text}" if title is not None else text) + return result + + +def _text(value: object) -> str | None: + if not isinstance(value, str): + return None + stripped = value.strip() + return stripped if stripped else None + + +def _dedupe(values: Iterable[str]) -> list[str]: + seen: set[str] = set() + result: list[str] = [] + for value in values: + key = value.strip().lower() + if key in seen: + continue + seen.add(key) + result.append(value) + return result + + +def _read_jsonl(path: Path, *, max_items: int) -> list[Mapping[str, Any]]: + rows: list[Mapping[str, Any]] = [] + for line_number, line in enumerate( + path.read_text(encoding="utf-8").splitlines(), 1 + ): + if len(rows) >= max_items: + break + if not line.strip(): + continue + value = json.loads(line) + if not isinstance(value, Mapping): + raise SystemExit(f"line {line_number} must be a JSON object") + rows.append(value) + return rows + + +def _write_jsonl(path: Path, rows: Sequence[Mapping[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "".join( + json.dumps(row, ensure_ascii=True, sort_keys=True) + "\n" for row in rows + ), + encoding="utf-8", + ) + + +def _write_json(path: Path, value: Mapping[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + + +def _ensure_outputs_writable(paths: Sequence[Path], *, overwrite: bool) -> None: + existing = [path for path in paths if path.exists()] + if existing and not overwrite: + formatted = ", ".join(str(path) for path in existing) + raise SystemExit( + f"output already exists: {formatted}. Use --overwrite to replace it." + ) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/compare_reranking.py b/scripts/compare_reranking.py index 91c81d9..5ce1a9d 100644 --- a/scripts/compare_reranking.py +++ b/scripts/compare_reranking.py @@ -2,13 +2,14 @@ from __future__ import annotations import argparse +import functools import json import math import os import sys from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Literal, cast +from typing import Any, Literal, cast ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) @@ -29,6 +30,7 @@ from benchmarks.mteb_eval import ( # noqa: E402 tourrank_stage_configs_for_candidate_count, ) +from ranksmith.types import Document # noqa: E402 Algorithm = Literal[ "original_bm25", @@ -49,6 +51,7 @@ "tourrank_r", "setwise_heapsort", "acurank", + "cbdr", ] Dataset = Literal["fixture", "benchmark-cache", "beir-scifact"] DEFAULT_FIXTURE = ROOT / "tests/fixtures/reranking_smoke_fixture.jsonl" @@ -66,6 +69,7 @@ "acurank_b4", "tourrank_r10", "prp_sliding_p3", + "cbdr", ) LEGACY_ALGORITHMS: tuple[Algorithm, ...] = ( "rankgpt_sliding_window", @@ -87,7 +91,7 @@ def main() -> None: needs_live = any(algorithm != "original_bm25" for algorithm in algorithms) if needs_live and not args.allow_live: raise SystemExit("Refusing live Azure calls without --allow-live.") - call_estimates = { + call_estimates: dict[str, int] = { algorithm: sum( _estimate_provider_calls( len(case.documents), @@ -105,13 +109,20 @@ def main() -> None: } if needs_live: print( - "Live Azure comparison will run " - f"{sum(call_estimates.values())} provider calls: {call_estimates}", + _call_estimate_message( + needs_live=True, + algorithms=algorithms, + call_estimates=call_estimates, + ), file=sys.stderr, ) else: print( - f"Offline comparison will run {call_estimates}", + _call_estimate_message( + needs_live=False, + algorithms=algorithms, + call_estimates=call_estimates, + ), file=sys.stderr, ) @@ -195,6 +206,25 @@ def _parse_args() -> argparse.Namespace: parser.add_argument("--tourrank-rounds", type=int, default=2) parser.add_argument("--set-size", type=int, default=3) parser.add_argument("--top-k", type=int, default=5) + parser.add_argument("--cbdr-base-artifact", type=Path) + parser.add_argument("--cbdr-context-artifact", type=Path) + parser.add_argument("--cbdr-skip-threshold", type=float, default=0.8) + parser.add_argument("--cbdr-device", default="cpu") + parser.add_argument("--cbdr-cache-dir", type=Path) + parser.add_argument("--cbdr-local-files-only", action="store_true") + parser.add_argument("--cbdr-hf-token-env") + parser.add_argument("--cbdr-max-length", type=int) + parser.add_argument("--cbdr-max-document-chars", type=int, default=4000) + parser.add_argument("--cbdr-allow-truncation", action="store_true") + parser.add_argument( + "--cbdr-answer-provider", + choices=("azure", "lmstudio"), + default="azure", + ) + parser.add_argument("--lmstudio-base-url") + parser.add_argument("--lmstudio-model") + parser.add_argument("--lmstudio-api-key") + parser.add_argument("--lmstudio-max-tokens", type=int, default=128) parser.add_argument( "--query-id", action="append", @@ -221,7 +251,7 @@ def _parse_args() -> argparse.Namespace: parser.add_argument( "--allow-live", action="store_true", - help="Required because this script sends live Azure OpenAI requests.", + help="Required because this script sends live model provider requests.", ) return parser.parse_args() @@ -242,6 +272,24 @@ def _validate_args(args: argparse.Namespace) -> None: raise SystemExit("--max-cases must be greater than 0.") if args.timeout is not None and args.timeout <= 0: raise SystemExit("--timeout must be greater than 0.") + cbdr_skip_threshold = getattr(args, "cbdr_skip_threshold", 0.8) + if cbdr_skip_threshold < 0.0 or cbdr_skip_threshold > 1.0: + raise SystemExit("--cbdr-skip-threshold must be in [0, 1].") + cbdr_max_length = getattr(args, "cbdr_max_length", None) + if cbdr_max_length is not None and cbdr_max_length < 1: + raise SystemExit("--cbdr-max-length must be greater than 0.") + cbdr_max_document_chars = getattr(args, "cbdr_max_document_chars", 4000) + if cbdr_max_document_chars < 1: + raise SystemExit("--cbdr-max-document-chars must be greater than 0.") + if getattr(args, "lmstudio_max_tokens", 128) < 1: + raise SystemExit("--lmstudio-max-tokens must be greater than 0.") + if args.algorithm == "cbdr": + if getattr(args, "cbdr_base_artifact", None) is None: + raise SystemExit("--cbdr-base-artifact is required with --algorithm cbdr.") + if getattr(args, "cbdr_context_artifact", None) is None: + raise SystemExit( + "--cbdr-context-artifact is required with --algorithm cbdr." + ) if args.checkpoint_output is not None and args.checkpoint_output == args.output: raise SystemExit("--checkpoint-output must differ from --output.") if args.dataset == "fixture": @@ -344,6 +392,41 @@ def _evaluate_cases( set_size=args.set_size, top_k=args.top_k, timeout=getattr(args, "timeout", None), + cbdr_base_artifact=getattr(args, "cbdr_base_artifact", None), + cbdr_context_artifact=getattr( + args, + "cbdr_context_artifact", + None, + ), + cbdr_skip_threshold=getattr(args, "cbdr_skip_threshold", 0.8), + cbdr_device=getattr(args, "cbdr_device", "cpu"), + cbdr_cache_dir=getattr(args, "cbdr_cache_dir", None), + cbdr_local_files_only=getattr( + args, + "cbdr_local_files_only", + False, + ), + cbdr_hf_token_env=getattr(args, "cbdr_hf_token_env", None), + cbdr_max_length=getattr(args, "cbdr_max_length", None), + cbdr_max_document_chars=getattr( + args, + "cbdr_max_document_chars", + 4000, + ), + cbdr_allow_truncation=getattr( + args, + "cbdr_allow_truncation", + False, + ), + cbdr_answer_provider=getattr( + args, + "cbdr_answer_provider", + "azure", + ), + lmstudio_base_url=getattr(args, "lmstudio_base_url", None), + lmstudio_model=getattr(args, "lmstudio_model", None), + lmstudio_api_key=getattr(args, "lmstudio_api_key", None), + lmstudio_max_tokens=getattr(args, "lmstudio_max_tokens", 128), ) evaluation = evaluate_ranked_ids( case=case, @@ -381,6 +464,43 @@ def _evaluate_cases( return evaluations, per_query +@functools.cache +def _cached_cbdr_estimators( + *, + base_artifact_path: Path, + context_artifact_path: Path, + hf_token: str | None, + cache_dir: str | None, + device: str, + local_files_only: bool, + max_length: int | None, + allow_truncation: bool, +) -> tuple[Any, Any]: + from ranksmith.confidence import StructuralConfidenceEstimator + + base_estimator = StructuralConfidenceEstimator.from_artifact( + base_artifact_path, + task_type="query_answerability_confidence", + hf_token=hf_token, + cache_dir=cache_dir, + device=device, + local_files_only=local_files_only, + max_length=max_length, + allow_truncation=allow_truncation, + ) + context_estimator = StructuralConfidenceEstimator.from_artifact( + context_artifact_path, + task_type="query_context_answerability_confidence", + hf_token=hf_token, + cache_dir=cache_dir, + device=device, + local_files_only=local_files_only, + max_length=max_length, + allow_truncation=allow_truncation, + ) + return base_estimator, context_estimator + + def _rank_case( *, case: BenchmarkCase, @@ -392,19 +512,42 @@ def _rank_case( set_size: int = 3, top_k: int | None = None, timeout: float | None = None, + cbdr_base_artifact: Path | None = None, + cbdr_context_artifact: Path | None = None, + cbdr_skip_threshold: float = 0.8, + cbdr_device: str = "cpu", + cbdr_cache_dir: Path | None = None, + cbdr_local_files_only: bool = False, + cbdr_hf_token_env: str | None = None, + cbdr_max_length: int | None = None, + cbdr_max_document_chars: int = 4000, + cbdr_allow_truncation: bool = False, + cbdr_answer_provider: str = "azure", + lmstudio_base_url: str | None = None, + lmstudio_model: str | None = None, + lmstudio_api_key: str | None = None, + lmstudio_max_tokens: int = 128, ) -> tuple[str, ...]: from ranksmith import ( AcuRankStrategy, AzureOpenAIReranker, - Document, ListwiseStrategy, PairwiseStrategy, SetwiseStrategy, TourRankStrategy, ) + from ranksmith.integrations import ( + AzureAnswerGenerator, + LMStudioModelProvider, + ProviderAnswerGenerator, + ) + from ranksmith.protocols import RerankStrategy + from ranksmith.strategies import CBDRStrategy if algorithm == "original_bm25": return tuple(document.id for document in case.documents) + documents = _case_documents(case) + strategy: RerankStrategy[Any] if algorithm in {"prp_sliding_k", "prp_sliding_p1", "prp_sliding_p3"}: strategy = PairwiseStrategy(passes=_prp_passes_for_algorithm(algorithm, passes)) elif algorithm in {"tourrank_r", "tourrank_r2", "tourrank_r10"}: @@ -434,6 +577,51 @@ def _rank_case( window_size=window_size, max_adaptive_reranker_calls=_acurank_budget_for_algorithm(algorithm), ) + elif algorithm == "cbdr": + if cbdr_base_artifact is None: + raise SystemExit("--cbdr-base-artifact is required with --algorithm cbdr.") + if cbdr_context_artifact is None: + raise SystemExit( + "--cbdr-context-artifact is required with --algorithm cbdr." + ) + answer_generator: Any + if cbdr_answer_provider == "azure": + answer_generator = AzureAnswerGenerator.from_env(timeout=timeout) + elif cbdr_answer_provider == "lmstudio": + answer_generator = ProviderAnswerGenerator( + provider=LMStudioModelProvider( + base_url=lmstudio_base_url, + model=lmstudio_model, + api_key=lmstudio_api_key, + timeout=timeout, + max_tokens=lmstudio_max_tokens, + ) + ) + else: + raise SystemExit("--cbdr-answer-provider must be azure or lmstudio.") + base_estimator, context_estimator = _cached_cbdr_estimators( + base_artifact_path=cbdr_base_artifact, + context_artifact_path=cbdr_context_artifact, + hf_token=_env_value_required(cbdr_hf_token_env), + cache_dir=str(cbdr_cache_dir) if cbdr_cache_dir is not None else None, + device=cbdr_device, + local_files_only=cbdr_local_files_only, + max_length=cbdr_max_length, + allow_truncation=cbdr_allow_truncation, + ) + strategy = CBDRStrategy( + base_estimator=base_estimator, + context_estimator=context_estimator, + answer_generator=answer_generator, + skip_threshold=cbdr_skip_threshold, + max_document_chars=cbdr_max_document_chars, + ) + results = strategy.rerank( + query=case.query, + documents=documents, + top_k=top_k, + ) + return tuple(result.document.id or "" for result in results) else: listwise_window_size, listwise_stride = _listwise_window_stride_for_algorithm( algorithm, @@ -456,11 +644,22 @@ def _rank_case( "AZURE_OPENAI_LLM_API_VERSION", fallback="AZURE_OPENAI_API_VERSION", default="2024-08-01-preview", - ), + ) + or "2024-08-01-preview", timeout=timeout or _env_float("AZURE_OPENAI_LLM_TIMEOUT"), strategy=strategy, ) - documents = [ + if top_k is None: + results = reranker.rerank(case.query, documents) + else: + results = reranker.rerank(case.query, documents, top_k=top_k) + return tuple(result.document.id or "" for result in results) + + +def _case_documents(case: BenchmarkCase) -> list[Document]: + from ranksmith import Document + + return [ Document( id=document.id, text=f"{document.title}\n\n{document.text}", @@ -468,11 +667,6 @@ def _rank_case( ) for document in case.documents ] - if top_k is None: - results = reranker.rerank(case.query, documents) - else: - results = reranker.rerank(case.query, documents, top_k=top_k) - return tuple(result.document.id or "" for result in results) def _build_report( @@ -582,6 +776,57 @@ def _method_setting( "passes": _prp_passes_for_algorithm(algorithm, args.passes), "top_k_early_stop": False, } + if algorithm == "cbdr": + base_artifact = getattr(args, "cbdr_base_artifact", None) + context_artifact = getattr(args, "cbdr_context_artifact", None) + cache_dir = getattr(args, "cbdr_cache_dir", None) + answer_provider = getattr(args, "cbdr_answer_provider", "azure") + lmstudio_base_url = None + lmstudio_model = None + lmstudio_api_key_configured = None + if answer_provider == "lmstudio": + lmstudio_base_url = _resolve_lmstudio_setting( + explicit=getattr(args, "lmstudio_base_url", None), + env_name="LMSTUDIO_BASE_URL", + default="http://localhost:1234/v1", + ) + lmstudio_model = _resolve_lmstudio_setting( + explicit=getattr(args, "lmstudio_model", None), + env_name="LMSTUDIO_MODEL", + default=None, + ) + lmstudio_api_key_configured = ( + _resolve_lmstudio_setting( + explicit=getattr(args, "lmstudio_api_key", None), + env_name="LMSTUDIO_API_KEY", + default="lm-studio", + ) + is not None + ) + return { + **base, + "base_artifact": ( + str(base_artifact) if base_artifact is not None else None + ), + "context_artifact": ( + str(context_artifact) if context_artifact is not None else None + ), + "answer_provider": answer_provider, + "skip_threshold": args.cbdr_skip_threshold, + "device": args.cbdr_device, + "cache_dir": str(cache_dir) if cache_dir is not None else None, + "local_files_only": args.cbdr_local_files_only, + "hf_token_env": args.cbdr_hf_token_env, + "max_length": args.cbdr_max_length, + "max_document_chars": args.cbdr_max_document_chars, + "allow_truncation": args.cbdr_allow_truncation, + "lmstudio_base_url": lmstudio_base_url, + "lmstudio_model": lmstudio_model, + "lmstudio_api_key": ("configured" if lmstudio_api_key_configured else None), + "lmstudio_max_tokens": getattr(args, "lmstudio_max_tokens", 128), + "provider_call_estimate": "upper_bound", + "top_k_early_stop": False, + } return { **base, "window_size": args.window_size, @@ -590,11 +835,44 @@ def _method_setting( } +def _resolve_lmstudio_setting( + *, + explicit: str | None, + env_name: str, + default: str | None, +) -> str | None: + if explicit is not None: + return explicit + env_value = os.environ.get(env_name) + if env_value is not None: + return env_value + return default + + def _append_checkpoint_row(path: Path, row: Mapping[str, object]) -> None: with path.open("a", encoding="utf-8") as handle: handle.write(json.dumps(row, sort_keys=True) + "\n") +def _call_estimate_message( + *, + needs_live: bool, + algorithms: Sequence[Algorithm], + call_estimates: Mapping[str, int], +) -> str: + total = sum(call_estimates.values()) + if not needs_live: + return f"Offline comparison will run {dict(call_estimates)}" + if "cbdr" in algorithms: + return ( + "Live Azure comparison upper-bounds provider calls at " + f"{total}: {dict(call_estimates)}" + ) + return ( + f"Live Azure comparison will run {total} provider calls: {dict(call_estimates)}" + ) + + def _aggregate_with_validity( evaluations: Sequence[EvaluationResult], per_query: Sequence[Mapping[str, object]], @@ -645,10 +923,10 @@ def _clean_env_value(value: str) -> str: def _dataset_name(args: argparse.Namespace) -> str: if args.dataset == "beir-scifact": - return args.dataset_name or "BEIR/SciFact" + return cast(str, args.dataset_name or "BEIR/SciFact") if args.dataset_name is not None and args.dataset_name.strip() != "": - return args.dataset_name.strip() - return args.cache_dir.name + return cast(str, args.dataset_name.strip()) + return cast(str, args.cache_dir.name) def _fixture_prefix(dataset_name: str) -> str: @@ -682,6 +960,15 @@ def _env_value( return default +def _env_value_required(name: str | None) -> str | None: + if name is None or name == "": + return None + value = os.environ.get(name) + if value is None or value == "": + raise SystemExit(f"Missing required environment variable: {name}") + return value + + def _env_float(name: str) -> float | None: value = os.environ.get(name) if value is None or value == "": @@ -701,6 +988,8 @@ def _estimate_provider_calls( ) -> int: if algorithm == "original_bm25": return 0 + if algorithm == "cbdr": + return document_count + 1 if algorithm in {"prp_sliding_k", "prp_sliding_p1", "prp_sliding_p3"}: return ( 2 diff --git a/scripts/generate_confidence_dataset.py b/scripts/generate_confidence_dataset.py new file mode 100644 index 0000000..f2b345a --- /dev/null +++ b/scripts/generate_confidence_dataset.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +import argparse +import json +import sys +from collections.abc import Sequence +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from ranksmith.confidence_generation import ( # noqa: E402 + ConfidenceGenerationResult, + QueryAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationConfig, + generate_query_answerability_confidence_dataset, + generate_query_context_answerability_confidence_dataset, +) +from ranksmith.integrations import LMStudioModelProvider # noqa: E402 + +TASK_QUERY = "query_answerability_confidence" +TASK_QUERY_CONTEXT = "query_context_answerability_confidence" +SUPPORTED_TASKS = (TASK_QUERY, TASK_QUERY_CONTEXT) + + +def main(argv: Sequence[str] | None = None) -> int: + args = _parse_args(argv) + provider = LMStudioModelProvider( + base_url=args.lmstudio_base_url, + model=args.lmstudio_model, + api_key=args.lmstudio_api_key, + max_tokens=args.lmstudio_max_tokens, + timeout=args.timeout, + ) + + if args.task == TASK_QUERY: + result = generate_query_answerability_confidence_dataset( + QueryAnswerabilityGenerationConfig( + input_path=args.input, + output_path=args.output, + provider=provider, + overwrite=args.overwrite, + resume=args.resume, + max_items=args.max_items, + source=args.source, + ) + ) + else: + result = generate_query_context_answerability_confidence_dataset( + QueryContextAnswerabilityGenerationConfig( + input_path=args.input, + output_path=args.output, + provider=provider, + overwrite=args.overwrite, + resume=args.resume, + max_items=args.max_items, + max_context_chars=args.max_context_chars, + source=args.source, + ) + ) + + print(json.dumps(_generation_summary(result), ensure_ascii=False, sort_keys=True)) + return 0 + + +def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Generate answerability confidence canonical JSONL datasets." + ) + parser.add_argument("--task", required=True, choices=SUPPORTED_TASKS) + parser.add_argument("--provider", required=True, choices=("lmstudio",)) + parser.add_argument("--input", required=True, type=Path) + parser.add_argument("--output", required=True, type=Path) + parser.add_argument("--overwrite", action="store_true") + parser.add_argument("--resume", action="store_true") + parser.add_argument("--max-items", type=int, default=None) + parser.add_argument("--source", default=None) + parser.add_argument("--max-context-chars", type=int, default=None) + parser.add_argument("--lmstudio-base-url", default=None) + parser.add_argument("--lmstudio-model", default=None) + parser.add_argument("--lmstudio-api-key", default=None) + parser.add_argument("--lmstudio-max-tokens", type=int, default=128) + parser.add_argument("--timeout", type=float, default=None) + args = parser.parse_args(argv) + if args.task == TASK_QUERY and args.max_context_chars is not None: + parser.error("--max-context-chars is only valid for query-context tasks") + if args.task == TASK_QUERY_CONTEXT and args.max_context_chars is None: + args.max_context_chars = 8000 + return args + + +def _generation_summary(result: ConfidenceGenerationResult) -> dict[str, int | str]: + return { + "output_path": str(result.output_path), + "input_count": result.input_count, + "generated_count": result.generated_count, + "skipped_count": result.skipped_count, + "positive_count": result.positive_count, + "negative_count": result.negative_count, + } + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/report_confidence_dataset.py b/scripts/report_confidence_dataset.py new file mode 100644 index 0000000..0bcfa84 --- /dev/null +++ b/scripts/report_confidence_dataset.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import argparse +import json +import sys +from collections.abc import Sequence +from pathlib import Path +from typing import cast + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from ranksmith.confidence import TaskType # noqa: E402 +from ranksmith.confidence_training.dataset_report import ( # noqa: E402 + build_dataset_report, +) + +TASK_QUERY = "query_answerability_confidence" +TASK_QUERY_CONTEXT = "query_context_answerability_confidence" +SUPPORTED_TASKS = (TASK_QUERY, TASK_QUERY_CONTEXT) + + +def main(argv: Sequence[str] | None = None) -> int: + args = _parse_args(argv) + report = build_dataset_report(args.dataset, cast(TaskType, args.task)) + print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Report confidence dataset balance.") + parser.add_argument("--task", required=True, choices=SUPPORTED_TASKS) + parser.add_argument("--dataset", required=True, type=Path) + return parser.parse_args(argv) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_lmstudio_confidence_pipeline.py b/scripts/run_lmstudio_confidence_pipeline.py new file mode 100644 index 0000000..3674486 --- /dev/null +++ b/scripts/run_lmstudio_confidence_pipeline.py @@ -0,0 +1,752 @@ +#!/usr/bin/env python +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from pathlib import Path +from typing import Any, cast + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from ranksmith.confidence import TaskType # noqa: E402 +from ranksmith.confidence.encoder import FrozenAutoEncoder # noqa: E402 +from ranksmith.confidence_generation import ( # noqa: E402 + ConfidenceGenerationResult, + QueryAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationConfig, + generate_query_answerability_confidence_dataset, + generate_query_context_answerability_confidence_dataset, +) +from ranksmith.confidence_training import ( # noqa: E402 + ConfidenceTrainingConfig, + ConfidenceTrainingResult, +) +from ranksmith.confidence_training.artifact import ( # noqa: E402 + export_scorer_artifact, + write_metadata_json, +) +from ranksmith.confidence_training.dataset import load_canonical_dataset # noqa: E402 +from ranksmith.confidence_training.dataset_report import ( # noqa: E402 + build_dataset_report, +) +from ranksmith.confidence_training.features import extract_feature_rows # noqa: E402 +from ranksmith.confidence_training.manifest import ( # noqa: E402 + build_dataset_manifest, + build_split_manifest, + json_hash, + training_config_hash, +) +from ranksmith.confidence_training.pipeline import ( # noqa: E402 + _feature_row_json, + _write_model, + _write_report_markdown, +) +from ranksmith.confidence_training.report import generate_training_report # noqa: E402 +from ranksmith.confidence_training.split import split_dataset # noqa: E402 +from ranksmith.confidence_training.train import ( # noqa: E402 + calibrate_classifier, + train_lightgbm_classifier, +) +from ranksmith.confidence_training.types import ConfidenceFeatureRow # noqa: E402 +from ranksmith.integrations import ( # noqa: E402 + LMStudioModelProvider, + ProviderAnswerGenerator, +) +from ranksmith.model import ModelProvider, ModelRequest, ModelResponse # noqa: E402 +from ranksmith.strategies import CBDRStrategy # noqa: E402 + +TASK_QUERY = "query_answerability_confidence" +TASK_QUERY_CONTEXT = "query_context_answerability_confidence" + + +@dataclass(frozen=True) +class _JsonObjectRetryProvider: + provider: ModelProvider + max_attempts: int + + def complete(self, request: ModelRequest) -> ModelResponse: + last_response: ModelResponse | None = None + for _ in range(self.max_attempts): + response = self.provider.complete(request) + last_response = response + if _is_valid_json_object(response.content): + return response + if last_response is not None: + return last_response + return self.provider.complete(request) + + +def _is_valid_json_object(content: str) -> bool: + try: + parsed = json.loads(content) + except json.JSONDecodeError: + return False + if not isinstance(parsed, dict): + return False + answer = parsed.get("answer") + return isinstance(answer, str) and answer.strip() != "" + + +def main(argv: Sequence[str] | None = None) -> int: + raw_argv = list(sys.argv[1:] if argv is None else argv) + if raw_argv[:1] == ["__extract_features"]: + return _extract_features_main(raw_argv[1:]) + + args = _parse_args(raw_argv) + run_dir = Path(args.run_dir) + if ( + run_dir.exists() + and any(run_dir.iterdir()) + and not (args.overwrite or args.resume_generation) + ): + raise SystemExit( + f"run dir already exists and is not empty: {run_dir}. " + "Use --resume-generation or --overwrite." + ) + + raw_dir = run_dir / "raw" + canonical_dir = run_dir / "canonical" + report_dir = run_dir / "reports" + training_dir = run_dir / "training" + artifact_dir = run_dir / "artifacts" + for directory in (raw_dir, canonical_dir, report_dir, training_dir, artifact_dir): + directory.mkdir(parents=True, exist_ok=True) + + query_raw = ( + Path(args.query_raw) + if args.query_raw is not None + else raw_dir / "query_answerability_raw.jsonl" + ) + query_context_raw = ( + Path(args.query_context_raw) + if args.query_context_raw is not None + else raw_dir / "query_context_answerability_raw.jsonl" + ) + if args.build_qa_raw: + _build_qa_raw_files( + raw_dir=raw_dir, + query_raw=query_raw, + query_context_raw=query_context_raw, + args=args, + ) + _require_input_file(query_raw, "--query-raw") + _require_input_file(query_context_raw, "--query-context-raw") + + provider: ModelProvider = _JsonObjectRetryProvider( + provider=LMStudioModelProvider( + model=args.lmstudio_model, + base_url=args.lmstudio_base_url, + api_key=args.lmstudio_api_key, + timeout=args.timeout, + max_tokens=args.lmstudio_max_tokens, + ), + max_attempts=args.generation_max_attempts, + ) + + query_canonical = canonical_dir / "query_answerability_confidence.jsonl" + context_canonical = canonical_dir / "query_context_answerability_confidence.jsonl" + + query_generation = generate_query_answerability_confidence_dataset( + QueryAnswerabilityGenerationConfig( + input_path=query_raw, + output_path=query_canonical, + provider=provider, + overwrite=args.overwrite, + resume=args.resume_generation, + max_items=args.max_items, + source=args.source, + ) + ) + context_generation = generate_query_context_answerability_confidence_dataset( + QueryContextAnswerabilityGenerationConfig( + input_path=query_context_raw, + output_path=context_canonical, + provider=provider, + overwrite=args.overwrite, + resume=args.resume_generation, + max_items=args.max_items, + max_context_chars=args.max_context_chars, + source=args.source, + ) + ) + + query_report = build_dataset_report(query_canonical, cast(TaskType, TASK_QUERY)) + context_report = build_dataset_report( + context_canonical, + cast(TaskType, TASK_QUERY_CONTEXT), + ) + _write_json(report_dir / "query_answerability_dataset_report.json", query_report) + _write_json( + report_dir / "query_context_answerability_dataset_report.json", + context_report, + ) + + base_artifact = artifact_dir / "query_answerability.joblib" + context_artifact = artifact_dir / "query_context_answerability.joblib" + query_training = _train_confidence_scorer_isolated( + _training_config( + task_type=TASK_QUERY, + dataset_path=query_canonical, + output_dir=training_dir / "query_answerability", + export_path=base_artifact, + args=args, + ) + ) + context_training = _train_confidence_scorer_isolated( + _training_config( + task_type=TASK_QUERY_CONTEXT, + dataset_path=context_canonical, + output_dir=training_dir / "query_context_answerability", + export_path=context_artifact, + args=args, + ) + ) + + strategy = CBDRStrategy.from_artifacts( + base_artifact_path=query_training.export_path, + context_artifact_path=context_training.export_path, + answer_generator=ProviderAnswerGenerator(provider=provider), + skip_threshold=args.skip_threshold, + cache_dir=args.cache_dir, + device=args.device, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + ) + benchmark_output = None + benchmark_checkpoint_output = None + if args.run_benchmark: + benchmark_output = run_dir / "benchmark" / "cbdr_report.json" + benchmark_checkpoint_output = run_dir / "benchmark" / "cbdr_checkpoint.jsonl" + _run_benchmark( + args=args, + output=benchmark_output, + checkpoint_output=benchmark_checkpoint_output, + base_artifact=query_training.export_path, + context_artifact=context_training.export_path, + ) + + summary = { + "run_dir": str(run_dir), + "generation": { + "query_answerability": _generation_summary(query_generation), + "query_context_answerability": _generation_summary(context_generation), + }, + "dataset_reports": { + "query_answerability": str( + report_dir / "query_answerability_dataset_report.json" + ), + "query_context_answerability": str( + report_dir / "query_context_answerability_dataset_report.json" + ), + }, + "training": { + "query_answerability": _training_summary(query_training), + "query_context_answerability": _training_summary(context_training), + }, + "artifacts": { + "query_answerability": str(query_training.export_path), + "query_context_answerability": str(context_training.export_path), + }, + "cbdr": { + "strategy_loaded": strategy.algorithm == "cbdr", + "skip_threshold": args.skip_threshold, + }, + "benchmark": { + "executed": args.run_benchmark, + "output": str(benchmark_output) if benchmark_output is not None else None, + "checkpoint_output": ( + str(benchmark_checkpoint_output) + if benchmark_checkpoint_output is not None + else None + ), + "command": _benchmark_command(run_dir, args=args), + }, + } + _write_json(report_dir / "summary.json", summary) + print(json.dumps(summary, ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Run the full LM Studio confidence pipeline: generation, reports, " + "training, artifact export, and CBDR loading." + ) + ) + parser.add_argument("--run-dir", required=True, type=Path) + parser.add_argument("--query-raw", type=Path, default=None) + parser.add_argument("--query-context-raw", type=Path, default=None) + parser.add_argument("--build-qa-raw", action="store_true") + parser.add_argument("--qa-source", choices=("triviaqa",), default="triviaqa") + parser.add_argument("--qa-dataset-name", default="mandarjoshi/trivia_qa") + parser.add_argument("--qa-dataset-config", default="rc") + parser.add_argument("--qa-split", default="train[:20000]") + parser.add_argument("--qa-input-jsonl", type=Path, default=None) + parser.add_argument("--qa-max-source-items", type=int, default=20000) + parser.add_argument("--qa-max-query-items", type=int, default=5000) + parser.add_argument("--qa-max-query-context-items", type=int, default=5000) + parser.add_argument("--lmstudio-model", default=None) + parser.add_argument("--lmstudio-base-url", default=None) + parser.add_argument("--lmstudio-api-key", default=None) + parser.add_argument("--lmstudio-max-tokens", type=int, default=128) + parser.add_argument("--generation-max-attempts", type=int, default=3) + parser.add_argument("--timeout", type=float, default=60.0) + parser.add_argument("--max-items", type=int, default=None) + parser.add_argument("--max-context-chars", type=int, default=8000) + parser.add_argument("--source", default=None) + parser.add_argument("--resume-generation", action="store_true") + parser.add_argument("--overwrite", action="store_true") + parser.add_argument("--encoder-name", default="bert-base-uncased") + parser.add_argument("--encoder-revision", default=None) + parser.add_argument("--tokenizer-name", default=None) + parser.add_argument("--tokenizer-revision", default=None) + parser.add_argument("--cache-dir", default=None) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--device", default="cpu") + parser.add_argument("--max-length", type=int, default=256) + parser.add_argument("--allow-truncation", action="store_true") + parser.add_argument("--skip-threshold", type=float, default=0.8) + parser.add_argument("--run-benchmark", action="store_true") + parser.add_argument("--allow-live", action="store_true") + parser.add_argument( + "--benchmark-dataset", + choices=("fixture", "benchmark-cache", "beir-scifact"), + default="benchmark-cache", + ) + parser.add_argument("--benchmark-cache-dir", type=Path) + parser.add_argument("--benchmark-candidates", type=Path) + parser.add_argument("--benchmark-max-cases", type=int) + parser.add_argument("--benchmark-top-k", type=int, default=5) + args = parser.parse_args(argv) + if args.build_qa_raw and ( + args.query_raw is not None or args.query_context_raw is not None + ): + parser.error("--build-qa-raw cannot be combined with explicit raw paths") + if not args.build_qa_raw and ( + args.query_raw is None or args.query_context_raw is None + ): + parser.error( + "--query-raw and --query-context-raw are required unless " + "--build-qa-raw is set" + ) + if args.qa_max_query_items < 30: + parser.error("--qa-max-query-items must be >= 30") + if args.qa_max_query_context_items < 30: + parser.error("--qa-max-query-context-items must be >= 30") + if args.qa_max_source_items < 2: + parser.error("--qa-max-source-items must be >= 2") + if args.qa_max_query_context_items % 2 != 0: + parser.error("--qa-max-query-context-items must be even") + if args.overwrite and args.resume_generation: + parser.error("--overwrite and --resume-generation cannot both be set") + if args.lmstudio_max_tokens < 1: + parser.error("--lmstudio-max-tokens must be >= 1") + if args.generation_max_attempts < 1: + parser.error("--generation-max-attempts must be >= 1") + if args.max_items is not None and args.max_items < 1: + parser.error("--max-items must be >= 1") + if args.max_context_chars < 1: + parser.error("--max-context-chars must be >= 1") + if args.skip_threshold < 0.0 or args.skip_threshold > 1.0: + parser.error("--skip-threshold must be in [0, 1]") + if args.run_benchmark and not args.allow_live: + parser.error("--run-benchmark requires --allow-live") + if args.run_benchmark and args.benchmark_dataset == "benchmark-cache": + if args.benchmark_cache_dir is None: + parser.error("--benchmark-cache-dir is required for benchmark-cache") + if args.benchmark_candidates is None: + parser.error("--benchmark-candidates is required for benchmark-cache") + if args.benchmark_max_cases is not None and args.benchmark_max_cases < 1: + parser.error("--benchmark-max-cases must be >= 1") + if args.benchmark_top_k < 1: + parser.error("--benchmark-top-k must be >= 1") + return args + + +def _training_config( + *, + task_type: str, + dataset_path: Path, + output_dir: Path, + export_path: Path, + args: argparse.Namespace, +) -> ConfidenceTrainingConfig: + return ConfidenceTrainingConfig( + task_type=cast(TaskType, task_type), + dataset_path=dataset_path, + output_dir=output_dir, + export_path=export_path, + encoder_name=args.encoder_name, + encoder_revision=args.encoder_revision, + tokenizer_name=args.tokenizer_name, + tokenizer_revision=args.tokenizer_revision, + cache_dir=args.cache_dir, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + ) + + +def _extract_features_main(argv: Sequence[str]) -> int: + args = _parse_extract_args(argv) + config = _training_config_from_extract_args(args) + output_dir = Path(config.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + samples = load_canonical_dataset(config.dataset_path, task_type=config.task_type) + split = split_dataset( + samples, + seed=config.seed, + train_ratio=config.train_ratio, + valid_ratio=config.valid_ratio, + test_ratio=config.test_ratio, + ) + encoder = FrozenAutoEncoder.from_pretrained( + encoder_name=config.encoder_name, + encoder_revision=config.encoder_revision, + tokenizer_name=config.tokenizer_name, + tokenizer_revision=config.tokenizer_revision, + hf_token=None, + local_files_only=config.local_files_only, + cache_dir=config.cache_dir, + device="cpu", + max_length=config.max_length, + allow_truncation=config.allow_truncation, + ) + for name, part in ( + ("train", split.train), + ("valid", split.valid), + ("test", split.test), + ): + rows = extract_feature_rows(part, encoder=encoder) + _write_feature_rows(output_dir / f"features_{name}.jsonl", rows) + return 0 + + +def _train_confidence_scorer_isolated( + config: ConfidenceTrainingConfig, +) -> ConfidenceTrainingResult: + output_dir = Path(config.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + export_path = Path(config.export_path) + + _run_feature_extraction_subprocess(config) + train_rows = _read_feature_rows(output_dir / "features_train.jsonl") + valid_rows = _read_feature_rows(output_dir / "features_valid.jsonl") + test_rows = _read_feature_rows(output_dir / "features_test.jsonl") + + model = train_lightgbm_classifier(train_rows, seed=config.seed) + scorer = calibrate_classifier(model, valid_rows) + report = generate_training_report( + model, + scorer, + valid_rows=valid_rows, + test_rows=test_rows, + ) + _write_json(output_dir / "report.json", report.to_dict()) + _write_report_markdown(output_dir / "report.md", report) + + samples = load_canonical_dataset(config.dataset_path, task_type=config.task_type) + split = split_dataset( + samples, + seed=config.seed, + train_ratio=config.train_ratio, + valid_ratio=config.valid_ratio, + test_ratio=config.test_ratio, + ) + dataset_manifest = build_dataset_manifest( + Path(config.dataset_path), + task_type=config.task_type, + sample_count=len(samples), + ) + split_manifest = build_split_manifest( + split, + seed=config.seed, + task_type=config.task_type, + ) + _write_json(output_dir / "dataset_manifest.json", dataset_manifest) + _write_json(output_dir / "split_manifest.json", split_manifest) + _write_model(output_dir / "model.joblib", scorer) + + metadata = export_scorer_artifact( + scorer, + config=config, + train_count=len(split.train), + valid_count=len(split.valid), + test_count=len(split.test), + dataset_manifest_hash=json_hash(dataset_manifest), + training_config_hash=training_config_hash(config), + ) + metadata_path = output_dir / "metadata.json" + write_metadata_json(metadata_path, metadata) + return ConfidenceTrainingResult( + output_dir=output_dir, + export_path=export_path, + report_path=output_dir / "report.json", + metadata_path=metadata_path, + ) + + +def _run_feature_extraction_subprocess(config: ConfidenceTrainingConfig) -> None: + command = [ + sys.executable, + str(Path(__file__).resolve()), + "__extract_features", + "--task", + config.task_type, + "--dataset", + str(config.dataset_path), + "--output-dir", + str(config.output_dir), + "--encoder-name", + config.encoder_name, + "--max-length", + str(config.max_length), + "--seed", + str(config.seed), + ] + if config.encoder_revision is not None: + command.extend(["--encoder-revision", config.encoder_revision]) + if config.tokenizer_name is not None: + command.extend(["--tokenizer-name", config.tokenizer_name]) + if config.tokenizer_revision is not None: + command.extend(["--tokenizer-revision", config.tokenizer_revision]) + if config.cache_dir is not None: + command.extend(["--cache-dir", config.cache_dir]) + if config.local_files_only: + command.append("--local-files-only") + if config.allow_truncation: + command.append("--allow-truncation") + subprocess.run(command, cwd=ROOT, check=True) + + +def _parse_extract_args(argv: Sequence[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=argparse.SUPPRESS) + parser.add_argument( + "--task", required=True, choices=(TASK_QUERY, TASK_QUERY_CONTEXT) + ) + parser.add_argument("--dataset", required=True, type=Path) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--encoder-name", required=True) + parser.add_argument("--encoder-revision", default=None) + parser.add_argument("--tokenizer-name", default=None) + parser.add_argument("--tokenizer-revision", default=None) + parser.add_argument("--cache-dir", default=None) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--max-length", type=int, required=True) + parser.add_argument("--allow-truncation", action="store_true") + parser.add_argument("--seed", type=int, required=True) + return parser.parse_args(argv) + + +def _training_config_from_extract_args( + args: argparse.Namespace, +) -> ConfidenceTrainingConfig: + return ConfidenceTrainingConfig( + task_type=cast(TaskType, args.task), + dataset_path=args.dataset, + output_dir=args.output_dir, + export_path=args.output_dir / "unused.joblib", + encoder_name=args.encoder_name, + encoder_revision=args.encoder_revision, + tokenizer_name=args.tokenizer_name, + tokenizer_revision=args.tokenizer_revision, + cache_dir=args.cache_dir, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + seed=args.seed, + ) + + +def _write_feature_rows(path: Path, rows: Sequence[ConfidenceFeatureRow]) -> None: + path.write_text( + "".join(json.dumps(_feature_row_json(row)) + "\n" for row in rows), + encoding="utf-8", + ) + + +def _read_feature_rows(path: Path) -> list[ConfidenceFeatureRow]: + rows: list[ConfidenceFeatureRow] = [] + for line in path.read_text(encoding="utf-8").splitlines(): + if not line.strip(): + continue + rows.append(ConfidenceFeatureRow(**json.loads(line))) + return rows + + +def _generation_summary(result: ConfidenceGenerationResult) -> Mapping[str, Any]: + return { + "output_path": str(result.output_path), + "input_count": result.input_count, + "generated_count": result.generated_count, + "skipped_count": result.skipped_count, + "positive_count": result.positive_count, + "negative_count": result.negative_count, + } + + +def _training_summary(result: ConfidenceTrainingResult) -> Mapping[str, str]: + return { + "output_dir": str(result.output_dir), + "export_path": str(result.export_path), + "report_path": str(result.report_path), + "metadata_path": str(result.metadata_path), + } + + +def _benchmark_command(run_dir: Path, *, args: argparse.Namespace) -> str: + parts = [ + "uv", + "run", + "python", + "scripts/compare_reranking.py", + "--dataset", + args.benchmark_dataset, + "--algorithm", + "cbdr", + "--cbdr-answer-provider", + "lmstudio", + "--cbdr-base-artifact", + str(run_dir / "artifacts/query_answerability.joblib"), + "--cbdr-context-artifact", + str(run_dir / "artifacts/query_context_answerability.joblib"), + "--lmstudio-model", + str(args.lmstudio_model or "$LMSTUDIO_MODEL"), + "--output", + str(run_dir / "benchmark/cbdr_report.json"), + "--checkpoint-output", + str(run_dir / "benchmark/cbdr_checkpoint.jsonl"), + "--allow-live", + ] + if args.benchmark_cache_dir is not None: + parts.extend(["--cache-dir", str(args.benchmark_cache_dir)]) + if args.benchmark_candidates is not None: + parts.extend(["--candidates", str(args.benchmark_candidates)]) + if args.benchmark_max_cases is not None: + parts.extend(["--max-cases", str(args.benchmark_max_cases)]) + parts.extend(["--top-k", str(args.benchmark_top_k)]) + return " ".join(parts) + + +def _run_benchmark( + *, + args: argparse.Namespace, + output: Path, + checkpoint_output: Path, + base_artifact: Path, + context_artifact: Path, +) -> None: + output.parent.mkdir(parents=True, exist_ok=True) + command = [ + sys.executable, + str(ROOT / "scripts/compare_reranking.py"), + "--dataset", + args.benchmark_dataset, + "--algorithm", + "cbdr", + "--cbdr-answer-provider", + "lmstudio", + "--cbdr-base-artifact", + str(base_artifact), + "--cbdr-context-artifact", + str(context_artifact), + "--cbdr-skip-threshold", + str(args.skip_threshold), + "--cbdr-device", + args.device, + "--cbdr-max-length", + str(args.max_length), + "--lmstudio-max-tokens", + str(args.lmstudio_max_tokens), + "--top-k", + str(args.benchmark_top_k), + "--output", + str(output), + "--checkpoint-output", + str(checkpoint_output), + "--allow-live", + ] + if args.lmstudio_model is not None: + command.extend(["--lmstudio-model", args.lmstudio_model]) + if args.lmstudio_base_url is not None: + command.extend(["--lmstudio-base-url", args.lmstudio_base_url]) + if args.lmstudio_api_key is not None: + command.extend(["--lmstudio-api-key", args.lmstudio_api_key]) + if args.benchmark_cache_dir is not None: + command.extend(["--cache-dir", str(args.benchmark_cache_dir)]) + if args.benchmark_candidates is not None: + command.extend(["--candidates", str(args.benchmark_candidates)]) + if args.benchmark_max_cases is not None: + command.extend(["--max-cases", str(args.benchmark_max_cases)]) + if args.cache_dir is not None: + command.extend(["--cbdr-cache-dir", args.cache_dir]) + if args.local_files_only: + command.append("--cbdr-local-files-only") + if args.allow_truncation: + command.append("--cbdr-allow-truncation") + subprocess.run(command, cwd=ROOT, check=True) + + +def _require_input_file(path: Path, option_name: str) -> None: + if not path.exists(): + raise SystemExit(f"{option_name} file does not exist: {path}") + if not path.is_file(): + raise SystemExit(f"{option_name} must be a file: {path}") + + +def _build_qa_raw_files( + *, + raw_dir: Path, + query_raw: Path, + query_context_raw: Path, + args: argparse.Namespace, +) -> None: + if args.resume_generation and query_raw.exists() and query_context_raw.exists(): + return + command = [ + sys.executable, + str(ROOT / "scripts/build_qa_confidence_raw_dataset.py"), + "--source", + args.qa_source, + "--dataset-name", + args.qa_dataset_name, + "--dataset-config", + args.qa_dataset_config, + "--split", + args.qa_split, + "--output-dir", + str(raw_dir), + "--max-source-items", + str(args.qa_max_source_items), + "--max-query-items", + str(args.qa_max_query_items), + "--max-query-context-items", + str(args.qa_max_query_context_items), + "--max-context-chars", + str(args.max_context_chars), + ] + if args.qa_input_jsonl is not None: + command.extend(["--input-jsonl", str(args.qa_input_jsonl)]) + if args.overwrite: + command.append("--overwrite") + subprocess.run(command, cwd=ROOT, check=True) + + +def _write_json(path: Path, value: Mapping[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/smoke_lmstudio_confidence_pipeline.py b/scripts/smoke_lmstudio_confidence_pipeline.py new file mode 100644 index 0000000..0e8b178 --- /dev/null +++ b/scripts/smoke_lmstudio_confidence_pipeline.py @@ -0,0 +1,791 @@ +#!/usr/bin/env python +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +import tempfile +from collections.abc import Mapping, Sequence +from datetime import datetime +from pathlib import Path +from typing import Any, cast + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from ranksmith.confidence import StructuralConfidenceResult, TaskType # noqa: E402 +from ranksmith.confidence.encoder import FrozenAutoEncoder # noqa: E402 +from ranksmith.confidence_generation import ( # noqa: E402 + ConfidenceGenerationResult, + QueryAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationConfig, + generate_query_answerability_confidence_dataset, + generate_query_context_answerability_confidence_dataset, +) +from ranksmith.confidence_training import ( # noqa: E402 + ConfidenceTrainingConfig, + ConfidenceTrainingResult, +) +from ranksmith.confidence_training.artifact import ( # noqa: E402 + export_scorer_artifact, + write_metadata_json, +) +from ranksmith.confidence_training.dataset import ( # noqa: E402 + load_canonical_dataset, +) +from ranksmith.confidence_training.dataset_report import ( # noqa: E402 + build_dataset_report, +) +from ranksmith.confidence_training.features import ( # noqa: E402 + extract_feature_rows, +) +from ranksmith.confidence_training.manifest import ( # noqa: E402 + build_dataset_manifest, + build_split_manifest, + json_hash, + training_config_hash, +) +from ranksmith.confidence_training.pipeline import ( # noqa: E402 + _feature_row_json, + _write_json, + _write_model, + _write_report_markdown, +) +from ranksmith.confidence_training.report import ( # noqa: E402 + generate_training_report, +) +from ranksmith.confidence_training.split import split_dataset # noqa: E402 +from ranksmith.confidence_training.train import ( # noqa: E402 + calibrate_classifier, + train_lightgbm_classifier, +) +from ranksmith.confidence_training.types import ( # noqa: E402 + ConfidenceFeatureRow, +) +from ranksmith.integrations import ( # noqa: E402 + LMStudioModelProvider, + ProviderAnswerGenerator, +) +from ranksmith.strategies import CBDRStrategy # noqa: E402 +from ranksmith.types import Document # noqa: E402 + +TASK_QUERY = "query_answerability_confidence" +TASK_QUERY_CONTEXT = "query_context_answerability_confidence" +DEFAULT_ENCODER = "hf-internal-testing/tiny-random-bert" + + +def main(argv: Sequence[str] | None = None) -> int: + raw_argv = list(sys.argv[1:] if argv is None else argv) + if raw_argv[:1] == ["__extract_features"]: + return _extract_features_main(raw_argv[1:]) + if raw_argv[:1] == ["__rerank_smoke"]: + return _rerank_smoke_main(raw_argv[1:]) + + args = _parse_args(raw_argv) + run_dir = _resolve_run_dir(args.output_dir) + _prepare_run_dir(run_dir, overwrite=args.overwrite) + + provider = LMStudioModelProvider( + model=args.lmstudio_model, + base_url=args.lmstudio_base_url, + api_key=args.lmstudio_api_key, + timeout=args.timeout, + max_tokens=args.lmstudio_max_tokens, + ) + + raw_dir = run_dir / "raw" + canonical_dir = run_dir / "canonical" + training_dir = run_dir / "training" + artifacts_dir = run_dir / "artifacts" + reports_dir = run_dir / "reports" + for directory in (raw_dir, canonical_dir, training_dir, artifacts_dir, reports_dir): + directory.mkdir(parents=True, exist_ok=True) + + live_query_raw = raw_dir / "live_query_answerability.jsonl" + live_context_raw = raw_dir / "live_query_context_answerability.jsonl" + live_query_canonical = canonical_dir / "live_query_answerability.jsonl" + live_context_canonical = canonical_dir / "live_query_context_answerability.jsonl" + training_query_canonical = canonical_dir / "training_query_answerability.jsonl" + training_context_canonical = ( + canonical_dir / "training_query_context_answerability.jsonl" + ) + + _write_jsonl(live_query_raw, _live_query_rows()) + _write_jsonl(live_context_raw, _live_context_rows()) + + live_query_result = generate_query_answerability_confidence_dataset( + QueryAnswerabilityGenerationConfig( + input_path=live_query_raw, + output_path=live_query_canonical, + provider=provider, + overwrite=True, + max_items=args.max_items, + source="lmstudio-smoke-live", + ) + ) + live_context_result = generate_query_context_answerability_confidence_dataset( + QueryContextAnswerabilityGenerationConfig( + input_path=live_context_raw, + output_path=live_context_canonical, + provider=provider, + overwrite=True, + max_items=args.max_items, + max_context_chars=args.max_context_chars, + source="lmstudio-smoke-live", + ) + ) + + _write_json( + reports_dir / "live_query_report.json", + _dataset_report(live_query_canonical, TASK_QUERY), + ) + _write_json( + reports_dir / "live_query_context_report.json", + _dataset_report(live_context_canonical, TASK_QUERY_CONTEXT), + ) + + _write_jsonl(training_query_canonical, _training_query_rows()) + _write_jsonl(training_context_canonical, _training_context_rows()) + _write_json( + reports_dir / "training_query_report.json", + _dataset_report(training_query_canonical, TASK_QUERY), + ) + _write_json( + reports_dir / "training_query_context_report.json", + _dataset_report(training_context_canonical, TASK_QUERY_CONTEXT), + ) + + base_artifact = artifacts_dir / "query_answerability.joblib" + context_artifact = artifacts_dir / "query_context_answerability.joblib" + base_training = _train_smoke_scorer( + _training_config( + task_type=TASK_QUERY, + dataset_path=training_query_canonical, + output_dir=training_dir / "query_answerability", + export_path=base_artifact, + args=args, + ) + ) + context_training = _train_smoke_scorer( + _training_config( + task_type=TASK_QUERY_CONTEXT, + dataset_path=training_context_canonical, + output_dir=training_dir / "query_context_answerability", + export_path=context_artifact, + args=args, + ) + ) + + answer_generator = ProviderAnswerGenerator(provider=provider) + strategy = CBDRStrategy.from_artifacts( + base_artifact_path=base_training.export_path, + context_artifact_path=context_training.export_path, + answer_generator=answer_generator, + skip_threshold=args.skip_threshold, + cache_dir=args.cache_dir, + device=args.device, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + ) + rerank_results = _run_optional_rerank_smoke( + include_rerank=args.include_rerank, + run_dir=run_dir, + base_artifact=base_training.export_path, + context_artifact=context_training.export_path, + args=args, + ) + + summary = { + "run_dir": str(run_dir), + "live_generation": { + "query_answerability": _generation_summary(live_query_result), + "query_context_answerability": _generation_summary(live_context_result), + }, + "training_artifacts": { + "query_answerability": str(base_training.export_path), + "query_context_answerability": str(context_training.export_path), + }, + "reports_dir": str(reports_dir), + "cbdr_smoke": { + "strategy_loaded": strategy.algorithm == "cbdr", + "rerank_executed": args.include_rerank, + "results": rerank_results, + }, + } + _write_json(reports_dir / "summary.json", summary) + print(json.dumps(summary, ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +def _extract_features_main(argv: Sequence[str]) -> int: + args = _parse_extract_args(argv) + config = _training_config_from_extract_args(args) + output_dir = Path(config.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + samples = load_canonical_dataset(config.dataset_path, task_type=config.task_type) + split = split_dataset( + samples, + seed=config.seed, + train_ratio=config.train_ratio, + valid_ratio=config.valid_ratio, + test_ratio=config.test_ratio, + ) + encoder = FrozenAutoEncoder.from_pretrained( + encoder_name=config.encoder_name, + encoder_revision=config.encoder_revision, + tokenizer_name=config.tokenizer_name, + tokenizer_revision=config.tokenizer_revision, + hf_token=None, + local_files_only=config.local_files_only, + cache_dir=config.cache_dir, + device="cpu", + max_length=config.max_length, + allow_truncation=config.allow_truncation, + ) + for name, part in ( + ("train", split.train), + ("valid", split.valid), + ("test", split.test), + ): + rows = extract_feature_rows(part, encoder=encoder) + _write_feature_rows(output_dir / f"features_{name}.jsonl", rows) + return 0 + + +def _rerank_smoke_main(argv: Sequence[str]) -> int: + args = _parse_rerank_args(argv) + provider = LMStudioModelProvider( + model=args.lmstudio_model, + base_url=args.lmstudio_base_url, + api_key=args.lmstudio_api_key, + timeout=args.timeout, + max_tokens=args.lmstudio_max_tokens, + ) + answer_generator = _LoggingAnswerGenerator( + ProviderAnswerGenerator(provider=provider), + enabled=True, + ) + loaded = CBDRStrategy.from_artifacts( + base_artifact_path=args.base_artifact, + context_artifact_path=args.context_artifact, + answer_generator=answer_generator, + skip_threshold=args.skip_threshold, + cache_dir=args.cache_dir, + device=args.device, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + ) + strategy = CBDRStrategy( + base_estimator=_LoggingEstimator(loaded.base_estimator, label="base"), + context_estimator=_LoggingEstimator(loaded.context_estimator, label="context"), + answer_generator=answer_generator, + skip_threshold=args.skip_threshold, + max_document_chars=loaded.max_document_chars, + ) + results = _execute_rerank_smoke(strategy) + _write_json(args.output_json, {"results": results}) + return 0 + + +def _train_smoke_scorer(config: ConfidenceTrainingConfig) -> ConfidenceTrainingResult: + output_dir = Path(config.output_dir) + export_path = Path(config.export_path) + output_dir.mkdir(parents=True, exist_ok=True) + + _run_feature_extraction_subprocess(config) + train_rows = _read_feature_rows(output_dir / "features_train.jsonl") + valid_rows = _read_feature_rows(output_dir / "features_valid.jsonl") + test_rows = _read_feature_rows(output_dir / "features_test.jsonl") + + model = train_lightgbm_classifier(train_rows, seed=config.seed) + scorer = calibrate_classifier(model, valid_rows) + report = generate_training_report( + model, + scorer, + valid_rows=valid_rows, + test_rows=test_rows, + ) + _write_json(output_dir / "report.json", report.to_dict()) + _write_report_markdown(output_dir / "report.md", report) + + samples = load_canonical_dataset(config.dataset_path, task_type=config.task_type) + split = split_dataset( + samples, + seed=config.seed, + train_ratio=config.train_ratio, + valid_ratio=config.valid_ratio, + test_ratio=config.test_ratio, + ) + dataset_manifest = build_dataset_manifest( + Path(config.dataset_path), + task_type=config.task_type, + sample_count=len(samples), + ) + split_manifest = build_split_manifest( + split, + seed=config.seed, + task_type=config.task_type, + ) + _write_json(output_dir / "dataset_manifest.json", dataset_manifest) + _write_json(output_dir / "split_manifest.json", split_manifest) + _write_model(output_dir / "model.joblib", scorer) + + metadata = export_scorer_artifact( + scorer, + config=config, + train_count=len(split.train), + valid_count=len(split.valid), + test_count=len(split.test), + dataset_manifest_hash=json_hash(dataset_manifest), + training_config_hash=training_config_hash(config), + ) + metadata_path = output_dir / "metadata.json" + write_metadata_json(metadata_path, metadata) + + return ConfidenceTrainingResult( + output_dir=output_dir, + export_path=export_path, + report_path=output_dir / "report.json", + metadata_path=metadata_path, + ) + + +def _run_feature_extraction_subprocess(config: ConfidenceTrainingConfig) -> None: + command = [ + sys.executable, + str(Path(__file__).resolve()), + "__extract_features", + "--task", + config.task_type, + "--dataset", + str(config.dataset_path), + "--output-dir", + str(config.output_dir), + "--encoder-name", + config.encoder_name, + "--max-length", + str(config.max_length), + "--seed", + str(config.seed), + ] + if config.encoder_revision is not None: + command.extend(["--encoder-revision", config.encoder_revision]) + if config.tokenizer_name is not None: + command.extend(["--tokenizer-name", config.tokenizer_name]) + if config.tokenizer_revision is not None: + command.extend(["--tokenizer-revision", config.tokenizer_revision]) + if config.cache_dir is not None: + command.extend(["--cache-dir", config.cache_dir]) + if config.local_files_only: + command.append("--local-files-only") + if config.allow_truncation: + command.append("--allow-truncation") + subprocess.run(command, cwd=ROOT, check=True) + + +def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Run a live LM Studio smoke for confidence generation, " + "training, and CBDR loading." + ) + ) + parser.add_argument("--lmstudio-model", default=None) + parser.add_argument("--lmstudio-base-url", default=None) + parser.add_argument("--lmstudio-api-key", default=None) + parser.add_argument("--lmstudio-max-tokens", type=int, default=128) + parser.add_argument("--timeout", type=float, default=60.0) + parser.add_argument("--max-items", type=int, default=2) + parser.add_argument("--max-context-chars", type=int, default=8000) + parser.add_argument("--output-dir", type=Path, default=None) + parser.add_argument("--overwrite", action="store_true") + parser.add_argument("--encoder-name", default=DEFAULT_ENCODER) + parser.add_argument("--encoder-revision", default=None) + parser.add_argument("--tokenizer-name", default=None) + parser.add_argument("--tokenizer-revision", default=None) + parser.add_argument("--cache-dir", default=None) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--device", default="cpu") + parser.add_argument("--max-length", type=int, default=128) + parser.add_argument("--allow-truncation", action="store_true") + parser.add_argument("--include-rerank", action="store_true") + parser.add_argument("--skip-threshold", type=float, default=0.8) + args = parser.parse_args(argv) + if args.max_items < 1: + parser.error("--max-items must be >= 1") + if args.max_context_chars < 1: + parser.error("--max-context-chars must be >= 1") + if args.lmstudio_max_tokens < 1: + parser.error("--lmstudio-max-tokens must be >= 1") + if args.skip_threshold < 0.0 or args.skip_threshold > 1.0: + parser.error("--skip-threshold must be in [0, 1]") + return args + + +def _parse_extract_args(argv: Sequence[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=argparse.SUPPRESS) + parser.add_argument( + "--task", required=True, choices=(TASK_QUERY, TASK_QUERY_CONTEXT) + ) + parser.add_argument("--dataset", required=True, type=Path) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--encoder-name", required=True) + parser.add_argument("--encoder-revision", default=None) + parser.add_argument("--tokenizer-name", default=None) + parser.add_argument("--tokenizer-revision", default=None) + parser.add_argument("--cache-dir", default=None) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--max-length", type=int, required=True) + parser.add_argument("--allow-truncation", action="store_true") + parser.add_argument("--seed", type=int, required=True) + return parser.parse_args(argv) + + +def _parse_rerank_args(argv: Sequence[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=argparse.SUPPRESS) + parser.add_argument("--base-artifact", required=True, type=Path) + parser.add_argument("--context-artifact", required=True, type=Path) + parser.add_argument("--output-json", required=True, type=Path) + parser.add_argument("--lmstudio-model", default=None) + parser.add_argument("--lmstudio-base-url", default=None) + parser.add_argument("--lmstudio-api-key", default=None) + parser.add_argument("--lmstudio-max-tokens", type=int, required=True) + parser.add_argument("--timeout", type=float, required=True) + parser.add_argument("--skip-threshold", type=float, required=True) + parser.add_argument("--cache-dir", default=None) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--device", required=True) + parser.add_argument("--max-length", type=int, required=True) + parser.add_argument("--allow-truncation", action="store_true") + return parser.parse_args(argv) + + +def _training_config_from_extract_args( + args: argparse.Namespace, +) -> ConfidenceTrainingConfig: + return ConfidenceTrainingConfig( + task_type=cast(TaskType, args.task), + dataset_path=args.dataset, + output_dir=args.output_dir, + export_path=args.output_dir / "unused.joblib", + encoder_name=args.encoder_name, + encoder_revision=args.encoder_revision, + tokenizer_name=args.tokenizer_name, + tokenizer_revision=args.tokenizer_revision, + cache_dir=args.cache_dir, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + seed=args.seed, + ) + + +def _resolve_run_dir(output_dir: Path | None) -> Path: + if output_dir is not None: + return output_dir + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + return Path(tempfile.gettempdir()) / f"ranksmith-lmstudio-smoke-{timestamp}" + + +def _prepare_run_dir(run_dir: Path, *, overwrite: bool) -> None: + if run_dir.exists() and any(run_dir.iterdir()) and not overwrite: + raise SystemExit(f"output dir already exists: {run_dir}. Use --overwrite.") + run_dir.mkdir(parents=True, exist_ok=True) + + +def _training_config( + *, + task_type: str, + dataset_path: Path, + output_dir: Path, + export_path: Path, + args: argparse.Namespace, +) -> ConfidenceTrainingConfig: + return ConfidenceTrainingConfig( + task_type=cast(TaskType, task_type), + dataset_path=dataset_path, + output_dir=output_dir, + export_path=export_path, + encoder_name=args.encoder_name, + encoder_revision=args.encoder_revision, + tokenizer_name=args.tokenizer_name, + tokenizer_revision=args.tokenizer_revision, + cache_dir=args.cache_dir, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + train_ratio=0.8, + valid_ratio=0.1, + test_ratio=0.1, + ) + + +def _dataset_report(path: Path, task_type: str) -> Mapping[str, Any]: + return build_dataset_report(path, cast(TaskType, task_type)) + + +def _generation_summary(result: ConfidenceGenerationResult) -> Mapping[str, Any]: + return { + "output_path": str(result.output_path), + "input_count": result.input_count, + "generated_count": result.generated_count, + "skipped_count": result.skipped_count, + "positive_count": result.positive_count, + "negative_count": result.negative_count, + } + + +def _live_query_rows() -> list[Mapping[str, Any]]: + return [ + { + "id": "live-q-1", + "query": "What is the capital of France?", + "gold_answer": "Paris", + "source": "lmstudio-smoke", + "group_id": "live-q", + }, + { + "id": "live-q-2", + "query": ( + "What exact color was the mayor's umbrella " + "in the unpublished town memo?" + ), + "gold_answer": "__NO_ANSWER__", + "source": "lmstudio-smoke", + "group_id": "live-q", + }, + ] + + +def _live_context_rows() -> list[Mapping[str, Any]]: + return [ + { + "id": "live-qc-1", + "query": "Who played Karen in Married to the Mob?", + "context": "Nancy Travis played Karen in the film Married to the Mob.", + "gold_answer": "Nancy Travis", + "source": "lmstudio-smoke", + "group_id": "live-qc", + }, + { + "id": "live-qc-2", + "query": "Who played Karen in Married to the Mob?", + "context": "Michelle Pfeiffer starred in Married to the Mob as Angela.", + "gold_answer": "__NO_ANSWER__", + "source": "lmstudio-smoke", + "group_id": "live-qc", + }, + ] + + +def _training_query_rows() -> list[Mapping[str, Any]]: + rows: list[Mapping[str, Any]] = [] + for index in range(15): + rows.append( + { + "id": f"train-q-pos-{index}", + "task_type": TASK_QUERY, + "query": f"What is synthetic known fact {index}?", + "answer": f"Known answer {index}", + "gold_answer": f"Known answer {index}", + "label": 1, + "source": "synthetic-smoke", + } + ) + rows.append( + { + "id": f"train-q-neg-{index}", + "task_type": TASK_QUERY, + "query": f"What is synthetic unknown fact {index}?", + "answer": "__NO_ANSWER__", + "gold_answer": "__NO_ANSWER__", + "label": 0, + "source": "synthetic-smoke", + } + ) + return rows + + +def _training_context_rows() -> list[Mapping[str, Any]]: + rows: list[Mapping[str, Any]] = [] + for index in range(15): + rows.append( + { + "id": f"train-qc-pos-{index}", + "task_type": TASK_QUERY_CONTEXT, + "query": f"Who is the synthetic actor {index}?", + "context": f"Context says Synthetic Actor {index} played the role.", + "answer": f"Synthetic Actor {index}", + "gold_answer": f"Synthetic Actor {index}", + "label": 1, + "source": "synthetic-smoke", + } + ) + rows.append( + { + "id": f"train-qc-neg-{index}", + "task_type": TASK_QUERY_CONTEXT, + "query": f"Who is the synthetic missing actor {index}?", + "context": "This context does not contain the requested actor.", + "answer": "__NO_ANSWER__", + "gold_answer": "__NO_ANSWER__", + "label": 0, + "source": "synthetic-smoke", + } + ) + return rows + + +def _write_jsonl(path: Path, rows: Sequence[Mapping[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "".join( + json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n" for row in rows + ), + encoding="utf-8", + ) + + +def _write_feature_rows(path: Path, rows: Sequence[ConfidenceFeatureRow]) -> None: + path.write_text( + "".join(json.dumps(_feature_row_json(row)) + "\n" for row in rows), + encoding="utf-8", + ) + + +def _read_feature_rows(path: Path) -> list[ConfidenceFeatureRow]: + rows: list[ConfidenceFeatureRow] = [] + for line in path.read_text(encoding="utf-8").splitlines(): + if not line.strip(): + continue + rows.append(ConfidenceFeatureRow(**json.loads(line))) + return rows + + +class _LoggingAnswerGenerator: + def __init__(self, inner: ProviderAnswerGenerator, *, enabled: bool) -> None: + self._inner = inner + self._enabled = enabled + + def answer_query(self, query: str) -> str: + if self._enabled: + print("[cbdr] answer_query start", flush=True) + answer = self._inner.answer_query(query) + if self._enabled: + print("[cbdr] answer_query done", flush=True) + return answer + + def answer_with_context(self, query: str, context: str) -> str: + if self._enabled: + print( + f"[cbdr] answer_with_context start chars={len(context)}", + flush=True, + ) + answer = self._inner.answer_with_context(query, context) + if self._enabled: + print("[cbdr] answer_with_context done", flush=True) + return answer + + +class _LoggingEstimator: + def __init__(self, inner: Any, *, label: str) -> None: + self._inner = inner + self._label = label + + @property + def task_type(self) -> str: + return str(self._inner.task_type) + + def score(self, item: object) -> StructuralConfidenceResult: + print(f"[cbdr] {self._label}_estimator score start", flush=True) + result = cast(StructuralConfidenceResult, self._inner.score(item)) + print( + f"[cbdr] {self._label}_estimator score done score={result.score}", + flush=True, + ) + return result + + +def _run_optional_rerank_smoke( + *, + include_rerank: bool, + run_dir: Path, + base_artifact: Path, + context_artifact: Path, + args: argparse.Namespace, +) -> list[Mapping[str, Any]]: + if not include_rerank: + return [] + output_json = run_dir / "reports" / "cbdr_rerank_smoke.json" + command = [ + sys.executable, + str(Path(__file__).resolve()), + "__rerank_smoke", + "--base-artifact", + str(base_artifact), + "--context-artifact", + str(context_artifact), + "--output-json", + str(output_json), + "--lmstudio-max-tokens", + str(args.lmstudio_max_tokens), + "--timeout", + str(args.timeout), + "--skip-threshold", + str(args.skip_threshold), + "--device", + args.device, + "--max-length", + str(args.max_length), + ] + if args.lmstudio_model is not None: + command.extend(["--lmstudio-model", args.lmstudio_model]) + if args.lmstudio_base_url is not None: + command.extend(["--lmstudio-base-url", args.lmstudio_base_url]) + if args.lmstudio_api_key is not None: + command.extend(["--lmstudio-api-key", args.lmstudio_api_key]) + if args.cache_dir is not None: + command.extend(["--cache-dir", args.cache_dir]) + if args.local_files_only: + command.append("--local-files-only") + if args.allow_truncation: + command.append("--allow-truncation") + subprocess.run(command, cwd=ROOT, check=True) + data = json.loads(output_json.read_text(encoding="utf-8")) + return list(data["results"]) + + +def _execute_rerank_smoke(strategy: CBDRStrategy) -> list[Mapping[str, Any]]: + print("[cbdr] rerank smoke start", flush=True) + results = strategy.rerank( + query="Who played Karen in Married to the Mob?", + documents=[ + Document( + id="nancy-travis", + text="Nancy Travis played Karen in the film Married to the Mob.", + ), + Document( + id="michelle-pfeiffer", + text="Michelle Pfeiffer starred in Married to the Mob as Angela.", + ), + ], + top_k=2, + ) + print("[cbdr] rerank smoke done", flush=True) + return [ + { + "rank": result.rank, + "document_id": result.document.id, + "original_index": result.original_index, + "metadata": result.metadata, + } + for result in results + ] + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/train_confidence_scorer.py b/scripts/train_confidence_scorer.py new file mode 100644 index 0000000..de52466 --- /dev/null +++ b/scripts/train_confidence_scorer.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import argparse +import json +import sys +from collections.abc import Sequence +from pathlib import Path +from typing import cast + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from ranksmith.confidence import TaskType # noqa: E402 +from ranksmith.confidence_training import ( # noqa: E402 + ConfidenceTrainingConfig, + ConfidenceTrainingResult, + train_confidence_scorer, +) + +TASK_QUERY = "query_answerability_confidence" +TASK_QUERY_CONTEXT = "query_context_answerability_confidence" +SUPPORTED_TASKS = (TASK_QUERY, TASK_QUERY_CONTEXT) + + +def main(argv: Sequence[str] | None = None) -> int: + args = _parse_args(argv) + result = train_confidence_scorer( + ConfidenceTrainingConfig( + task_type=cast(TaskType, args.task), + dataset_path=args.dataset, + output_dir=args.output_dir, + export_path=args.export_path, + encoder_name=args.encoder_name, + encoder_revision=args.encoder_revision, + tokenizer_name=args.tokenizer_name, + tokenizer_revision=args.tokenizer_revision, + cache_dir=args.cache_dir, + local_files_only=args.local_files_only, + max_length=args.max_length, + allow_truncation=args.allow_truncation, + seed=args.seed, + train_ratio=args.train_ratio, + valid_ratio=args.valid_ratio, + test_ratio=args.test_ratio, + calibration_method=args.calibration_method, + ) + ) + + print(json.dumps(_training_summary(result), ensure_ascii=False, sort_keys=True)) + return 0 + + +def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Train a confidence scorer artifact.") + parser.add_argument("--task", required=True, choices=SUPPORTED_TASKS) + parser.add_argument("--dataset", required=True, type=Path) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--export-path", required=True, type=Path) + parser.add_argument("--encoder-name", default="bert-base-uncased") + parser.add_argument("--encoder-revision", default=None) + parser.add_argument("--tokenizer-name", default=None) + parser.add_argument("--tokenizer-revision", default=None) + parser.add_argument("--cache-dir", default=None) + parser.add_argument("--local-files-only", action="store_true") + parser.add_argument("--max-length", type=int, default=256) + parser.add_argument("--allow-truncation", action="store_true") + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--train-ratio", type=float, default=0.8) + parser.add_argument("--valid-ratio", type=float, default=0.1) + parser.add_argument("--test-ratio", type=float, default=0.1) + parser.add_argument("--calibration-method", choices=("sigmoid",), default="sigmoid") + return parser.parse_args(argv) + + +def _training_summary(result: ConfidenceTrainingResult) -> dict[str, str]: + return { + "output_dir": str(result.output_dir), + "export_path": str(result.export_path), + "report_path": str(result.report_path), + "metadata_path": str(result.metadata_path), + } + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/ranksmith/azure.py b/src/ranksmith/azure.py index 7d7aa12..41d412e 100644 --- a/src/ranksmith/azure.py +++ b/src/ranksmith/azure.py @@ -28,6 +28,8 @@ AsyncPairwiseStrategy, AsyncSetwiseStrategy, AsyncTourRankStrategy, + CBDRStrategy, + ConfidenceGainStrategy, ListwiseStrategy, PairwiseStrategy, SetwiseStrategy, @@ -162,6 +164,8 @@ async def rerank( def _is_builtin_sync_strategy(strategy: object) -> bool: return type(strategy) in { AcuRankStrategy, + CBDRStrategy, + ConfidenceGainStrategy, ListwiseStrategy, PairwiseStrategy, SetwiseStrategy, diff --git a/src/ranksmith/confidence/__init__.py b/src/ranksmith/confidence/__init__.py index 488b61f..88343e8 100644 --- a/src/ranksmith/confidence/__init__.py +++ b/src/ranksmith/confidence/__init__.py @@ -9,6 +9,8 @@ from ranksmith.confidence.types import ( AnswerConfidenceInput, JudgmentConfidenceInput, + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, ScoreOutput, ScorerMetadata, StructuralConfidenceInput, @@ -24,6 +26,8 @@ "ConfidenceError", "ConfidenceInputError", "JudgmentConfidenceInput", + "QueryAnswerabilityConfidenceInput", + "QueryContextAnswerabilityConfidenceInput", "ScorerMetadata", "ScoreOutput", "StructuralConfidenceEstimator", diff --git a/src/ranksmith/confidence/scorer.py b/src/ranksmith/confidence/scorer.py index 555409a..8346e08 100644 --- a/src/ranksmith/confidence/scorer.py +++ b/src/ranksmith/confidence/scorer.py @@ -293,7 +293,12 @@ def _validate_metadata_values(metadata: ScorerMetadata) -> None: raise ConfidenceArtifactError("unsupported local_stride") if metadata.input_template_version != INPUT_TEMPLATE_VERSION: raise ConfidenceArtifactError("unsupported input_template_version") - if metadata.task_type not in {"answer_confidence", "judgment_confidence"}: + if metadata.task_type not in { + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", + }: raise ConfidenceArtifactError("invalid task_type") if not metadata.scorer_type.strip(): raise ConfidenceArtifactError("scorer_type must be non-empty") @@ -333,6 +338,10 @@ def _task_type(value: object) -> TaskType: return "answer_confidence" if value == "judgment_confidence": return "judgment_confidence" + if value == "query_answerability_confidence": + return "query_answerability_confidence" + if value == "query_context_answerability_confidence": + return "query_context_answerability_confidence" raise ConfidenceArtifactError("invalid task_type") diff --git a/src/ranksmith/confidence/templates.py b/src/ranksmith/confidence/templates.py index 019de36..681d5d3 100644 --- a/src/ranksmith/confidence/templates.py +++ b/src/ranksmith/confidence/templates.py @@ -4,6 +4,8 @@ from ranksmith.confidence.types import ( AnswerConfidenceInput, JudgmentConfidenceInput, + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, StructuralConfidenceInput, TaskType, ) @@ -40,4 +42,25 @@ def format_confidence_input( judgment = _require_non_empty(item.judgment, field_name="judgment") return f"Query:\n{query}\n\nDocument:\n{document}\n\nJudgment:\n{judgment}" + if task_type == "query_answerability_confidence": + if not isinstance(item, QueryAnswerabilityConfidenceInput): + raise ConfidenceInputError( + "query_answerability_confidence requires " + "QueryAnswerabilityConfidenceInput" + ) + query = _require_non_empty(item.query, field_name="query") + answer = _require_non_empty(item.answer, field_name="answer") + return f"Query:\n{query}\n\nAnswer:\n{answer}" + + if task_type == "query_context_answerability_confidence": + if not isinstance(item, QueryContextAnswerabilityConfidenceInput): + raise ConfidenceInputError( + "query_context_answerability_confidence requires " + "QueryContextAnswerabilityConfidenceInput" + ) + query = _require_non_empty(item.query, field_name="query") + context = _require_non_empty(item.context, field_name="context") + answer = _require_non_empty(item.answer, field_name="answer") + return f"Query:\n{query}\n\nContext:\n{context}\n\nAnswer:\n{answer}" + raise ConfidenceInputError(f"unsupported task_type: {task_type!r}") diff --git a/src/ranksmith/confidence/types.py b/src/ranksmith/confidence/types.py index c9db4a1..a95a5f2 100644 --- a/src/ranksmith/confidence/types.py +++ b/src/ranksmith/confidence/types.py @@ -5,7 +5,12 @@ from types import MappingProxyType from typing import Any, Literal, Protocol, TypeAlias -TaskType: TypeAlias = Literal["answer_confidence", "judgment_confidence"] +TaskType: TypeAlias = Literal[ + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", +] ScoreOutput: TypeAlias = Literal["probability"] @@ -22,7 +27,25 @@ class JudgmentConfidenceInput: judgment: str -StructuralConfidenceInput: TypeAlias = AnswerConfidenceInput | JudgmentConfidenceInput +@dataclass(frozen=True) +class QueryAnswerabilityConfidenceInput: + query: str + answer: str + + +@dataclass(frozen=True) +class QueryContextAnswerabilityConfidenceInput: + query: str + context: str + answer: str + + +StructuralConfidenceInput: TypeAlias = ( + AnswerConfidenceInput + | JudgmentConfidenceInput + | QueryAnswerabilityConfidenceInput + | QueryContextAnswerabilityConfidenceInput +) @dataclass(frozen=True) diff --git a/src/ranksmith/confidence_generation/__init__.py b/src/ranksmith/confidence_generation/__init__.py index d046e08..3c95e4c 100644 --- a/src/ranksmith/confidence_generation/__init__.py +++ b/src/ranksmith/confidence_generation/__init__.py @@ -6,10 +6,14 @@ from ranksmith.confidence_generation.pipeline import ( generate_answer_confidence_dataset, generate_judgment_confidence_dataset, + generate_query_answerability_confidence_dataset, + generate_query_context_answerability_confidence_dataset, ) from ranksmith.confidence_generation.types import ( AnswerGenerationConfig, ConfidenceGenerationResult, + QueryAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationConfig, RelevanceGenerationConfig, ) @@ -19,7 +23,11 @@ "ConfidenceGenerationInputError", "ConfidenceGenerationParseError", "ConfidenceGenerationResult", + "QueryAnswerabilityGenerationConfig", + "QueryContextAnswerabilityGenerationConfig", "RelevanceGenerationConfig", "generate_answer_confidence_dataset", "generate_judgment_confidence_dataset", + "generate_query_answerability_confidence_dataset", + "generate_query_context_answerability_confidence_dataset", ] diff --git a/src/ranksmith/confidence_generation/io.py b/src/ranksmith/confidence_generation/io.py index fc7307d..2794c53 100644 --- a/src/ranksmith/confidence_generation/io.py +++ b/src/ranksmith/confidence_generation/io.py @@ -10,13 +10,34 @@ from ranksmith.confidence_generation.errors import ConfidenceGenerationInputError from ranksmith.confidence_generation.types import ( AnswerGenerationSample, + QueryAnswerabilityGenerationSample, + QueryContextAnswerabilityGenerationSample, RelevanceGenerationSample, ) -TaskType = Literal["answer_confidence", "judgment_confidence"] +TaskType = Literal[ + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", +] _ANSWER_REQUIRED = ("id", "query", "context", "gold_answer") _ANSWER_ALLOWED = {*_ANSWER_REQUIRED, "source", "group_id", "metadata"} +_QUERY_ANSWERABILITY_REQUIRED = ("id", "query", "gold_answer") +_QUERY_ANSWERABILITY_ALLOWED = { + *_QUERY_ANSWERABILITY_REQUIRED, + "source", + "group_id", + "metadata", +} +_QUERY_CONTEXT_ANSWERABILITY_REQUIRED = ("id", "query", "context", "gold_answer") +_QUERY_CONTEXT_ANSWERABILITY_ALLOWED = { + *_QUERY_CONTEXT_ANSWERABILITY_REQUIRED, + "source", + "group_id", + "metadata", +} _RELEVANCE_REQUIRED = ("id", "query", "document", "relevance_label") _RELEVANCE_ALLOWED = {*_RELEVANCE_REQUIRED, "source", "group_id", "metadata"} _ANSWER_OUTPUT_REQUIRED = ("id", "context", "answer", "label") @@ -27,6 +48,29 @@ "group_id", "metadata", } +_QUERY_ANSWERABILITY_OUTPUT_REQUIRED = ("id", "task_type", "query", "answer", "label") +_QUERY_ANSWERABILITY_OUTPUT_ALLOWED = { + *_QUERY_ANSWERABILITY_OUTPUT_REQUIRED, + "gold_answer", + "source", + "group_id", + "metadata", +} +_QUERY_CONTEXT_ANSWERABILITY_OUTPUT_REQUIRED = ( + "id", + "task_type", + "query", + "context", + "answer", + "label", +) +_QUERY_CONTEXT_ANSWERABILITY_OUTPUT_ALLOWED = { + *_QUERY_CONTEXT_ANSWERABILITY_OUTPUT_REQUIRED, + "gold_answer", + "source", + "group_id", + "metadata", +} _JUDGMENT_OUTPUT_REQUIRED = ( "id", "query", @@ -70,6 +114,63 @@ def load_answer_generation_samples( return samples +def load_query_answerability_generation_samples( + path: str | Path, +) -> list[QueryAnswerabilityGenerationSample]: + samples: list[QueryAnswerabilityGenerationSample] = [] + seen: set[str] = set() + for line_number, row in _read_jsonl_objects(Path(path)): + try: + _validate_keys( + row, + required=_QUERY_ANSWERABILITY_REQUIRED, + allowed=_QUERY_ANSWERABILITY_ALLOWED, + ) + sample = QueryAnswerabilityGenerationSample( + id=_required_text(row, "id"), + query=_required_text(row, "query"), + gold_answer=_gold_answer(row["gold_answer"]), + source=_optional_text(row.get("source"), "source"), + group_id=_optional_text(row.get("group_id"), "group_id"), + metadata=_metadata(row.get("metadata")), + ) + except ConfidenceGenerationInputError as exc: + raise ConfidenceGenerationInputError(f"line {line_number}: {exc}") from exc + _check_duplicate(sample.id, seen) + samples.append(sample) + return samples + + +def load_query_context_answerability_generation_samples( + path: str | Path, + *, + max_context_chars: int, +) -> list[QueryContextAnswerabilityGenerationSample]: + samples: list[QueryContextAnswerabilityGenerationSample] = [] + seen: set[str] = set() + for line_number, row in _read_jsonl_objects(Path(path)): + try: + _validate_keys( + row, + required=_QUERY_CONTEXT_ANSWERABILITY_REQUIRED, + allowed=_QUERY_CONTEXT_ANSWERABILITY_ALLOWED, + ) + sample = QueryContextAnswerabilityGenerationSample( + id=_required_text(row, "id"), + query=_required_text(row, "query"), + context=_bounded_text(row, "context", max_context_chars), + gold_answer=_gold_answer(row["gold_answer"]), + source=_optional_text(row.get("source"), "source"), + group_id=_optional_text(row.get("group_id"), "group_id"), + metadata=_metadata(row.get("metadata")), + ) + except ConfidenceGenerationInputError as exc: + raise ConfidenceGenerationInputError(f"line {line_number}: {exc}") from exc + _check_duplicate(sample.id, seen) + samples.append(sample) + return samples + + def load_relevance_generation_samples( path: str | Path, *, @@ -123,7 +224,7 @@ def open_output_path( def write_jsonl_row(handle: IO[str], row: Mapping[str, Any]) -> None: - handle.write(json.dumps(dict(row), ensure_ascii=False, allow_nan=False) + "\n") + handle.write(json.dumps(dict(row), ensure_ascii=True, allow_nan=False) + "\n") handle.flush() @@ -294,7 +395,7 @@ def _validate_output_task(row: Mapping[str, Any], task_type: TaskType) -> str: _label(row["label"]) if "gold_answer" in row: _gold_answer(row["gold_answer"]) - else: + elif task_type == "judgment_confidence": _validate_keys( row, required=_JUDGMENT_OUTPUT_REQUIRED, @@ -306,6 +407,33 @@ def _validate_output_task(row: Mapping[str, Any], task_type: TaskType) -> str: _judgment(row["judgment"]) _label(row["label"]) _relevance_label(row["relevance_label"]) + elif task_type == "query_answerability_confidence": + _validate_keys( + row, + required=_QUERY_ANSWERABILITY_OUTPUT_REQUIRED, + allowed=_QUERY_ANSWERABILITY_OUTPUT_ALLOWED, + ) + _validate_task_type_field(row, task_type) + row_id = _required_text(row, "id") + _required_text(row, "query") + _required_text(row, "answer") + _label(row["label"]) + if "gold_answer" in row: + _gold_answer(row["gold_answer"]) + else: + _validate_keys( + row, + required=_QUERY_CONTEXT_ANSWERABILITY_OUTPUT_REQUIRED, + allowed=_QUERY_CONTEXT_ANSWERABILITY_OUTPUT_ALLOWED, + ) + _validate_task_type_field(row, task_type) + row_id = _required_text(row, "id") + _required_text(row, "query") + _required_text(row, "context") + _required_text(row, "answer") + _label(row["label"]) + if "gold_answer" in row: + _gold_answer(row["gold_answer"]) if "source" in row: _required_text(row, "source") @@ -316,6 +444,14 @@ def _validate_output_task(row: Mapping[str, Any], task_type: TaskType) -> str: return row_id +def _validate_task_type_field(row: Mapping[str, Any], task_type: TaskType) -> None: + value = row["task_type"] + if value != task_type: + raise ConfidenceGenerationInputError( + f"task_type must be {task_type!r} for requested task" + ) + + def _label(value: object) -> int: if type(value) is not int or value not in {0, 1}: raise ConfidenceGenerationInputError("label must be integer 0 or 1") diff --git a/src/ranksmith/confidence_generation/pipeline.py b/src/ranksmith/confidence_generation/pipeline.py index b7687d6..3948300 100644 --- a/src/ranksmith/confidence_generation/pipeline.py +++ b/src/ranksmith/confidence_generation/pipeline.py @@ -7,6 +7,8 @@ from ranksmith.confidence_generation.io import ( load_answer_generation_samples, load_completed_ids, + load_query_answerability_generation_samples, + load_query_context_answerability_generation_samples, load_relevance_generation_samples, open_output_path, write_jsonl_row, @@ -22,14 +24,22 @@ ) from ranksmith.confidence_generation.prompts import ( ANSWER_SYSTEM_PROMPT, + QUERY_ANSWERABILITY_SYSTEM_PROMPT, + QUERY_CONTEXT_ANSWERABILITY_SYSTEM_PROMPT, RELEVANCE_SYSTEM_PROMPT, build_answer_prompt, + build_query_answerability_prompt, + build_query_context_answerability_prompt, build_relevance_prompt, ) from ranksmith.confidence_generation.types import ( AnswerGenerationConfig, AnswerGenerationSample, ConfidenceGenerationResult, + QueryAnswerabilityGenerationConfig, + QueryAnswerabilityGenerationSample, + QueryContextAnswerabilityGenerationConfig, + QueryContextAnswerabilityGenerationSample, RelevanceGenerationConfig, RelevanceGenerationSample, UsageCallback, @@ -93,6 +103,64 @@ def generate_judgment_confidence_dataset( ) +def generate_query_answerability_confidence_dataset( + config: QueryAnswerabilityGenerationConfig, +) -> ConfidenceGenerationResult: + samples = load_query_answerability_generation_samples(config.input_path) + output_path = Path(config.output_path) + completed_ids = ( + load_completed_ids(output_path, task_type="query_answerability_confidence") + if config.resume + else set() + ) + + return _write_generation_dataset( + samples=samples, + output_path=output_path, + completed_ids=completed_ids, + overwrite=config.overwrite, + resume=config.resume, + max_items=config.max_items, + get_id=lambda sample: sample.id, + build_row=lambda sample: _build_query_answerability_generated_row( + sample, + config, + ), + ) + + +def generate_query_context_answerability_confidence_dataset( + config: QueryContextAnswerabilityGenerationConfig, +) -> ConfidenceGenerationResult: + samples = load_query_context_answerability_generation_samples( + config.input_path, + max_context_chars=config.max_context_chars, + ) + output_path = Path(config.output_path) + completed_ids = ( + load_completed_ids( + output_path, + task_type="query_context_answerability_confidence", + ) + if config.resume + else set() + ) + + return _write_generation_dataset( + samples=samples, + output_path=output_path, + completed_ids=completed_ids, + overwrite=config.overwrite, + resume=config.resume, + max_items=config.max_items, + get_id=lambda sample: sample.id, + build_row=lambda sample: _build_query_context_answerability_generated_row( + sample, + config, + ), + ) + + def _call_provider( provider: ModelProvider, *, @@ -307,3 +375,136 @@ def _judgment_canonical_row( if sample.group_id is not None: row["group_id"] = sample.group_id return row + + +def _build_query_answerability_generated_row( + sample: QueryAnswerabilityGenerationSample, + config: QueryAnswerabilityGenerationConfig, +) -> Mapping[str, Any]: + raw_output = _call_provider( + config.provider, + system=QUERY_ANSWERABILITY_SYSTEM_PROMPT, + user=build_query_answerability_prompt( + sample, + no_answer_value=config.no_answer_value, + ), + on_usage=config.on_usage, + ) + answer = parse_answer_output(raw_output) + label = int( + normalized_exact_match( + answer, + sample.gold_answer, + no_answer_value=config.no_answer_value, + ) + ) + return _query_answerability_canonical_row( + sample, + answer=answer, + label=label, + raw_output=raw_output, + config=config, + ) + + +def _query_answerability_canonical_row( + sample: QueryAnswerabilityGenerationSample, + *, + answer: str, + label: int, + raw_output: str, + config: QueryAnswerabilityGenerationConfig, +) -> dict[str, Any]: + generation: dict[str, Any] = { + "generation_task": "query_answerability", + "match_policy": "normalized_exact", + "no_answer_value": config.no_answer_value, + } + if config.include_raw_model_output: + generation["raw_model_output"] = raw_output + + row: dict[str, Any] = { + "id": sample.id, + "task_type": "query_answerability_confidence", + "query": sample.query, + "answer": answer, + "gold_answer": sample.gold_answer, + "label": label, + "metadata": { + "input_metadata": dict(sample.metadata), + "generation": generation, + }, + } + source = sample.source if sample.source is not None else config.source + if source is not None: + row["source"] = source + if sample.group_id is not None: + row["group_id"] = sample.group_id + return row + + +def _build_query_context_answerability_generated_row( + sample: QueryContextAnswerabilityGenerationSample, + config: QueryContextAnswerabilityGenerationConfig, +) -> Mapping[str, Any]: + raw_output = _call_provider( + config.provider, + system=QUERY_CONTEXT_ANSWERABILITY_SYSTEM_PROMPT, + user=build_query_context_answerability_prompt( + sample, + no_answer_value=config.no_answer_value, + ), + on_usage=config.on_usage, + ) + answer = parse_answer_output(raw_output) + label = int( + normalized_exact_match( + answer, + sample.gold_answer, + no_answer_value=config.no_answer_value, + ) + ) + return _query_context_answerability_canonical_row( + sample, + answer=answer, + label=label, + raw_output=raw_output, + config=config, + ) + + +def _query_context_answerability_canonical_row( + sample: QueryContextAnswerabilityGenerationSample, + *, + answer: str, + label: int, + raw_output: str, + config: QueryContextAnswerabilityGenerationConfig, +) -> dict[str, Any]: + generation: dict[str, Any] = { + "generation_task": "query_context_answerability", + "match_policy": "normalized_exact", + "no_answer_value": config.no_answer_value, + } + if config.include_raw_model_output: + generation["raw_model_output"] = raw_output + + row: dict[str, Any] = { + "id": sample.id, + "task_type": "query_context_answerability_confidence", + "query": sample.query, + "context": sample.context, + "answer": answer, + "gold_answer": sample.gold_answer, + "label": label, + "metadata": { + "input_metadata": dict(sample.metadata), + "generation": generation, + }, + } + source = sample.source if sample.source is not None else config.source + if source is not None: + row["source"] = source + if sample.group_id is not None: + row["group_id"] = sample.group_id + return row diff --git a/src/ranksmith/confidence_generation/prompts.py b/src/ranksmith/confidence_generation/prompts.py index ac5f496..80b67e7 100644 --- a/src/ranksmith/confidence_generation/prompts.py +++ b/src/ranksmith/confidence_generation/prompts.py @@ -4,6 +4,8 @@ from ranksmith.confidence_generation.types import ( AnswerGenerationSample, + QueryAnswerabilityGenerationSample, + QueryContextAnswerabilityGenerationSample, RelevanceGenerationSample, ) @@ -17,6 +19,18 @@ 'Return only JSON with a "judgment" value of "relevant" or "not_relevant".' ) +QUERY_ANSWERABILITY_SYSTEM_PROMPT = ( + "You are a strict JSON answer API. " + "Answer questions using your parametric knowledge. " + "Never reason or explain. Return exactly one JSON object and stop." +) + +QUERY_CONTEXT_ANSWERABILITY_SYSTEM_PROMPT = ( + "You are a strict JSON answer API. " + "Answer questions using only the provided context. " + "Never reason or explain. Return exactly one JSON object and stop." +) + def build_answer_prompt( sample: AnswerGenerationSample, @@ -49,3 +63,48 @@ def build_relevance_prompt(sample: RelevanceGenerationSample) -> str: "answering the query.\n" 'Use "not_relevant" otherwise.' ) + + +def build_query_answerability_prompt( + sample: QueryAnswerabilityGenerationSample, + *, + no_answer_value: str, +) -> str: + no_answer_contract = json.dumps( + {"answer": no_answer_value}, + ensure_ascii=False, + separators=(",", ":"), + ) + return ( + f"Question:\n{sample.query}\n\n" + "Return JSON only. Valid examples:\n" + '{"answer":"short answer"}\n' + f"{no_answer_contract}\n\n" + "The answer string must be a short answer only, not an explanation. " + "Do not output any other text. " + "Answer from your parametric knowledge. If you are not immediately " + f"certain, return {no_answer_contract}." + ) + + +def build_query_context_answerability_prompt( + sample: QueryContextAnswerabilityGenerationSample, + *, + no_answer_value: str, +) -> str: + no_answer_contract = json.dumps( + {"answer": no_answer_value}, + ensure_ascii=False, + separators=(",", ":"), + ) + return ( + f"Question:\n{sample.query}\n\n" + f"Context:\n{sample.context}\n\n" + "Return JSON only. Valid examples:\n" + '{"answer":"short answer"}\n' + f"{no_answer_contract}\n\n" + "The answer string must be a short answer only, not an explanation. " + "Do not output any other text. " + "Use only the context. If the context does not directly contain the answer, " + f"return {no_answer_contract}." + ) diff --git a/src/ranksmith/confidence_generation/types.py b/src/ranksmith/confidence_generation/types.py index dcc8e06..7af4f89 100644 --- a/src/ranksmith/confidence_generation/types.py +++ b/src/ranksmith/confidence_generation/types.py @@ -47,6 +47,56 @@ def __post_init__(self) -> None: ) +@dataclass(frozen=True) +class QueryAnswerabilityGenerationConfig: + input_path: str | Path + output_path: str | Path + provider: ModelProvider + overwrite: bool = False + resume: bool = False + max_items: int | None = None + include_raw_model_output: bool = True + on_usage: UsageCallback | None = None + source: str | None = None + no_answer_value: str = "__NO_ANSWER__" + + def __post_init__(self) -> None: + _validate_common_config( + provider=self.provider, + overwrite=self.overwrite, + resume=self.resume, + max_items=self.max_items, + source=self.source, + ) + _validate_no_answer_value(self.no_answer_value) + + +@dataclass(frozen=True) +class QueryContextAnswerabilityGenerationConfig: + input_path: str | Path + output_path: str | Path + provider: ModelProvider + overwrite: bool = False + resume: bool = False + max_items: int | None = None + max_context_chars: int = 4000 + include_raw_model_output: bool = True + on_usage: UsageCallback | None = None + source: str | None = None + no_answer_value: str = "__NO_ANSWER__" + + def __post_init__(self) -> None: + _validate_common_config( + provider=self.provider, + overwrite=self.overwrite, + resume=self.resume, + max_items=self.max_items, + source=self.source, + ) + _validate_positive_int("max_context_chars", self.max_context_chars) + _validate_no_answer_value(self.no_answer_value) + + @dataclass(frozen=True) class RelevanceGenerationConfig: input_path: str | Path @@ -112,6 +162,33 @@ def __post_init__(self) -> None: object.__setattr__(self, "metadata", MappingProxyType(dict(self.metadata))) +@dataclass(frozen=True) +class QueryAnswerabilityGenerationSample: + id: str + query: str + gold_answer: str | list[str] + source: str | None = None + group_id: str | None = None + metadata: Mapping[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + object.__setattr__(self, "metadata", MappingProxyType(dict(self.metadata))) + + +@dataclass(frozen=True) +class QueryContextAnswerabilityGenerationSample: + id: str + query: str + context: str + gold_answer: str | list[str] + source: str | None = None + group_id: str | None = None + metadata: Mapping[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + object.__setattr__(self, "metadata", MappingProxyType(dict(self.metadata))) + + @dataclass(frozen=True) class RelevanceGenerationSample: id: str @@ -149,3 +226,10 @@ def _validate_positive_int(name: str, value: object) -> None: raise ConfidenceGenerationInputError(f"{name} must be an int") if value < 1: raise ConfidenceGenerationInputError(f"{name} must be >= 1") + + +def _validate_no_answer_value(value: object) -> None: + if not isinstance(value, str) or not value.strip(): + raise ConfidenceGenerationInputError( + "no_answer_value must be a non-empty string" + ) diff --git a/src/ranksmith/confidence_training/dataset.py b/src/ranksmith/confidence_training/dataset.py index 1844c80..fcffed4 100644 --- a/src/ranksmith/confidence_training/dataset.py +++ b/src/ranksmith/confidence_training/dataset.py @@ -15,6 +15,8 @@ _ANSWER_REQUIRED = ("id", "context", "answer", "label") _JUDGMENT_REQUIRED = ("id", "query", "document", "judgment", "label") +_QUERY_ANSWERABILITY_REQUIRED = ("id", "query", "answer", "label") +_QUERY_CONTEXT_ANSWERABILITY_REQUIRED = ("id", "query", "context", "answer", "label") _ANSWER_ALLOWED = {*_ANSWER_REQUIRED, "gold_answer", "source", "group_id", "metadata"} _JUDGMENT_ALLOWED = { *_JUDGMENT_REQUIRED, @@ -23,6 +25,21 @@ "group_id", "metadata", } +_ANSWERABILITY_OPTIONAL_ALLOWED = { + "task_type", + "gold_answer", + "source", + "group_id", + "metadata", +} +_QUERY_ANSWERABILITY_ALLOWED = { + *_QUERY_ANSWERABILITY_REQUIRED, + *_ANSWERABILITY_OPTIONAL_ALLOWED, +} +_QUERY_CONTEXT_ANSWERABILITY_ALLOWED = { + *_QUERY_CONTEXT_ANSWERABILITY_REQUIRED, + *_ANSWERABILITY_OPTIONAL_ALLOWED, +} @dataclass(frozen=True) @@ -64,6 +81,37 @@ class _TaskDatasetSchema: metadata=_metadata(row.get("metadata")), ), ), + "query_answerability_confidence": _TaskDatasetSchema( + required=_QUERY_ANSWERABILITY_REQUIRED, + allowed=_QUERY_ANSWERABILITY_ALLOWED, + parser=lambda row: CanonicalConfidenceSample( + id=_required_text(row, "id"), + task_type="query_answerability_confidence", + label=_label(row["label"]), + query=_required_text(row, "query"), + answer=_required_text(row, "answer"), + gold_answer=_optional_gold_answer(row.get("gold_answer")), + source=_optional_text(row.get("source"), "source"), + group_id=_optional_text(row.get("group_id"), "group_id"), + metadata=_metadata(row.get("metadata")), + ), + ), + "query_context_answerability_confidence": _TaskDatasetSchema( + required=_QUERY_CONTEXT_ANSWERABILITY_REQUIRED, + allowed=_QUERY_CONTEXT_ANSWERABILITY_ALLOWED, + parser=lambda row: CanonicalConfidenceSample( + id=_required_text(row, "id"), + task_type="query_context_answerability_confidence", + label=_label(row["label"]), + query=_required_text(row, "query"), + context=_required_text(row, "context"), + answer=_required_text(row, "answer"), + gold_answer=_optional_gold_answer(row.get("gold_answer")), + source=_optional_text(row.get("source"), "source"), + group_id=_optional_text(row.get("group_id"), "group_id"), + metadata=_metadata(row.get("metadata")), + ), + ), } @@ -120,6 +168,7 @@ def _parse_row( raise ConfidenceDatasetError(f"unsupported task_type: {task_type}") try: _validate_keys(row, required=schema.required, allowed=schema.allowed) + _validate_row_task_type(row, task_type=task_type) return schema.parser(row) except (ConfidenceDatasetError, ConfidenceLabelError) as exc: raise type(exc)(f"line {line_number}: {exc}") from exc @@ -144,6 +193,14 @@ def _validate_keys( raise ConfidenceDatasetError(f"unexpected field for task: {unexpected[0]}") +def _validate_row_task_type(row: Mapping[str, Any], *, task_type: TaskType) -> None: + value = row.get("task_type") + if value is None: + return + if value != task_type: + raise ConfidenceDatasetError("task_type field must match requested task_type") + + def _required_text(row: Mapping[str, Any], name: str) -> str: value = row[name] if not isinstance(value, str): diff --git a/src/ranksmith/confidence_training/dataset_report.py b/src/ranksmith/confidence_training/dataset_report.py new file mode 100644 index 0000000..8285f6d --- /dev/null +++ b/src/ranksmith/confidence_training/dataset_report.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from ranksmith.confidence import TaskType +from ranksmith.confidence_training.dataset import load_canonical_dataset +from ranksmith.confidence_training.types import CanonicalConfidenceSample + +MISSING_SOURCE_KEY = "__MISSING__" + + +def build_dataset_report(path: str | Path, task_type: TaskType) -> dict[str, Any]: + samples = load_canonical_dataset(path, task_type=task_type) + positive_count = sum(sample.label for sample in samples) + negative_count = len(samples) - positive_count + present_sources = {sample.source for sample in samples if sample.source is not None} + present_groups = { + sample.group_id for sample in samples if sample.group_id is not None + } + + sample_count = len(samples) + missing_source_count = sum(1 for sample in samples if sample.source is None) + missing_group_id_count = sum(1 for sample in samples if sample.group_id is None) + + return { + "task_type": task_type, + "sample_count": sample_count, + "positive_count": positive_count, + "negative_count": negative_count, + "positive_rate": _rate(positive_count, sample_count), + "source_count": len(present_sources), + "group_count": len(present_groups), + "missing_source_count": missing_source_count, + "missing_source_rate": _rate(missing_source_count, sample_count), + "missing_group_id_count": missing_group_id_count, + "missing_group_id_rate": _rate(missing_group_id_count, sample_count), + "sources": _source_reports(samples), + } + + +def _source_reports( + samples: list[CanonicalConfidenceSample], +) -> dict[str, dict[str, int | float]]: + buckets: dict[str, list[CanonicalConfidenceSample]] = {} + for sample in samples: + source = sample.source if sample.source is not None else MISSING_SOURCE_KEY + buckets.setdefault(source, []).append(sample) + + return { + source: _source_report(source_samples) + for source, source_samples in sorted(buckets.items()) + } + + +def _source_report( + samples: list[CanonicalConfidenceSample], +) -> dict[str, int | float]: + positive_count = sum(sample.label for sample in samples) + sample_count = len(samples) + return { + "sample_count": sample_count, + "positive_count": positive_count, + "negative_count": sample_count - positive_count, + "positive_rate": _rate(positive_count, sample_count), + } + + +def _rate(numerator: int, denominator: int) -> float: + if denominator == 0: + return 0.0 + return numerator / denominator diff --git a/src/ranksmith/confidence_training/features.py b/src/ranksmith/confidence_training/features.py index ebb6b4f..e1de6e0 100644 --- a/src/ranksmith/confidence_training/features.py +++ b/src/ranksmith/confidence_training/features.py @@ -2,7 +2,12 @@ from typing import Protocol -from ranksmith.confidence import AnswerConfidenceInput, JudgmentConfidenceInput +from ranksmith.confidence import ( + AnswerConfidenceInput, + JudgmentConfidenceInput, + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, +) from ranksmith.confidence.errors import ConfidenceError from ranksmith.confidence.features import ( FEATURE_DIM, @@ -82,4 +87,23 @@ def _sample_input(sample: CanonicalConfidenceSample) -> StructuralConfidenceInpu document=sample.document, judgment=sample.judgment, ) + if sample.task_type == "query_answerability_confidence": + if sample.query is None or sample.answer is None: + raise ConfidenceTrainingError( + "query answerability sample is missing text fields" + ) + return QueryAnswerabilityConfidenceInput( + query=sample.query, + answer=sample.answer, + ) + if sample.task_type == "query_context_answerability_confidence": + if sample.query is None or sample.context is None or sample.answer is None: + raise ConfidenceTrainingError( + "query context answerability sample is missing text fields" + ) + return QueryContextAnswerabilityConfidenceInput( + query=sample.query, + context=sample.context, + answer=sample.answer, + ) raise ConfidenceTrainingError(f"unsupported task_type: {sample.task_type}") diff --git a/src/ranksmith/confidence_training/types.py b/src/ranksmith/confidence_training/types.py index 18496fd..e8f7f14 100644 --- a/src/ranksmith/confidence_training/types.py +++ b/src/ranksmith/confidence_training/types.py @@ -34,7 +34,12 @@ class ConfidenceTrainingConfig: calibration_method: CalibrationMethod = "sigmoid" def __post_init__(self) -> None: - if self.task_type not in {"answer_confidence", "judgment_confidence"}: + if self.task_type not in { + "answer_confidence", + "judgment_confidence", + "query_answerability_confidence", + "query_context_answerability_confidence", + }: raise ConfidenceTrainingConfigError("unsupported task_type") if self.calibration_method != "sigmoid": raise ConfidenceTrainingConfigError("unsupported calibration_method") diff --git a/src/ranksmith/integrations/__init__.py b/src/ranksmith/integrations/__init__.py new file mode 100644 index 0000000..ec3e2bb --- /dev/null +++ b/src/ranksmith/integrations/__init__.py @@ -0,0 +1,9 @@ +from ranksmith.integrations.answer_generator import ProviderAnswerGenerator +from ranksmith.integrations.azure_answer_generator import AzureAnswerGenerator +from ranksmith.integrations.lmstudio_provider import LMStudioModelProvider + +__all__ = [ + "AzureAnswerGenerator", + "LMStudioModelProvider", + "ProviderAnswerGenerator", +] diff --git a/src/ranksmith/integrations/answer_generator.py b/src/ranksmith/integrations/answer_generator.py new file mode 100644 index 0000000..7252394 --- /dev/null +++ b/src/ranksmith/integrations/answer_generator.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass + +from ranksmith.errors import RerankParseError, RerankProviderError +from ranksmith.integrations.validation import validate_no_answer_value +from ranksmith.model import ModelMessage, ModelProvider, ModelRequest + + +@dataclass(frozen=True) +class ProviderAnswerGenerator: + provider: ModelProvider + no_answer_value: str = "__NO_ANSWER__" + + def __post_init__(self) -> None: + validate_no_answer_value(self.no_answer_value) + + def answer_query(self, query: str) -> str: + return self._complete( + system=( + "You are a strict JSON answer API for confidence estimation. " + "Never reason or explain. Return exactly one JSON object and stop." + ), + user=( + f"Question:\n{query}\n\n" + "Return JSON only. Valid examples:\n" + '{"answer":"short answer"}\n' + f"{_answer_contract(self.no_answer_value)}\n\n" + "The answer string must be a short answer only, not an " + "explanation. Do not output any other text. " + "Answer from your parametric knowledge. If you are not " + "immediately certain, return " + f"{_answer_contract(self.no_answer_value)}." + ), + ) + + def answer_with_context(self, query: str, context: str) -> str: + return self._complete( + system=( + "You are a strict JSON answer API for confidence estimation. " + "Use only the provided context. Never reason or explain. " + "Return exactly one JSON object and stop." + ), + user=( + f"Question:\n{query}\n\n" + f"Context:\n{context}\n\n" + "Return JSON only. Valid examples:\n" + '{"answer":"short answer"}\n' + f"{_answer_contract(self.no_answer_value)}\n\n" + "The answer string must be a short answer only, not an " + "explanation. Do not output any other text. " + "Use only the context. If the context does not directly contain " + "the answer, " + f"return {_answer_contract(self.no_answer_value)}." + ), + ) + + def _complete(self, *, system: str, user: str) -> str: + try: + response = self.provider.complete( + ModelRequest( + messages=[ + ModelMessage(role="system", content=system), + ModelMessage(role="user", content=user), + ], + response_format="json_object", + temperature=0, + ) + ) + except RerankProviderError: + raise + except Exception as exc: + message = str(exc) + if message: + raise RerankProviderError( + f"Answer generation provider failed: {message}" + ) from exc + raise RerankProviderError("Answer generation provider failed.") from exc + return _parse_answer(response.content) + + +def _parse_answer(content: str) -> str: + try: + parsed = json.loads(content) + except json.JSONDecodeError as exc: + raise RerankParseError("answer response must be valid JSON") from exc + if not isinstance(parsed, dict): + raise RerankParseError("answer response must be a JSON object") + answer = parsed.get("answer") + if not isinstance(answer, str) or answer.strip() == "": + raise RerankParseError('answer response must contain a non-empty "answer"') + return answer + + +def _answer_contract(no_answer_value: str) -> str: + return json.dumps( + {"answer": no_answer_value}, + ensure_ascii=False, + separators=(",", ":"), + ) diff --git a/src/ranksmith/integrations/azure_answer_generator.py b/src/ranksmith/integrations/azure_answer_generator.py new file mode 100644 index 0000000..54aef56 --- /dev/null +++ b/src/ranksmith/integrations/azure_answer_generator.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +import os +from dataclasses import dataclass, field + +from ranksmith.errors import RerankInputError +from ranksmith.integrations.answer_generator import ProviderAnswerGenerator +from ranksmith.integrations.validation import validate_no_answer_value +from ranksmith.model import ModelProvider +from ranksmith.providers import AzureAOAIProvider + + +@dataclass(frozen=True) +class AzureAnswerGenerator: + provider: ModelProvider = field(init=False) + no_answer_value: str = field(init=False, default="__NO_ANSWER__") + _generator: ProviderAnswerGenerator = field( + init=False, + repr=False, + compare=False, + ) + + def __init__( + self, + *, + api_key: str | None = None, + azure_endpoint: str | None = None, + azure_deployment: str | None = None, + api_version: str = "2024-08-01-preview", + timeout: float | None = None, + provider: ModelProvider | None = None, + no_answer_value: str = "__NO_ANSWER__", + ) -> None: + validate_no_answer_value(no_answer_value) + if provider is not None: + resolved_provider = provider + else: + if api_key is None: + raise RerankInputError("AZURE_OPENAI_API_KEY is required") + if azure_endpoint is None: + raise RerankInputError("AZURE_OPENAI_ENDPOINT is required") + if azure_deployment is None: + raise RerankInputError( + "AZURE_OPENAI_LLM_DEPLOYMENT or AZURE_OPENAI_DEPLOYMENT is required" + ) + resolved_provider = AzureAOAIProvider( + api_key=api_key, + azure_endpoint=azure_endpoint, + azure_deployment=azure_deployment, + api_version=api_version, + timeout=timeout, + ) + resolved_generator = ProviderAnswerGenerator( + provider=resolved_provider, + no_answer_value=no_answer_value, + ) + object.__setattr__(self, "provider", resolved_provider) + object.__setattr__(self, "no_answer_value", no_answer_value) + object.__setattr__(self, "_generator", resolved_generator) + + @classmethod + def from_env( + cls, + *, + timeout: float | None = None, + no_answer_value: str = "__NO_ANSWER__", + ) -> AzureAnswerGenerator: + return cls( + api_key=_required_env("AZURE_OPENAI_API_KEY"), + azure_endpoint=_required_env("AZURE_OPENAI_ENDPOINT"), + azure_deployment=_required_env( + "AZURE_OPENAI_LLM_DEPLOYMENT", + fallback="AZURE_OPENAI_DEPLOYMENT", + ), + api_version=_env_value( + "AZURE_OPENAI_LLM_API_VERSION", + fallback="AZURE_OPENAI_API_VERSION", + default="2024-08-01-preview", + ) + or "2024-08-01-preview", + timeout=timeout, + no_answer_value=no_answer_value, + ) + + def answer_query(self, query: str) -> str: + return self._generator.answer_query(query) + + def answer_with_context(self, query: str, context: str) -> str: + return self._generator.answer_with_context(query, context) + + +def _required_env(name: str, *, fallback: str | None = None) -> str: + value = _env_value(name, fallback=fallback) + if value is None or value == "": + names = name if fallback is None else f"{name} or {fallback}" + raise RerankInputError(f"{names} is required") + return value + + +def _env_value( + name: str, + *, + fallback: str | None = None, + default: str | None = None, +) -> str | None: + value = os.environ.get(name) + if value is not None and value != "": + return value + if fallback is not None: + fallback_value = os.environ.get(fallback) + if fallback_value is not None and fallback_value != "": + return fallback_value + return default diff --git a/src/ranksmith/integrations/lmstudio_provider.py b/src/ranksmith/integrations/lmstudio_provider.py new file mode 100644 index 0000000..84b82a2 --- /dev/null +++ b/src/ranksmith/integrations/lmstudio_provider.py @@ -0,0 +1,183 @@ +from __future__ import annotations + +import os +from typing import Any, Protocol, cast + +from openai import OpenAI + +from ranksmith.errors import RerankInputError, RerankProviderError +from ranksmith.model import ModelRequest, ModelResponse +from ranksmith.types import RerankUsage + + +class _CompletionsClient(Protocol): + def create( + self, + *, + model: str, + messages: list[dict[str, str]], + response_format: dict[str, object], + temperature: float, + max_tokens: int, + reasoning_effort: str, + ) -> object: ... + + +class _ChatClient(Protocol): + completions: _CompletionsClient + + +class _OpenAICompatibleClient(Protocol): + chat: _ChatClient + + +class LMStudioModelProvider: + def __init__( + self, + *, + model: str | None = None, + base_url: str | None = None, + api_key: str | None = None, + timeout: float | None = None, + max_tokens: int = 128, + client: _OpenAICompatibleClient | None = None, + ) -> None: + resolved_model = ( + model if model is not None else os.environ.get("LMSTUDIO_MODEL") + ) + if resolved_model is None or resolved_model.strip() == "": + raise RerankInputError("LMSTUDIO_MODEL is required") + if max_tokens <= 0: + raise RerankInputError("max_tokens must be greater than 0") + + resolved_base_url = _resolve_optional_setting( + explicit=base_url, + env_name="LMSTUDIO_BASE_URL", + default="http://localhost:1234/v1", + setting_name="base_url", + ) + resolved_api_key = _resolve_optional_setting( + explicit=api_key, + env_name="LMSTUDIO_API_KEY", + default="lm-studio", + setting_name="api_key", + ) + + self._model = resolved_model + self._base_url = resolved_base_url + self._api_key_configured = resolved_api_key != "" + self._max_tokens = max_tokens + self._client: _OpenAICompatibleClient = ( + client + if client is not None + else cast( + _OpenAICompatibleClient, + OpenAI( + api_key=resolved_api_key, + base_url=resolved_base_url, + timeout=timeout, + ), + ) + ) + + @property + def model(self) -> str: + return self._model + + @property + def base_url(self) -> str: + return self._base_url + + @property + def api_key_configured(self) -> bool: + return self._api_key_configured + + @property + def max_tokens(self) -> int: + return self._max_tokens + + def complete(self, request: ModelRequest) -> ModelResponse: + response_format = _to_lmstudio_response_format(request) + try: + response = self._client.chat.completions.create( + model=self._model, + messages=_to_openai_messages(request), + response_format=response_format, + temperature=request.temperature, + max_tokens=self._max_tokens, + reasoning_effort="none", + ) + except Exception as exc: + message = str(exc) + if message: + raise RerankProviderError( + f"LM Studio request failed: {message}" + ) from exc + raise RerankProviderError("LM Studio request failed.") from exc + + content = _extract_content(response) + if content is None or content == "": + raise RerankProviderError("LM Studio returned an empty response.") + return ModelResponse(content=content, usage=_extract_usage(response)) + + +def _to_lmstudio_response_format(request: ModelRequest) -> dict[str, Any]: + if request.response_format != "json_object": + raise RerankProviderError("LM Studio received unsupported response_format.") + return { + "type": "json_schema", + "json_schema": { + "name": "ranksmith_json_response", + "schema": {"type": "object"}, + }, + } + + +def _resolve_optional_setting( + *, + explicit: str | None, + env_name: str, + default: str, + setting_name: str, +) -> str: + if explicit is not None: + if explicit.strip() == "": + raise RerankInputError(f"{setting_name} must not be blank") + return explicit + value = os.environ.get(env_name) + if value is not None: + if value.strip() == "": + raise RerankInputError(f"{env_name} must not be blank") + return value + return default + + +def _extract_usage(response: object) -> RerankUsage | None: + usage = getattr(response, "usage", None) + if usage is None: + return None + return RerankUsage( + prompt_tokens=int(getattr(usage, "prompt_tokens", 0) or 0), + completion_tokens=int(getattr(usage, "completion_tokens", 0) or 0), + total_tokens=int(getattr(usage, "total_tokens", 0) or 0), + ) + + +def _extract_content(response: object) -> str | None: + choices = getattr(response, "choices", None) + if not choices: + raise RerankProviderError("LM Studio returned an invalid response.") + try: + content = choices[0].message.content + except AttributeError as exc: + raise RerankProviderError("LM Studio returned an invalid response.") from exc + if content is not None and not isinstance(content, str): + raise RerankProviderError("LM Studio returned an invalid response.") + return content + + +def _to_openai_messages(request: ModelRequest) -> list[dict[str, str]]: + return [ + {"role": message.role, "content": message.content} + for message in request.messages + ] diff --git a/src/ranksmith/integrations/validation.py b/src/ranksmith/integrations/validation.py new file mode 100644 index 0000000..25dfd85 --- /dev/null +++ b/src/ranksmith/integrations/validation.py @@ -0,0 +1,6 @@ +from __future__ import annotations + + +def validate_no_answer_value(value: object) -> None: + if not isinstance(value, str) or value.strip() == "": + raise ValueError("no_answer_value must be a non-empty string") diff --git a/src/ranksmith/model.py b/src/ranksmith/model.py index 969b9c6..c06f98f 100644 --- a/src/ranksmith/model.py +++ b/src/ranksmith/model.py @@ -32,6 +32,8 @@ class ModelMessage: @dataclass(frozen=True) class ModelRequest: messages: Sequence[ModelMessage] + response_format: Literal["json_object"] = "json_object" + temperature: float = 0 @dataclass(frozen=True) diff --git a/src/ranksmith/strategies/__init__.py b/src/ranksmith/strategies/__init__.py index 222d0d4..a708325 100644 --- a/src/ranksmith/strategies/__init__.py +++ b/src/ranksmith/strategies/__init__.py @@ -1,4 +1,11 @@ from ranksmith.strategies.acurank import AcuRankStrategy, AsyncAcuRankStrategy +from ranksmith.strategies.cbdr import CBDRStrategy +from ranksmith.strategies.confidence_gain import ( + AnswerGenerator, + ConfidenceEstimator, + ConfidenceGainResult, + ConfidenceGainStrategy, +) from ranksmith.strategies.listwise import AsyncListwiseStrategy, ListwiseStrategy from ranksmith.strategies.pairwise import AsyncPairwiseStrategy, PairwiseStrategy from ranksmith.strategies.setwise import AsyncSetwiseStrategy, SetwiseStrategy @@ -10,11 +17,16 @@ __all__ = [ "AcuRankStrategy", + "AnswerGenerator", "AsyncAcuRankStrategy", "AsyncListwiseStrategy", "AsyncPairwiseStrategy", "AsyncSetwiseStrategy", "AsyncTourRankStrategy", + "CBDRStrategy", + "ConfidenceEstimator", + "ConfidenceGainResult", + "ConfidenceGainStrategy", "ListwiseStrategy", "PairwiseStrategy", "SetwiseStrategy", diff --git a/src/ranksmith/strategies/cbdr.py b/src/ranksmith/strategies/cbdr.py new file mode 100644 index 0000000..b06cd93 --- /dev/null +++ b/src/ranksmith/strategies/cbdr.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import math +from collections.abc import Sequence +from dataclasses import dataclass +from numbers import Real +from pathlib import Path +from typing import Literal + +from ranksmith.confidence import StructuralConfidenceEstimator +from ranksmith.errors import RerankInputError +from ranksmith.types import Document, RerankResult + +from .common import validate_documents_max_chars, validate_top_k +from .confidence_gain import ( + AnswerGenerator, + ConfidenceEstimator, + _call_answer_query, + _call_answer_with_context, + _confidence_gain, + _score_base_answerability, + _score_context_answerability, + _validate_confidence_score, + _validate_estimator_tasks, +) + +CBDRAlgorithm = Literal["cbdr"] + + +@dataclass(frozen=True) +class CBDRStrategy: + base_estimator: ConfidenceEstimator + context_estimator: ConfidenceEstimator + answer_generator: AnswerGenerator + skip_threshold: float = 0.8 + max_document_chars: int = 4000 + algorithm: CBDRAlgorithm = "cbdr" + + @classmethod + def from_artifacts( + cls, + *, + base_artifact_path: str | Path, + context_artifact_path: str | Path, + base_metadata_path: str | Path | None = None, + context_metadata_path: str | Path | None = None, + answer_generator: AnswerGenerator, + skip_threshold: float = 0.8, + max_document_chars: int = 4000, + hf_token: str | None = None, + cache_dir: str | None = None, + device: str = "cpu", + local_files_only: bool = False, + max_length: int | None = None, + allow_truncation: bool = False, + ) -> CBDRStrategy: + return cls( + base_estimator=StructuralConfidenceEstimator.from_artifact( + base_artifact_path, + metadata_path=base_metadata_path, + task_type="query_answerability_confidence", + hf_token=hf_token, + cache_dir=cache_dir, + device=device, + local_files_only=local_files_only, + max_length=max_length, + allow_truncation=allow_truncation, + ), + context_estimator=StructuralConfidenceEstimator.from_artifact( + context_artifact_path, + metadata_path=context_metadata_path, + task_type="query_context_answerability_confidence", + hf_token=hf_token, + cache_dir=cache_dir, + device=device, + local_files_only=local_files_only, + max_length=max_length, + allow_truncation=allow_truncation, + ), + answer_generator=answer_generator, + skip_threshold=skip_threshold, + max_document_chars=max_document_chars, + ) + + def __post_init__(self) -> None: + if self.algorithm != "cbdr": + raise ValueError('algorithm must be "cbdr"') + if self.max_document_chars < 1: + raise ValueError("max_document_chars must be greater than 0") + _validate_probability_config(self.skip_threshold, "skip_threshold") + _validate_estimator_tasks( + base_estimator=self.base_estimator, + context_estimator=self.context_estimator, + ) + + def rerank( + self, + *, + query: str, + documents: Sequence[Document], + model_client: object | None = None, + top_k: int | None = None, + ) -> list[RerankResult]: + del model_client + validate_top_k(top_k) + if query.strip() == "": + raise RerankInputError("query must not be empty") + if not documents or top_k == 0: + return [] + + base_answer = _call_answer_query(self.answer_generator, query) + base_result = _score_base_answerability( + estimator=self.base_estimator, + query=query, + answer=base_answer, + ) + base_score = _validate_confidence_score(base_result.score, "base") + + if base_score >= self.skip_threshold: + return _original_order_results( + documents=documents, + top_k=top_k, + algorithm=self.algorithm, + base_score=base_score, + skip_threshold=self.skip_threshold, + ) + + validate_documents_max_chars( + documents, + max_document_chars=self.max_document_chars, + ) + scored = [] + for original_index, document in enumerate(documents): + context_answer = _call_answer_with_context( + self.answer_generator, + query, + document.text, + ) + context_result = _score_context_answerability( + estimator=self.context_estimator, + query=query, + context=document.text, + answer=context_answer, + ) + context_score = _validate_confidence_score( + context_result.score, + "context", + ) + scored.append( + ( + original_index, + context_score, + _confidence_gain( + base_score=base_score, + context_score=context_score, + ), + ) + ) + + scored.sort(key=lambda item: (-item[2], item[0])) + if top_k is not None: + scored = scored[:top_k] + + return [ + RerankResult( + document=documents[original_index], + rank=rank, + original_index=original_index, + metadata={ + "strategy": "cbdr", + "algorithm": self.algorithm, + "cbdr_skipped": False, + "base_confidence": base_score, + "skip_threshold": self.skip_threshold, + "context_confidence": context_score, + "confidence_gain": gain, + }, + ) + for rank, (original_index, context_score, gain) in enumerate( + scored, + start=1, + ) + ] + + +def _validate_probability_config(value: object, name: str) -> float: + if isinstance(value, bool) or not isinstance(value, Real): + raise ValueError(f"{name} must be a finite probability in [0, 1]") + probability = float(value) + if not math.isfinite(probability) or probability < 0.0 or probability > 1.0: + raise ValueError(f"{name} must be a finite probability in [0, 1]") + return probability + + +def _original_order_results( + *, + documents: Sequence[Document], + top_k: int | None, + algorithm: CBDRAlgorithm, + base_score: float, + skip_threshold: float, +) -> list[RerankResult]: + indexed = list(enumerate(documents)) + if top_k is not None: + indexed = indexed[:top_k] + return [ + RerankResult( + document=document, + rank=rank, + original_index=original_index, + metadata={ + "strategy": "cbdr", + "algorithm": algorithm, + "cbdr_skipped": True, + "base_confidence": base_score, + "skip_threshold": skip_threshold, + "context_confidence": None, + "confidence_gain": None, + }, + ) + for rank, (original_index, document) in enumerate(indexed, start=1) + ] diff --git a/src/ranksmith/strategies/confidence_gain.py b/src/ranksmith/strategies/confidence_gain.py new file mode 100644 index 0000000..f989bd1 --- /dev/null +++ b/src/ranksmith/strategies/confidence_gain.py @@ -0,0 +1,245 @@ +from __future__ import annotations + +import math +from collections.abc import Sequence +from dataclasses import dataclass +from numbers import Real +from typing import Literal, Protocol + +from ranksmith.confidence import ( + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + StructuralConfidenceResult, +) +from ranksmith.errors import ( + RerankInputError, + RerankProviderError, + RerankStrategyError, +) +from ranksmith.types import Document, RerankResult + +from .common import validate_documents_max_chars, validate_top_k + +ConfidenceGainAlgorithm = Literal["confidence_gain"] +QUERY_ANSWERABILITY_TASK = "query_answerability_confidence" +QUERY_CONTEXT_ANSWERABILITY_TASK = "query_context_answerability_confidence" +AnswerabilityConfidenceInput = ( + QueryAnswerabilityConfidenceInput | QueryContextAnswerabilityConfidenceInput +) + + +class AnswerGenerator(Protocol): + def answer_query(self, query: str) -> str: ... + + def answer_with_context(self, query: str, context: str) -> str: ... + + +class ConfidenceEstimator(Protocol): + @property + def task_type(self) -> str: ... + + def score( + self, + item: AnswerabilityConfidenceInput, + ) -> StructuralConfidenceResult: ... + + +@dataclass(frozen=True) +class ConfidenceGainResult: + base_score: float + context_score: float + gain: float + base_result: StructuralConfidenceResult + context_result: StructuralConfidenceResult + + +@dataclass(frozen=True) +class ConfidenceGainStrategy: + base_estimator: ConfidenceEstimator + context_estimator: ConfidenceEstimator + answer_generator: AnswerGenerator + max_document_chars: int = 4000 + algorithm: ConfidenceGainAlgorithm = "confidence_gain" + + def __post_init__(self) -> None: + if self.algorithm != "confidence_gain": + raise ValueError('algorithm must be "confidence_gain"') + if self.max_document_chars < 1: + raise ValueError("max_document_chars must be greater than 0") + _validate_estimator_tasks( + base_estimator=self.base_estimator, + context_estimator=self.context_estimator, + ) + + def rerank( + self, + *, + query: str, + documents: Sequence[Document], + model_client: object, + top_k: int | None = None, + ) -> list[RerankResult]: + del model_client + validate_top_k(top_k) + if query.strip() == "": + raise RerankInputError("query must not be empty") + validate_documents_max_chars( + documents, + max_document_chars=self.max_document_chars, + ) + if not documents: + return [] + + base_answer = _call_answer_query(self.answer_generator, query) + base_result = _score_base_answerability( + estimator=self.base_estimator, + query=query, + answer=base_answer, + ) + base_score = _validate_confidence_score(base_result.score, "base") + + scored: list[tuple[int, ConfidenceGainResult]] = [] + for original_index, document in enumerate(documents): + context_answer = _call_answer_with_context( + self.answer_generator, + query, + document.text, + ) + context_result = _score_context_answerability( + estimator=self.context_estimator, + query=query, + context=document.text, + answer=context_answer, + ) + context_score = _validate_confidence_score( + context_result.score, + "context", + ) + gain = _confidence_gain( + base_score=base_score, + context_score=context_score, + ) + scored.append( + ( + original_index, + ConfidenceGainResult( + base_score=base_score, + context_score=context_score, + gain=gain, + base_result=base_result, + context_result=context_result, + ), + ) + ) + + scored.sort(key=lambda item: (-item[1].gain, item[0])) + if top_k is not None: + scored = scored[:top_k] + + return [ + RerankResult( + document=documents[original_index], + rank=rank, + original_index=original_index, + metadata={ + "strategy": "confidence_gain", + "algorithm": self.algorithm, + "base_confidence": result.base_score, + "context_confidence": result.context_score, + "confidence_gain": result.gain, + }, + ) + for rank, (original_index, result) in enumerate(scored, start=1) + ] + + +def _validate_estimator_tasks( + *, + base_estimator: ConfidenceEstimator, + context_estimator: ConfidenceEstimator, +) -> None: + if base_estimator.task_type != QUERY_ANSWERABILITY_TASK: + raise RerankInputError( + f"base_estimator task_type must be {QUERY_ANSWERABILITY_TASK!r}" + ) + if context_estimator.task_type != QUERY_CONTEXT_ANSWERABILITY_TASK: + raise RerankInputError( + f"context_estimator task_type must be {QUERY_CONTEXT_ANSWERABILITY_TASK!r}" + ) + + +def _call_answer_query(generator: AnswerGenerator, query: str) -> str: + try: + answer = generator.answer_query(query) + except RerankProviderError: + raise + except Exception as exc: + raise RerankProviderError(str(exc)) from exc + return _validate_answer(answer, "answer_query") + + +def _call_answer_with_context( + generator: AnswerGenerator, + query: str, + context: str, +) -> str: + try: + answer = generator.answer_with_context(query, context) + except RerankProviderError: + raise + except Exception as exc: + raise RerankProviderError(str(exc)) from exc + return _validate_answer(answer, "answer_with_context") + + +def _validate_answer(answer: object, method_name: str) -> str: + if not isinstance(answer, str) or answer.strip() == "": + raise RerankProviderError(f"{method_name} must return a non-empty string") + return answer + + +def _score_base_answerability( + *, + estimator: ConfidenceEstimator, + query: str, + answer: str, +) -> StructuralConfidenceResult: + return estimator.score( + QueryAnswerabilityConfidenceInput(query=query, answer=answer) + ) + + +def _score_context_answerability( + *, + estimator: ConfidenceEstimator, + query: str, + context: str, + answer: str, +) -> StructuralConfidenceResult: + return estimator.score( + QueryContextAnswerabilityConfidenceInput( + query=query, + context=context, + answer=answer, + ) + ) + + +def _validate_confidence_score(score: object, label: str) -> float: + if isinstance(score, bool) or not isinstance(score, Real): + raise RerankStrategyError(f"{label} confidence score must be numeric") + value = float(score) + if not math.isfinite(value) or value < 0.0 or value > 1.0: + raise RerankStrategyError(f"{label} confidence score must be finite in [0, 1]") + return value + + +def _confidence_gain( + *, + base_score: float, + context_score: float, +) -> float: + gain = context_score - base_score + if not math.isfinite(gain) or gain < -1.0 or gain > 1.0: + raise RerankStrategyError("confidence gain must be finite in [-1, 1]") + return gain diff --git a/tests/fixtures/confidence_query_answerability_raw.jsonl b/tests/fixtures/confidence_query_answerability_raw.jsonl new file mode 100644 index 0000000..bce08d1 --- /dev/null +++ b/tests/fixtures/confidence_query_answerability_raw.jsonl @@ -0,0 +1,2 @@ +{"id":"q1","query":"What is the capital of France?","gold_answer":"Paris","source":"fixture","group_id":"geo"} +{"id":"q2","query":"Who wrote an unknown private diary?","gold_answer":"__NO_ANSWER__","source":"fixture","group_id":"unknown"} diff --git a/tests/fixtures/confidence_query_context_answerability_raw.jsonl b/tests/fixtures/confidence_query_context_answerability_raw.jsonl new file mode 100644 index 0000000..061497d --- /dev/null +++ b/tests/fixtures/confidence_query_context_answerability_raw.jsonl @@ -0,0 +1,2 @@ +{"id":"qc1","query":"Who played Karen?","context":"Nancy Travis played Karen.","gold_answer":"Nancy Travis","source":"fixture","group_id":"tv"} +{"id":"qc2","query":"What color is the hidden key?","context":"The passage does not mention a key.","gold_answer":"__NO_ANSWER__","source":"fixture","group_id":"missing"} diff --git a/tests/test_azure_answer_generator.py b/tests/test_azure_answer_generator.py new file mode 100644 index 0000000..af94d7a --- /dev/null +++ b/tests/test_azure_answer_generator.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import importlib +from dataclasses import dataclass + +import pytest + +from ranksmith.errors import RerankInputError, RerankParseError, RerankProviderError +from ranksmith.model import ModelRequest, ModelResponse + + +@dataclass +class FakeProvider: + responses: list[str] + requests: list[ModelRequest] + + def complete(self, request: ModelRequest) -> ModelResponse: + self.requests.append(request) + return ModelResponse(content=self.responses.pop(0)) + + +def test_integrations_exports_are_submodule_only() -> None: + integrations = importlib.import_module("ranksmith.integrations") + root = importlib.import_module("ranksmith") + + assert integrations.AzureAnswerGenerator is not None + assert not hasattr(root, "AzureAnswerGenerator") + + +def test_azure_answer_generator_parses_query_answer_json() -> None: + from ranksmith.integrations import AzureAnswerGenerator + + provider = FakeProvider(responses=['{"answer": "Nancy Travis"}'], requests=[]) + generator = AzureAnswerGenerator(provider=provider) + + assert generator.answer_query("who played karen?") == "Nancy Travis" + assert provider.requests[0].response_format == "json_object" + assert provider.requests[0].temperature == 0 + assert provider.requests[0].messages[0].role == "system" + assert provider.requests[0].messages[1].role == "user" + assert "who played karen?" in provider.requests[0].messages[1].content + assert "__NO_ANSWER__" in provider.requests[0].messages[1].content + assert "best concise answer" not in provider.requests[0].messages[0].content + + +def test_azure_answer_generator_parses_context_answer_json() -> None: + from ranksmith.integrations import AzureAnswerGenerator + + provider = FakeProvider(responses=['{"answer": "Nancy Travis"}'], requests=[]) + generator = AzureAnswerGenerator(provider=provider) + + assert ( + generator.answer_with_context( + "who played karen?", + "Nancy Travis played Karen.", + ) + == "Nancy Travis" + ) + assert "Nancy Travis played Karen." in provider.requests[0].messages[1].content + assert "__NO_ANSWER__" in provider.requests[0].messages[1].content + + +def test_azure_answer_generator_uses_configured_no_answer_value() -> None: + from ranksmith.integrations import AzureAnswerGenerator + + provider = FakeProvider(responses=['{"answer": "UNKNOWN"}'], requests=[]) + generator = AzureAnswerGenerator(provider=provider, no_answer_value="UNKNOWN") + + assert generator.answer_query("query") == "UNKNOWN" + assert '{"answer":"UNKNOWN"}' in provider.requests[0].messages[1].content + + +def test_azure_answer_generator_rejects_empty_no_answer_value() -> None: + from ranksmith.integrations import AzureAnswerGenerator + + with pytest.raises(ValueError, match="no_answer_value"): + AzureAnswerGenerator( + provider=FakeProvider(responses=[], requests=[]), + no_answer_value=" ", + ) + + +@pytest.mark.parametrize( + "content", + [ + "not json", + "{}", + '{"answer": ""}', + '{"answer": " "}', + '{"answer": 123}', + ], +) +def test_azure_answer_generator_rejects_invalid_answer_json(content: str) -> None: + from ranksmith.integrations import AzureAnswerGenerator + + generator = AzureAnswerGenerator( + provider=FakeProvider(responses=[content], requests=[]), + ) + + with pytest.raises(RerankParseError): + generator.answer_query("query") + + +def test_azure_answer_generator_preserves_provider_error() -> None: + from ranksmith.integrations import AzureAnswerGenerator + + class FailingProvider: + def complete(self, request: ModelRequest) -> ModelResponse: + del request + raise RerankProviderError("provider failed") + + generator = AzureAnswerGenerator(provider=FailingProvider()) + + with pytest.raises(RerankProviderError, match="provider failed"): + generator.answer_query("query") + + +def test_azure_answer_generator_requires_azure_config_without_provider( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from ranksmith.integrations import AzureAnswerGenerator + + monkeypatch.delenv("AZURE_OPENAI_API_KEY", raising=False) + monkeypatch.delenv("AZURE_OPENAI_ENDPOINT", raising=False) + monkeypatch.delenv("AZURE_OPENAI_LLM_DEPLOYMENT", raising=False) + monkeypatch.delenv("AZURE_OPENAI_DEPLOYMENT", raising=False) + + with pytest.raises(RerankInputError, match="AZURE_OPENAI_API_KEY"): + AzureAnswerGenerator.from_env() diff --git a/tests/test_cbdr_strategy.py b/tests/test_cbdr_strategy.py new file mode 100644 index 0000000..914a714 --- /dev/null +++ b/tests/test_cbdr_strategy.py @@ -0,0 +1,684 @@ +from __future__ import annotations + +import importlib +import math +import sys +from dataclasses import dataclass +from pathlib import Path +from types import ModuleType +from typing import Any, cast + +import pytest + +from ranksmith import AzureOpenAIReranker +from ranksmith.confidence import ( + StructuralConfidenceEstimator, + StructuralConfidenceResult, + TaskType, +) +from ranksmith.confidence.scorer import ARTIFACT_SCHEMA_VERSION +from ranksmith.errors import ( + DocumentTooLongError, + RerankInputError, + RerankProviderError, + RerankStrategyError, +) +from ranksmith.strategies import CBDRStrategy +from ranksmith.types import Document + + +@dataclass +class FakeEstimator: + task_type: TaskType + scores: list[Any] + calls: list[object] | None = None + + def score(self, item: object) -> StructuralConfidenceResult: + if self.calls is not None: + self.calls.append(item) + value = self.scores.pop(0) + if isinstance(value, BaseException): + raise value + return StructuralConfidenceResult( + score=value, + task_type=self.task_type, + feature_schema_version="structural-v1", + ) + + +class FakeGenerator: + def __init__( + self, + *, + base_answer: object = "base answer", + context_answers: list[object] | None = None, + ) -> None: + self.base_answer = base_answer + self.context_answers = context_answers or [] + self.query_calls: list[str] = [] + self.context_calls: list[tuple[str, str]] = [] + + def answer_query(self, query: str) -> Any: + self.query_calls.append(query) + if isinstance(self.base_answer, BaseException): + raise self.base_answer + return self.base_answer + + def answer_with_context(self, query: str, context: str) -> Any: + self.context_calls.append((query, context)) + answer = self.context_answers.pop(0) + if isinstance(answer, BaseException): + raise answer + return answer + + +class ArtifactScorer: + def __init__(self, scores: list[float]) -> None: + self.scores = scores + + def predict_confidence(self, features: object) -> float: + del features + return self.scores.pop(0) + + +class ArtifactEncoder: + encoder_name = "bert-base-uncased" + encoder_revision = None + tokenizer_name = "bert-base-uncased" + tokenizer_revision = None + + def __init__(self, *, max_length: int) -> None: + self.max_length = max_length + + def encode(self, text: str) -> tuple[list[list[float]], list[int]]: + seed = float(len(text) % 7 + 1) + hidden = [[seed + row * 0.01, row * 0.02, seed * 0.03] for row in range(40)] + return hidden, [1] * len(hidden) + + +def _artifact_metadata(task_type: TaskType) -> dict[str, object]: + return { + "artifact_schema_version": ARTIFACT_SCHEMA_VERSION, + "scorer_type": "joblib-wrapper", + "task_type": task_type, + "encoder_name": "bert-base-uncased", + "encoder_revision": None, + "tokenizer_name": "bert-base-uncased", + "tokenizer_revision": None, + "input_template_version": "structural-template-v1", + "feature_schema_version": "structural-v1", + "feature_dim": 70, + "feature_dtype": "float64", + "max_length": 64, + "granularity": "two_scale", + "local_window_size": 5, + "local_stride": 2, + "score_output": "probability", + "positive_class_index": 1, + } + + +def _install_artifact_joblib( + monkeypatch: pytest.MonkeyPatch, + artifacts: dict[Path, object], +) -> None: + module = ModuleType("joblib") + + def load(path: str | Path) -> object: + return artifacts[Path(path)] + + module.load = load # type: ignore[attr-defined] + monkeypatch.setitem(sys.modules, "joblib", module) + + +def _artifact_strategy( + *, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + base_scores: list[float], + context_scores: list[float], + generator: FakeGenerator, + skip_threshold: float, +) -> CBDRStrategy: + base_artifact_path = tmp_path / "query_answerability.joblib" + context_artifact_path = tmp_path / "query_context_answerability.joblib" + _install_artifact_joblib( + monkeypatch, + { + base_artifact_path: { + "metadata": _artifact_metadata("query_answerability_confidence"), + "scorer": ArtifactScorer(base_scores), + }, + context_artifact_path: { + "metadata": _artifact_metadata( + "query_context_answerability_confidence" + ), + "scorer": ArtifactScorer(context_scores), + }, + }, + ) + + def fake_from_pretrained(**kwargs: object) -> ArtifactEncoder: + return ArtifactEncoder(max_length=cast(int, kwargs["max_length"])) + + monkeypatch.setattr( + "ranksmith.confidence.structural.FrozenAutoEncoder.from_pretrained", + fake_from_pretrained, + ) + + return CBDRStrategy( + base_estimator=StructuralConfidenceEstimator.from_artifact(base_artifact_path), + context_estimator=StructuralConfidenceEstimator.from_artifact( + context_artifact_path + ), + answer_generator=generator, + skip_threshold=skip_threshold, + ) + + +def _strategy( + *, + base_scores: list[Any] | None = None, + context_scores: list[Any] | None = None, + generator: FakeGenerator | None = None, + skip_threshold: float = 0.8, + max_document_chars: int = 4000, +) -> CBDRStrategy: + return CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=base_scores or [0.2], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=context_scores or [0.7], + ), + answer_generator=generator or FakeGenerator(context_answers=["context answer"]), + skip_threshold=skip_threshold, + max_document_chars=max_document_chars, + ) + + +def _unused_model_client() -> Any: + return object() + + +def test_cbdr_exports_are_submodule_only() -> None: + strategies = importlib.import_module("ranksmith.strategies") + root = importlib.import_module("ranksmith") + + assert strategies.CBDRStrategy is not None + assert not hasattr(root, "CBDRStrategy") + + +def test_cbdr_from_artifacts_builds_estimators_with_hf_options( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + calls: list[dict[str, object]] = [] + base_estimator = FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.9], + ) + context_estimator = FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.5], + ) + + def fake_from_artifact(path: str | Path, **kwargs: object) -> FakeEstimator: + calls.append({"path": Path(path), **kwargs}) + return base_estimator if len(calls) == 1 else context_estimator + + monkeypatch.setattr( + "ranksmith.confidence.StructuralConfidenceEstimator.from_artifact", + fake_from_artifact, + ) + generator = FakeGenerator(context_answers=[]) + + strategy = CBDRStrategy.from_artifacts( + base_artifact_path=tmp_path / "base.joblib", + context_artifact_path=tmp_path / "context.joblib", + base_metadata_path=tmp_path / "base.metadata.json", + context_metadata_path=tmp_path / "context.metadata.json", + answer_generator=generator, + skip_threshold=0.7, + max_document_chars=123, + hf_token="token", + cache_dir="/tmp/hf", + device="cpu", + local_files_only=True, + max_length=128, + allow_truncation=True, + ) + + assert strategy.base_estimator is base_estimator + assert strategy.context_estimator is context_estimator + assert strategy.answer_generator is generator + assert strategy.skip_threshold == 0.7 + assert strategy.max_document_chars == 123 + assert calls == [ + { + "path": tmp_path / "base.joblib", + "metadata_path": tmp_path / "base.metadata.json", + "task_type": "query_answerability_confidence", + "hf_token": "token", + "cache_dir": "/tmp/hf", + "device": "cpu", + "local_files_only": True, + "max_length": 128, + "allow_truncation": True, + }, + { + "path": tmp_path / "context.joblib", + "metadata_path": tmp_path / "context.metadata.json", + "task_type": "query_context_answerability_confidence", + "hf_token": "token", + "cache_dir": "/tmp/hf", + "device": "cpu", + "local_files_only": True, + "max_length": 128, + "allow_truncation": True, + }, + ] + + +def test_cbdr_empty_documents_returns_empty_without_calls() -> None: + generator = FakeGenerator(context_answers=[]) + strategy = _strategy(generator=generator) + + assert ( + strategy.rerank( + query="Who?", + documents=[], + model_client=object(), + ) + == [] + ) + assert generator.query_calls == [] + assert generator.context_calls == [] + + +def test_cbdr_top_k_zero_returns_empty_without_calls() -> None: + generator = FakeGenerator(context_answers=["a"]) + strategy = _strategy(generator=generator) + + assert ( + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + top_k=0, + ) + == [] + ) + assert generator.query_calls == [] + assert generator.context_calls == [] + + +def test_cbdr_skip_path_preserves_original_order_and_metadata() -> None: + generator = FakeGenerator(context_answers=[]) + strategy = _strategy(base_scores=[0.91], generator=generator, skip_threshold=0.8) + documents = [ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + ] + + results = strategy.rerank(query="Who?", documents=documents, model_client=object()) + + assert [result.document.id for result in results] == ["a", "b"] + assert [result.rank for result in results] == [1, 2] + assert [result.original_index for result in results] == [0, 1] + assert [dict(result.metadata) for result in results] == [ + { + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": True, + "base_confidence": 0.91, + "skip_threshold": 0.8, + "context_confidence": None, + "confidence_gain": None, + }, + { + "strategy": "cbdr", + "algorithm": "cbdr", + "cbdr_skipped": True, + "base_confidence": 0.91, + "skip_threshold": 0.8, + "context_confidence": None, + "confidence_gain": None, + }, + ] + assert generator.query_calls == ["Who?"] + assert generator.context_calls == [] + + +def test_cbdr_skip_path_applies_top_k_after_original_order() -> None: + strategy = _strategy(base_scores=[0.9], skip_threshold=0.8) + + results = strategy.rerank( + query="Who?", + documents=[ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + ], + model_client=object(), + top_k=1, + ) + + assert [result.document.id for result in results] == ["a"] + assert [result.rank for result in results] == [1] + assert [result.original_index for result in results] == [0] + + +def test_cbdr_skip_path_does_not_validate_long_documents() -> None: + strategy = _strategy( + base_scores=[0.9], + context_scores=[], + generator=FakeGenerator(context_answers=[]), + skip_threshold=0.8, + max_document_chars=3, + ) + + results = strategy.rerank( + query="Who?", + documents=[Document(text="abcdef")], + model_client=object(), + ) + + assert len(results) == 1 + assert results[0].metadata["cbdr_skipped"] is True + + +def test_cbdr_rerank_path_sorts_by_gain_and_preserves_ties() -> None: + generator = FakeGenerator(context_answers=["answer a", "answer b", "answer c"]) + strategy = _strategy( + base_scores=[0.4], + context_scores=[0.6, 0.8, 0.8], + generator=generator, + skip_threshold=0.9, + ) + + results = strategy.rerank( + query="Who?", + documents=[ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + Document(id="c", text="gamma"), + ], + model_client=object(), + top_k=2, + ) + + assert [result.document.id for result in results] == ["b", "c"] + assert [result.rank for result in results] == [1, 2] + assert [result.original_index for result in results] == [1, 2] + assert [result.metadata["cbdr_skipped"] for result in results] == [False, False] + assert [result.metadata["base_confidence"] for result in results] == [0.4, 0.4] + assert [result.metadata["context_confidence"] for result in results] == [0.8, 0.8] + assert [result.metadata["confidence_gain"] for result in results] == pytest.approx( + [0.4, 0.4] + ) + assert generator.context_calls == [ + ("Who?", "alpha"), + ("Who?", "beta"), + ("Who?", "gamma"), + ] + + +@pytest.mark.parametrize("skip_threshold", [math.nan, math.inf, -0.1, 1.1, True]) +def test_cbdr_invalid_skip_threshold_fails(skip_threshold: object) -> None: + with pytest.raises(ValueError, match="skip_threshold"): + CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + skip_threshold=cast(float, skip_threshold), + ) + + +def test_cbdr_threshold_zero_always_skips_non_empty_documents() -> None: + strategy = _strategy(base_scores=[0.0], skip_threshold=0.0) + + results = strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert results[0].metadata["cbdr_skipped"] is True + + +def test_cbdr_threshold_one_skips_only_at_exact_one() -> None: + skipped = _strategy(base_scores=[1.0], skip_threshold=1.0).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + reranked = _strategy( + base_scores=[0.999], + context_scores=[1.0], + generator=FakeGenerator(context_answers=["a"]), + skip_threshold=1.0, + ).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert skipped[0].metadata["cbdr_skipped"] is True + assert reranked[0].metadata["cbdr_skipped"] is False + + +def test_cbdr_rerank_path_validates_long_documents() -> None: + strategy = _strategy(base_scores=[0.2], skip_threshold=0.8, max_document_chars=3) + + with pytest.raises(DocumentTooLongError): + strategy.rerank( + query="Who?", + documents=[Document(text="abcdef")], + model_client=object(), + ) + + +def test_cbdr_empty_query_fails() -> None: + with pytest.raises(RerankInputError, match="query"): + _strategy().rerank( + query=" ", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_negative_top_k_fails() -> None: + with pytest.raises(RerankInputError, match="top_k"): + _strategy().rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + top_k=-1, + ) + + +def test_cbdr_invalid_task_types_fail() -> None: + with pytest.raises(RerankInputError, match="base_estimator"): + CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + ) + + with pytest.raises(RerankInputError, match="context_estimator"): + CBDRStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + ) + + +@pytest.mark.parametrize("score", [math.nan, math.inf, -0.1, 1.1, "0.5", True]) +def test_cbdr_invalid_base_score_fails(score: object) -> None: + with pytest.raises(RerankStrategyError): + _strategy(base_scores=[score]).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +@pytest.mark.parametrize("score", [math.nan, math.inf, -0.1, 1.1, "0.5", True]) +def test_cbdr_invalid_context_score_fails(score: object) -> None: + with pytest.raises(RerankStrategyError): + _strategy( + base_scores=[0.1], + context_scores=[score], + skip_threshold=0.8, + ).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_answer_generator_empty_output_fails() -> None: + with pytest.raises(RerankProviderError): + _strategy(generator=FakeGenerator(base_answer="")).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_generator_unexpected_exception_wraps_provider_error() -> None: + with pytest.raises(RerankProviderError) as exc_info: + _strategy(generator=FakeGenerator(base_answer=RuntimeError("boom"))).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert isinstance(exc_info.value.__cause__, RuntimeError) + + +def test_cbdr_direct_estimator_unexpected_exception_propagates() -> None: + error = RuntimeError("confidence failed") + + with pytest.raises(RuntimeError, match="confidence failed"): + _strategy(base_scores=[error]).rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_cbdr_facade_wraps_unexpected_estimator_error() -> None: + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=_strategy(base_scores=[RuntimeError("confidence failed")]), + ) + + with pytest.raises(RerankProviderError) as exc_info: + reranker.rerank("Who?", [Document(text="alpha")]) + + assert isinstance(exc_info.value.__cause__, RuntimeError) + + +def test_cbdr_artifact_e2e_skip_path_through_azure_facade( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + strategy = _artifact_strategy( + monkeypatch=monkeypatch, + tmp_path=tmp_path, + base_scores=[0.91], + context_scores=[], + generator=FakeGenerator(context_answers=[]), + skip_threshold=0.8, + ) + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=strategy, + ) + + results = reranker.rerank( + "who played karen in married to the mob?", + [ + Document( + id="similar-but-weak", + text="Michelle Pfeiffer appears in the film.", + ), + Document(id="direct-evidence", text="Nancy Travis played Karen."), + ], + ) + + assert [result.document.id for result in results] == [ + "similar-but-weak", + "direct-evidence", + ] + assert [result.metadata["cbdr_skipped"] for result in results] == [True, True] + assert [result.metadata["base_confidence"] for result in results] == [0.91, 0.91] + assert [result.metadata["context_confidence"] for result in results] == [ + None, + None, + ] + assert [result.metadata["confidence_gain"] for result in results] == [None, None] + + +def test_cbdr_artifact_e2e_rerank_path_through_azure_facade( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + strategy = _artifact_strategy( + monkeypatch=monkeypatch, + tmp_path=tmp_path, + base_scores=[0.3], + context_scores=[0.45, 0.9], + generator=FakeGenerator( + base_answer="base answer", + context_answers=["low answer", "high answer"], + ), + skip_threshold=0.8, + ) + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=strategy, + ) + + results = reranker.rerank( + "who played karen in married to the mob?", + [ + Document( + id="similar-but-weak", + text="Michelle Pfeiffer appears in the film.", + ), + Document(id="direct-evidence", text="Nancy Travis played Karen."), + ], + ) + + assert [result.document.id for result in results] == [ + "direct-evidence", + "similar-but-weak", + ] + assert [result.metadata["cbdr_skipped"] for result in results] == [False, False] + assert [result.metadata["base_confidence"] for result in results] == [0.3, 0.3] + assert [result.metadata["context_confidence"] for result in results] == [0.9, 0.45] + assert [result.metadata["confidence_gain"] for result in results] == pytest.approx( + [0.6, 0.15] + ) diff --git a/tests/test_compare_reranking.py b/tests/test_compare_reranking.py index 35528ba..b5feacf 100644 --- a/tests/test_compare_reranking.py +++ b/tests/test_compare_reranking.py @@ -4,6 +4,7 @@ import importlib.util import sys from pathlib import Path +from typing import Any, cast import pytest @@ -96,6 +97,37 @@ def test_compare_optional_prp_sliding_p1_is_preserved() -> None: assert compare_reranking._selected_algorithms(args, cases) == ("prp_sliding_p1",) +def test_compare_explicit_cbdr_is_preserved() -> None: + args = argparse.Namespace(algorithm="cbdr") + cases: list[BenchmarkCase] = [] + + assert compare_reranking._selected_algorithms(args, cases) == ("cbdr",) + + +def test_compare_estimates_cbdr_provider_calls_as_upper_bound() -> None: + assert ( + compare_reranking._estimate_provider_calls( + 20, + "cbdr", + window_size=20, + stride=10, + top_k=5, + ) + == 21 + ) + + +def test_compare_describes_cbdr_estimates_as_upper_bounds() -> None: + assert ( + compare_reranking._call_estimate_message( + needs_live=True, + algorithms=("cbdr",), + call_estimates={"cbdr": 21}, + ) + == "Live Azure comparison upper-bounds provider calls at 21: {'cbdr': 21}" + ) + + def test_compare_explicit_tourrank_is_preserved_for_non_100_candidate_cases() -> None: args = argparse.Namespace(algorithm="tourrank_r") cases: list[BenchmarkCase] = [] @@ -242,6 +274,16 @@ def test_compare_report_records_method_settings() -> None: passes=10, tourrank_rounds=2, set_size=3, + cbdr_base_artifact=None, + cbdr_context_artifact=None, + cbdr_skip_threshold=0.8, + cbdr_device="cpu", + cbdr_cache_dir=None, + cbdr_local_files_only=False, + cbdr_hf_token_env=None, + cbdr_max_length=None, + cbdr_max_document_chars=4000, + cbdr_allow_truncation=False, query_id=[], timeout=None, checkpoint_output=None, @@ -266,6 +308,167 @@ def test_compare_report_records_method_settings() -> None: } +def test_compare_cbdr_method_settings_record_runtime_options() -> None: + args = argparse.Namespace( + candidate_count=20, + cbdr_base_artifact=Path("base.joblib"), + cbdr_context_artifact=Path("context.joblib"), + cbdr_answer_provider="lmstudio", + cbdr_skip_threshold=0.7, + cbdr_device="cpu", + cbdr_cache_dir=Path(".hf-cache"), + cbdr_local_files_only=True, + cbdr_hf_token_env="HF_TOKEN", + cbdr_max_length=128, + cbdr_max_document_chars=1234, + cbdr_allow_truncation=True, + lmstudio_base_url="http://localhost:1234/v1", + lmstudio_model="google/gemma-4-12b", + lmstudio_api_key="local-key", + lmstudio_max_tokens=64, + ) + + assert compare_reranking._method_setting(args, "cbdr") == { + "candidate_count": 20, + "base_artifact": "base.joblib", + "context_artifact": "context.joblib", + "answer_provider": "lmstudio", + "skip_threshold": 0.7, + "device": "cpu", + "cache_dir": ".hf-cache", + "local_files_only": True, + "hf_token_env": "HF_TOKEN", + "max_length": 128, + "max_document_chars": 1234, + "allow_truncation": True, + "lmstudio_base_url": "http://localhost:1234/v1", + "lmstudio_model": "google/gemma-4-12b", + "lmstudio_api_key": "configured", + "lmstudio_max_tokens": 64, + "provider_call_estimate": "upper_bound", + "top_k_early_stop": False, + } + + +def test_compare_cbdr_method_settings_record_lmstudio_env_defaults( + monkeypatch: pytest.MonkeyPatch, +) -> None: + args = argparse.Namespace( + candidate_count=20, + cbdr_base_artifact=Path("base.joblib"), + cbdr_context_artifact=Path("context.joblib"), + cbdr_answer_provider="lmstudio", + cbdr_skip_threshold=0.7, + cbdr_device="cpu", + cbdr_cache_dir=None, + cbdr_local_files_only=False, + cbdr_hf_token_env=None, + cbdr_max_length=128, + cbdr_max_document_chars=1234, + cbdr_allow_truncation=False, + lmstudio_base_url=None, + lmstudio_model=None, + lmstudio_api_key=None, + lmstudio_max_tokens=64, + ) + monkeypatch.setenv("LMSTUDIO_MODEL", "env-model") + monkeypatch.setenv("LMSTUDIO_API_KEY", "env-key") + monkeypatch.delenv("LMSTUDIO_BASE_URL", raising=False) + + settings = compare_reranking._method_setting(args, "cbdr") + + assert settings["lmstudio_base_url"] == "http://localhost:1234/v1" + assert settings["lmstudio_model"] == "env-model" + assert settings["lmstudio_api_key"] == "configured" + + +def test_compare_cbdr_requires_artifacts() -> None: + args = argparse.Namespace( + algorithm="cbdr", + cbdr_base_artifact=None, + cbdr_context_artifact=Path("context.joblib"), + cbdr_skip_threshold=0.8, + cbdr_max_length=None, + cbdr_max_document_chars=4000, + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + top_k=5, + candidate_count=20, + max_cases=None, + timeout=None, + checkpoint_output=None, + dataset="fixture", + cache_dir=None, + dataset_name=None, + candidates=None, + candidate_strategy="candidate_file", + ) + + with pytest.raises(SystemExit, match="--cbdr-base-artifact"): + compare_reranking._validate_args(args) + + +def test_compare_cbdr_requires_context_artifact() -> None: + args = argparse.Namespace( + algorithm="cbdr", + cbdr_base_artifact=Path("base.joblib"), + cbdr_context_artifact=None, + cbdr_skip_threshold=0.8, + cbdr_max_length=None, + cbdr_max_document_chars=4000, + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + top_k=5, + candidate_count=20, + max_cases=None, + timeout=None, + checkpoint_output=None, + dataset="fixture", + cache_dir=None, + dataset_name=None, + candidates=None, + candidate_strategy="candidate_file", + ) + + with pytest.raises(SystemExit, match="--cbdr-context-artifact"): + compare_reranking._validate_args(args) + + +def test_compare_rejects_non_positive_lmstudio_max_tokens() -> None: + args = argparse.Namespace( + algorithm="original_bm25", + cbdr_skip_threshold=0.8, + cbdr_max_length=None, + cbdr_max_document_chars=4000, + lmstudio_max_tokens=0, + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + top_k=5, + candidate_count=20, + max_cases=None, + timeout=None, + checkpoint_output=None, + output=None, + dataset="fixture", + cache_dir=None, + dataset_name=None, + candidates=None, + candidate_strategy="candidate_file", + ) + + with pytest.raises(SystemExit, match="--lmstudio-max-tokens"): + compare_reranking._validate_args(args) + + def test_compare_setwise_heapsort_uses_set_size( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -312,7 +515,7 @@ def rerank( set_size=5, ) - assert captured["strategy"].set_size == 5 + assert cast(Any, captured["strategy"]).set_size == 5 def test_compare_setwise_hs_s10_uses_fixed_set_size( @@ -361,7 +564,7 @@ def rerank( set_size=5, ) - assert captured["strategy"].set_size == 10 + assert cast(Any, captured["strategy"]).set_size == 10 def test_compare_setwise_heapsort_forwards_top_k( @@ -462,7 +665,7 @@ def rerank( tourrank_rounds=2, ) - assert captured["strategy"].max_adaptive_reranker_calls == 4 + assert cast(Any, captured["strategy"]).max_adaptive_reranker_calls == 4 def test_compare_acurank_b1_uses_budget_one(monkeypatch: pytest.MonkeyPatch) -> None: @@ -508,7 +711,7 @@ def rerank( tourrank_rounds=2, ) - assert captured["strategy"].max_adaptive_reranker_calls == 1 + assert cast(Any, captured["strategy"]).max_adaptive_reranker_calls == 1 def test_compare_acurank_k5_b1_uses_target_rank_five( @@ -556,8 +759,8 @@ def rerank( tourrank_rounds=2, ) - assert captured["strategy"].target_rank == 5 - assert captured["strategy"].max_adaptive_reranker_calls == 1 + assert cast(Any, captured["strategy"]).target_rank == 5 + assert cast(Any, captured["strategy"]).max_adaptive_reranker_calls == 1 def test_compare_rankgpt_sw_w5_uses_small_sliding_window( @@ -605,8 +808,8 @@ def rerank( tourrank_rounds=2, ) - assert captured["strategy"].window_size == 5 - assert captured["strategy"].stride == 2 + assert cast(Any, captured["strategy"]).window_size == 5 + assert cast(Any, captured["strategy"]).stride == 2 def test_compare_original_bm25_returns_candidate_order() -> None: @@ -658,6 +861,52 @@ def test_compare_original_bm25_only_does_not_require_live_flag( assert output_path.exists() +def test_compare_cbdr_requires_live_flag( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + sys, + "argv", + [ + "compare_reranking.py", + "--algorithm", + "cbdr", + "--cbdr-base-artifact", + str(tmp_path / "base.joblib"), + "--cbdr-context-artifact", + str(tmp_path / "context.joblib"), + ], + ) + + with pytest.raises(SystemExit, match="--allow-live"): + compare_reranking.main() + + +def test_compare_cbdr_lmstudio_requires_live_flag( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + sys, + "argv", + [ + "compare_reranking.py", + "--algorithm", + "cbdr", + "--cbdr-base-artifact", + str(tmp_path / "base.joblib"), + "--cbdr-context-artifact", + str(tmp_path / "context.joblib"), + "--cbdr-answer-provider", + "lmstudio", + ], + ) + + with pytest.raises(SystemExit, match="--allow-live"): + compare_reranking.main() + + def test_compare_cli_defaults_match_top20_evaluate_at_5( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -670,6 +919,366 @@ def test_compare_cli_defaults_match_top20_evaluate_at_5( assert args.stride == 10 assert args.set_size == 3 assert args.top_k == 5 + assert args.cbdr_skip_threshold == 0.8 + assert args.cbdr_device == "cpu" + assert args.cbdr_max_document_chars == 4000 + assert args.cbdr_answer_provider == "azure" + assert args.lmstudio_base_url is None + assert args.lmstudio_model is None + assert args.lmstudio_api_key is None + assert args.lmstudio_max_tokens == 128 + + +def test_compare_cbdr_rank_case_builds_strategy_from_artifacts( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + captured: dict[str, object] = {} + base_estimator = object() + context_estimator = object() + + def fake_cached_cbdr_estimators(**kwargs: object) -> tuple[object, object]: + captured["estimator_kwargs"] = kwargs + return base_estimator, context_estimator + + class FakeCBDRStrategy: + def __init__(self, **kwargs: object) -> None: + captured["strategy_kwargs"] = kwargs + + def rerank( + self, + *, + query: str, + documents: list[object], + model_client: object | None = None, + top_k: int | None = None, + ) -> list[object]: + del query, model_client, top_k + return [ + type("Result", (), {"document": document})() for document in documents + ] + + class FakeAnswerGenerator: + @classmethod + def from_env(cls, **kwargs: object) -> object: + captured["generator_kwargs"] = kwargs + generator = cls() + captured["answer_generator"] = generator + return generator + + class FakeReranker: + def __init__(self, **kwargs: object) -> None: + del kwargs + raise AssertionError("CBDR benchmark must not create AzureOpenAIReranker") + + def rerank( + self, + query: str, + documents: list[object], + *, + top_k: int | None = None, + ) -> list[object]: + del query, top_k + return [ + type("Result", (), {"document": document})() for document in documents + ] + + monkeypatch.setenv("AZURE_OPENAI_API_KEY", "key") + monkeypatch.setenv("AZURE_OPENAI_ENDPOINT", "https://example.openai.azure.com") + monkeypatch.setenv("AZURE_OPENAI_DEPLOYMENT", "deployment") + monkeypatch.setenv("HF_TOKEN", "hf-token") + monkeypatch.setattr("ranksmith.AzureOpenAIReranker", FakeReranker) + monkeypatch.setattr("ranksmith.strategies.CBDRStrategy", FakeCBDRStrategy) + monkeypatch.setattr( + compare_reranking, + "_cached_cbdr_estimators", + fake_cached_cbdr_estimators, + ) + monkeypatch.setattr( + "ranksmith.integrations.AzureAnswerGenerator", + FakeAnswerGenerator, + ) + + ranked = compare_reranking._rank_case( + case=BenchmarkCase( + fixture_id="fixture", + dataset="dataset", + source="source", + license="license", + query_id="q1", + query="query", + documents=tuple( + BenchmarkDocument(id=str(index), title="", text="") + for index in range(2) + ), + qrels={}, + ), + algorithm="cbdr", + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + top_k=5, + cbdr_base_artifact=tmp_path / "base.joblib", + cbdr_context_artifact=tmp_path / "context.joblib", + cbdr_skip_threshold=0.7, + cbdr_device="cpu", + cbdr_cache_dir=tmp_path / "hf", + cbdr_local_files_only=True, + cbdr_hf_token_env="HF_TOKEN", + cbdr_max_length=128, + cbdr_max_document_chars=1234, + cbdr_allow_truncation=True, + ) + + assert ranked == ("0", "1") + assert captured["generator_kwargs"] == {"timeout": None} + assert captured["estimator_kwargs"] == { + "base_artifact_path": tmp_path / "base.joblib", + "context_artifact_path": tmp_path / "context.joblib", + "hf_token": "hf-token", + "cache_dir": str(tmp_path / "hf"), + "device": "cpu", + "local_files_only": True, + "max_length": 128, + "allow_truncation": True, + } + assert captured["strategy_kwargs"] == { + "base_estimator": base_estimator, + "context_estimator": context_estimator, + "answer_generator": captured["answer_generator"], + "skip_threshold": 0.7, + "max_document_chars": 1234, + } + + +def test_compare_cbdr_rank_case_rejects_unsupported_answer_provider( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + class FakeAnswerGenerator: + @classmethod + def from_env(cls, **kwargs: object) -> object: + del kwargs + raise AssertionError("unsupported provider must not create Azure generator") + + class FakeLMStudioModelProvider: + def __init__(self, **kwargs: object) -> None: + del kwargs + raise AssertionError("unsupported provider must not create LM Studio") + + monkeypatch.setattr( + "ranksmith.integrations.AzureAnswerGenerator", + FakeAnswerGenerator, + ) + monkeypatch.setattr( + "ranksmith.integrations.LMStudioModelProvider", + FakeLMStudioModelProvider, + ) + + with pytest.raises(SystemExit, match="--cbdr-answer-provider"): + compare_reranking._rank_case( + case=BenchmarkCase( + fixture_id="fixture", + dataset="dataset", + source="source", + license="license", + query_id="q1", + query="query", + documents=(BenchmarkDocument(id="d1", title="", text=""),), + qrels={"d1": 1}, + ), + algorithm="cbdr", + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + cbdr_base_artifact=tmp_path / "base.joblib", + cbdr_context_artifact=tmp_path / "context.joblib", + cbdr_answer_provider="bad", + ) + + +def test_compare_cbdr_rank_case_uses_lmstudio_answer_provider( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + captured: dict[str, object] = {} + + class FakeLMStudioModelProvider: + def __init__(self, **kwargs: object) -> None: + captured["provider_kwargs"] = kwargs + + class FakeProviderAnswerGenerator: + def __init__(self, *, provider: object) -> None: + captured["provider"] = provider + self.no_answer_value = "__NO_ANSWER__" + + base_estimator = object() + context_estimator = object() + + def fake_cached_cbdr_estimators(**kwargs: object) -> tuple[object, object]: + captured["estimator_kwargs"] = kwargs + return base_estimator, context_estimator + + class FakeCBDRStrategy: + def __init__(self, **kwargs: object) -> None: + captured["strategy_kwargs"] = kwargs + + def rerank( + self, + *, + query: str, + documents: list[object], + model_client: object | None = None, + top_k: int | None = None, + ) -> list[object]: + del query, model_client, top_k + return [ + type("Result", (), {"document": document})() for document in documents + ] + + class FakeAzureAnswerGenerator: + @classmethod + def from_env(cls, **kwargs: object) -> object: + del kwargs + raise AssertionError("LM Studio CBDR must not create Azure generator") + + monkeypatch.setattr("ranksmith.strategies.CBDRStrategy", FakeCBDRStrategy) + monkeypatch.setattr( + compare_reranking, + "_cached_cbdr_estimators", + fake_cached_cbdr_estimators, + ) + monkeypatch.setattr( + "ranksmith.integrations.AzureAnswerGenerator", + FakeAzureAnswerGenerator, + ) + monkeypatch.setattr( + "ranksmith.integrations.LMStudioModelProvider", + FakeLMStudioModelProvider, + ) + monkeypatch.setattr( + "ranksmith.integrations.ProviderAnswerGenerator", + FakeProviderAnswerGenerator, + ) + + ranked = compare_reranking._rank_case( + case=BenchmarkCase( + fixture_id="fixture", + dataset="dataset", + source="source", + license="license", + query_id="q1", + query="query", + documents=(BenchmarkDocument(id="d1", title="", text=""),), + qrels={"d1": 1}, + ), + algorithm="cbdr", + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + cbdr_base_artifact=tmp_path / "base.joblib", + cbdr_context_artifact=tmp_path / "context.joblib", + cbdr_answer_provider="lmstudio", + lmstudio_base_url="http://localhost:1234/v1", + lmstudio_model="google/gemma-4-12b", + lmstudio_api_key="local-key", + lmstudio_max_tokens=64, + timeout=2.5, + ) + + assert ranked == ("d1",) + assert captured["provider_kwargs"] == { + "base_url": "http://localhost:1234/v1", + "model": "google/gemma-4-12b", + "api_key": "local-key", + "timeout": 2.5, + "max_tokens": 64, + } + assert captured["estimator_kwargs"] == { + "base_artifact_path": tmp_path / "base.joblib", + "context_artifact_path": tmp_path / "context.joblib", + "hf_token": None, + "cache_dir": None, + "device": "cpu", + "local_files_only": False, + "max_length": None, + "allow_truncation": False, + } + strategy_kwargs = cast(dict[str, object], captured["strategy_kwargs"]) + answer_generator = strategy_kwargs["answer_generator"] + assert strategy_kwargs == { + "base_estimator": base_estimator, + "context_estimator": context_estimator, + "answer_generator": answer_generator, + "skip_threshold": 0.8, + "max_document_chars": 4000, + } + assert cast(Any, answer_generator).no_answer_value == "__NO_ANSWER__" + assert isinstance(captured["provider"], FakeLMStudioModelProvider) + + +def test_compare_evaluate_cases_forwards_cbdr_answer_provider_options( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + args = argparse.Namespace( + window_size=20, + stride=10, + passes=10, + tourrank_rounds=2, + set_size=3, + top_k=5, + timeout=1.5, + cbdr_base_artifact=Path("base.joblib"), + cbdr_context_artifact=Path("context.joblib"), + cbdr_skip_threshold=0.8, + cbdr_device="cpu", + cbdr_cache_dir=None, + cbdr_local_files_only=False, + cbdr_hf_token_env=None, + cbdr_max_length=None, + cbdr_max_document_chars=4000, + cbdr_allow_truncation=False, + cbdr_answer_provider="lmstudio", + lmstudio_base_url="http://localhost:1234/v1", + lmstudio_model="google/gemma-4-12b", + lmstudio_api_key="local-key", + lmstudio_max_tokens=64, + checkpoint_output=None, + ) + case = BenchmarkCase( + fixture_id="fixture", + dataset="dataset", + source="source", + license="license", + query_id="q1", + query="query", + documents=(BenchmarkDocument(id="d1", title="", text=""),), + qrels={"d1": 1}, + ) + + def fake_rank_case(**kwargs: object) -> tuple[str, ...]: + captured.update(kwargs) + return ("d1",) + + monkeypatch.setattr(compare_reranking, "_rank_case", fake_rank_case) + + compare_reranking._evaluate_cases( + args=args, + algorithms=("cbdr",), + cases=(case,), + ) + + assert captured["cbdr_answer_provider"] == "lmstudio" + assert captured["lmstudio_base_url"] == "http://localhost:1234/v1" + assert captured["lmstudio_model"] == "google/gemma-4-12b" + assert captured["lmstudio_api_key"] == "local-key" + assert captured["lmstudio_max_tokens"] == 64 def test_compare_live_invalid_output_is_recorded( diff --git a/tests/test_confidence_answerability_tasks.py b/tests/test_confidence_answerability_tasks.py new file mode 100644 index 0000000..3d21209 --- /dev/null +++ b/tests/test_confidence_answerability_tasks.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import importlib +from collections.abc import Sequence +from dataclasses import dataclass + +import pytest + +from ranksmith.confidence import ( + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + ScorerMetadata, + StructuralConfidenceEstimator, + TaskType, +) + + +@dataclass +class FakeEncoder: + encoder_name: str = "bert-base-uncased" + encoder_revision: str | None = None + tokenizer_name: str = "bert-base-uncased" + tokenizer_revision: str | None = None + max_length: int = 64 + last_text: str | None = None + + def encode(self, text: str) -> tuple[list[list[float]], list[int]]: + self.last_text = text + hidden = [[float(row + col) for col in range(4)] for row in range(8)] + mask = [1] * 8 + return hidden, mask + + +class FakeScorer: + def __init__( + self, + *, + task_type: TaskType, + score: float = 0.75, + ) -> None: + self.metadata = ScorerMetadata( + artifact_schema_version="structural-artifact-v1", + scorer_type="fake", + task_type=task_type, + encoder_name="bert-base-uncased", + encoder_revision=None, + tokenizer_name="bert-base-uncased", + tokenizer_revision=None, + input_template_version="structural-template-v1", + feature_schema_version="structural-v1", + feature_dim=70, + feature_dtype="float64", + max_length=64, + granularity="two_scale", + local_window_size=5, + local_stride=2, + score_output="probability", + positive_class_index=1, + ) + self.score = score + + def predict_confidence(self, features: Sequence[float]) -> float: + assert len(features) == 70 + return self.score + + +@pytest.fixture(autouse=True) +def fake_structural_features(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + "ranksmith.confidence.structural.extract_structural_features", + lambda hidden_states, mask, *, max_length: [0.0] * 70, + ) + + +def test_confidence_submodule_exports_answerability_inputs() -> None: + confidence = importlib.import_module("ranksmith.confidence") + + assert confidence.QueryAnswerabilityConfidenceInput is ( + QueryAnswerabilityConfidenceInput + ) + assert confidence.QueryContextAnswerabilityConfidenceInput is ( + QueryContextAnswerabilityConfidenceInput + ) + + +def test_estimator_scores_query_answerability_input_with_exact_template() -> None: + encoder = FakeEncoder() + estimator = StructuralConfidenceEstimator( + encoder=encoder, + scorer=FakeScorer(task_type="query_answerability_confidence"), + task_type="query_answerability_confidence", + ) + + result = estimator.score( + QueryAnswerabilityConfidenceInput(query="Who?", answer="Nancy Travis") + ) + + assert result.score == 0.75 + assert result.task_type == "query_answerability_confidence" + assert encoder.last_text == "Query:\nWho?\n\nAnswer:\nNancy Travis" + + +def test_estimator_scores_query_context_answerability_input_with_exact_template() -> ( + None +): + encoder = FakeEncoder() + estimator = StructuralConfidenceEstimator( + encoder=encoder, + scorer=FakeScorer(task_type="query_context_answerability_confidence"), + task_type="query_context_answerability_confidence", + ) + + result = estimator.score( + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="Karen was played by Nancy Travis.", + answer="Nancy Travis", + ) + ) + + assert result.score == 0.75 + assert result.task_type == "query_context_answerability_confidence" + assert encoder.last_text == ( + "Query:\nWho?\n\nContext:\nKaren was played by Nancy Travis." + "\n\nAnswer:\nNancy Travis" + ) diff --git a/tests/test_confidence_gain_strategy.py b/tests/test_confidence_gain_strategy.py new file mode 100644 index 0000000..68d5d98 --- /dev/null +++ b/tests/test_confidence_gain_strategy.py @@ -0,0 +1,658 @@ +from __future__ import annotations + +import importlib +import math +import sys +from dataclasses import dataclass +from pathlib import Path +from types import ModuleType +from typing import Any, cast + +import pytest + +from ranksmith import AzureOpenAIReranker +from ranksmith.confidence import ( + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + StructuralConfidenceEstimator, + StructuralConfidenceResult, + TaskType, +) +from ranksmith.confidence.scorer import ARTIFACT_SCHEMA_VERSION +from ranksmith.errors import ( + DocumentTooLongError, + RerankInputError, + RerankProviderError, + RerankStrategyError, +) +from ranksmith.strategies import ConfidenceGainStrategy +from ranksmith.types import Document + + +@dataclass +class FakeEstimator: + task_type: TaskType + scores: list[Any] + calls: list[object] | None = None + + def score(self, item: object) -> StructuralConfidenceResult: + if self.calls is not None: + self.calls.append(item) + value = self.scores.pop(0) + if isinstance(value, BaseException): + raise value + return StructuralConfidenceResult( + score=value, + task_type=self.task_type, + feature_schema_version="structural-v1", + ) + + +class FakeGenerator: + def __init__( + self, + *, + base_answer: object = "base answer", + context_answers: list[object] | None = None, + ) -> None: + self.base_answer = base_answer + self.context_answers = context_answers or [] + self.query_calls: list[str] = [] + self.context_calls: list[tuple[str, str]] = [] + + def answer_query(self, query: str) -> Any: + self.query_calls.append(query) + if isinstance(self.base_answer, BaseException): + raise self.base_answer + return self.base_answer + + def answer_with_context(self, query: str, context: str) -> Any: + self.context_calls.append((query, context)) + answer = self.context_answers.pop(0) + if isinstance(answer, BaseException): + raise answer + return answer + + +class ArtifactScorer: + def __init__(self, scores: list[float]) -> None: + self.scores = scores + + def predict_confidence(self, features: object) -> float: + del features + return self.scores.pop(0) + + +class ArtifactEncoder: + encoder_name = "bert-base-uncased" + encoder_revision = None + tokenizer_name = "bert-base-uncased" + tokenizer_revision = None + + def __init__(self, *, max_length: int) -> None: + self.max_length = max_length + + def encode(self, text: str) -> tuple[list[list[float]], list[int]]: + seed = float(len(text) % 7 + 1) + hidden = [[seed + row * 0.01, row * 0.02, seed * 0.03] for row in range(40)] + return hidden, [1] * len(hidden) + + +def _artifact_metadata(task_type: TaskType) -> dict[str, object]: + return { + "artifact_schema_version": ARTIFACT_SCHEMA_VERSION, + "scorer_type": "joblib-wrapper", + "task_type": task_type, + "encoder_name": "bert-base-uncased", + "encoder_revision": None, + "tokenizer_name": "bert-base-uncased", + "tokenizer_revision": None, + "input_template_version": "structural-template-v1", + "feature_schema_version": "structural-v1", + "feature_dim": 70, + "feature_dtype": "float64", + "max_length": 64, + "granularity": "two_scale", + "local_window_size": 5, + "local_stride": 2, + "score_output": "probability", + "positive_class_index": 1, + } + + +def _install_artifact_joblib( + monkeypatch: pytest.MonkeyPatch, + artifacts: dict[Path, object], +) -> None: + module = ModuleType("joblib") + + def load(path: str | Path) -> object: + return artifacts[Path(path)] + + module.load = load # type: ignore[attr-defined] + monkeypatch.setitem(sys.modules, "joblib", module) + + +def _strategy( + *, + base_scores: list[Any] | None = None, + context_scores: list[Any] | None = None, + generator: FakeGenerator | None = None, +) -> ConfidenceGainStrategy: + return ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=base_scores or [0.2], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=context_scores or [0.7], + ), + answer_generator=generator or FakeGenerator(context_answers=["context answer"]), + ) + + +def _unused_model_client() -> Any: + return object() + + +def test_confidence_gain_exports_are_submodule_only() -> None: + strategies = importlib.import_module("ranksmith.strategies") + root = importlib.import_module("ranksmith") + + assert strategies.AnswerGenerator is not None + assert strategies.ConfidenceEstimator is not None + assert strategies.ConfidenceGainResult is not None + assert strategies.ConfidenceGainStrategy is not None + assert not hasattr(root, "AnswerGenerator") + assert not hasattr(root, "ConfidenceEstimator") + assert not hasattr(root, "ConfidenceGainResult") + assert not hasattr(root, "ConfidenceGainStrategy") + + +def test_confidence_gain_sorts_by_gain_desc_and_preserves_ties() -> None: + generator = FakeGenerator(context_answers=["answer a", "answer b", "answer c"]) + strategy = _strategy( + base_scores=[0.4], + context_scores=[0.6, 0.8, 0.8], + generator=generator, + ) + documents = [ + Document(id="a", text="alpha"), + Document(id="b", text="beta"), + Document(id="c", text="gamma"), + ] + + results = strategy.rerank( + query="Who?", + documents=documents, + model_client=object(), + ) + + assert [result.document.id for result in results] == ["b", "c", "a"] + assert [result.rank for result in results] == [1, 2, 3] + assert [result.original_index for result in results] == [1, 2, 0] + assert [dict(result.metadata) for result in results] == [ + { + "strategy": "confidence_gain", + "algorithm": "confidence_gain", + "base_confidence": 0.4, + "context_confidence": 0.8, + "confidence_gain": 0.4, + }, + { + "strategy": "confidence_gain", + "algorithm": "confidence_gain", + "base_confidence": 0.4, + "context_confidence": 0.8, + "confidence_gain": 0.4, + }, + { + "strategy": "confidence_gain", + "algorithm": "confidence_gain", + "base_confidence": 0.4, + "context_confidence": 0.6, + "confidence_gain": 0.19999999999999996, + }, + ] + + +def test_confidence_gain_applies_top_k_after_sorting() -> None: + strategy = _strategy( + base_scores=[0.1], + context_scores=[0.2, 0.9, 0.5], + generator=FakeGenerator(context_answers=["a", "b", "c"]), + ) + + results = strategy.rerank( + query="Who?", + documents=[Document(text="a"), Document(text="b"), Document(text="c")], + model_client=object(), + top_k=2, + ) + + assert [result.original_index for result in results] == [1, 2] + assert [result.rank for result in results] == [1, 2] + + +def test_confidence_gain_calls_answer_generator_expected_number_of_times() -> None: + generator = FakeGenerator(context_answers=["a", "b"]) + strategy = _strategy( + base_scores=[0.3], + context_scores=[0.4, 0.5], + generator=generator, + ) + + strategy.rerank( + query="Who?", + documents=[Document(text="alpha"), Document(text="beta")], + model_client=object(), + ) + + assert generator.query_calls == ["Who?"] + assert generator.context_calls == [("Who?", "alpha"), ("Who?", "beta")] + + +def test_confidence_gain_task_mismatch_fails_for_base_and_context() -> None: + from ranksmith.strategies import ConfidenceGainStrategy # noqa: PLC0415 + + with pytest.raises(RerankInputError, match="base_estimator"): + ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + ) + + with pytest.raises(RerankInputError, match="context_estimator"): + ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + ) + + +def test_confidence_gain_invalid_algorithm_fails() -> None: + with pytest.raises(ValueError, match="algorithm"): + ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + algorithm=cast(Any, "other"), + ) + + +def test_confidence_gain_invalid_max_document_chars_fails() -> None: + with pytest.raises(ValueError, match="max_document_chars"): + ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.1], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.2], + ), + answer_generator=FakeGenerator(), + max_document_chars=0, + ) + + +def test_confidence_gain_negative_top_k_fails() -> None: + with pytest.raises(RerankInputError, match="top_k"): + _strategy().rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + top_k=-1, + ) + + +def test_confidence_gain_empty_query_fails() -> None: + with pytest.raises(RerankInputError, match="query"): + _strategy().rerank( + query=" ", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_confidence_gain_empty_documents_returns_empty_without_calls() -> None: + generator = FakeGenerator(context_answers=[]) + strategy = _strategy(generator=generator) + + assert strategy.rerank(query="Who?", documents=[], model_client=object()) == [] + assert generator.query_calls == [] + assert generator.context_calls == [] + + +def test_confidence_gain_long_document_fails() -> None: + from ranksmith.strategies import ConfidenceGainStrategy # noqa: PLC0415 + + strategy = ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.2], + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.5], + ), + answer_generator=FakeGenerator(context_answers=["a"]), + max_document_chars=3, + ) + + with pytest.raises(DocumentTooLongError): + strategy.rerank( + query="Who?", + documents=[Document(text="abcdef")], + model_client=object(), + top_k=None, + ) + + +@pytest.mark.parametrize("base_answer", ["", " ", 123]) +def test_confidence_gain_empty_or_non_string_answer_query_fails( + base_answer: object, +) -> None: + strategy = _strategy( + generator=FakeGenerator(base_answer=base_answer, context_answers=["a"]) + ) + + with pytest.raises(RerankProviderError): + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +@pytest.mark.parametrize("context_answer", ["", " ", 123]) +def test_confidence_gain_empty_or_non_string_answer_with_context_fails( + context_answer: object, +) -> None: + strategy = _strategy(generator=FakeGenerator(context_answers=[context_answer])) + + with pytest.raises(RerankProviderError): + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_confidence_gain_generator_unexpected_exception_wraps_provider_error() -> None: + strategy = _strategy( + generator=FakeGenerator( + base_answer=TimeoutError("timeout"), + context_answers=["a"], + ) + ) + + with pytest.raises(RerankProviderError) as exc_info: + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert isinstance(exc_info.value.__cause__, TimeoutError) + + +def test_confidence_gain_preserves_answer_query_provider_error() -> None: + error = RerankProviderError("provider failed") + strategy = _strategy( + generator=FakeGenerator( + base_answer=error, + context_answers=["a"], + ) + ) + + with pytest.raises(RerankProviderError) as exc_info: + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert exc_info.value is error + assert exc_info.value.__cause__ is None + + +def test_confidence_gain_preserves_answer_with_context_provider_error() -> None: + error = RerankProviderError("provider failed") + strategy = _strategy( + generator=FakeGenerator( + context_answers=[error], + ) + ) + + with pytest.raises(RerankProviderError) as exc_info: + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert exc_info.value is error + assert exc_info.value.__cause__ is None + + +def test_confidence_gain_facade_preserves_generator_provider_error() -> None: + error = RerankProviderError("provider failed") + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=_strategy( + generator=FakeGenerator( + base_answer=error, + context_answers=["a"], + ) + ), + ) + + with pytest.raises(RerankProviderError) as exc_info: + reranker.rerank("Who?", [Document(text="alpha")]) + + assert exc_info.value is error + + +def test_confidence_gain_facade_wraps_unexpected_generator_error() -> None: + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=_strategy( + generator=FakeGenerator( + base_answer=RuntimeError("generation failed"), + context_answers=["a"], + ) + ), + ) + + with pytest.raises(RerankProviderError) as exc_info: + reranker.rerank("Who?", [Document(text="alpha")]) + + assert isinstance(exc_info.value.__cause__, RuntimeError) + + +def test_confidence_gain_facade_preserves_estimator_provider_error() -> None: + error = RerankProviderError("confidence provider failed") + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=_strategy(base_scores=[error]), + ) + + with pytest.raises(RerankProviderError) as exc_info: + reranker.rerank("Who?", [Document(text="alpha")]) + + assert exc_info.value is error + + +def test_confidence_gain_facade_wraps_unexpected_estimator_error() -> None: + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=_strategy(base_scores=[RuntimeError("confidence failed")]), + ) + + with pytest.raises(RerankProviderError) as exc_info: + reranker.rerank("Who?", [Document(text="alpha")]) + + assert isinstance(exc_info.value.__cause__, RuntimeError) + + +@pytest.mark.parametrize("score", [math.nan, math.inf, -0.1, 1.1, "0.5", True]) +def test_confidence_gain_invalid_base_score_fails(score: object) -> None: + strategy = _strategy(base_scores=[score], context_scores=[0.5]) + + with pytest.raises(RerankStrategyError): + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +@pytest.mark.parametrize("score", [math.nan, math.inf, -0.1, 1.1, "0.5", True]) +def test_confidence_gain_invalid_context_score_fails(score: object) -> None: + strategy = _strategy(context_scores=[score]) + + with pytest.raises(RerankStrategyError): + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_confidence_gain_estimator_confidence_error_propagates() -> None: + error = RuntimeError("confidence scorer failed") + strategy = _strategy(base_scores=[error]) + + with pytest.raises(RuntimeError, match="confidence scorer failed"): + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + +def test_confidence_gain_passes_expected_confidence_inputs() -> None: + base_calls: list[object] = [] + context_calls: list[object] = [] + from ranksmith.strategies import ConfidenceGainStrategy # noqa: PLC0415 + + strategy = ConfidenceGainStrategy( + base_estimator=FakeEstimator( + task_type="query_answerability_confidence", + scores=[0.2], + calls=base_calls, + ), + context_estimator=FakeEstimator( + task_type="query_context_answerability_confidence", + scores=[0.5], + calls=context_calls, + ), + answer_generator=FakeGenerator( + base_answer="base", + context_answers=["context"], + ), + ) + + strategy.rerank( + query="Who?", + documents=[Document(text="alpha")], + model_client=object(), + ) + + assert base_calls == [ + QueryAnswerabilityConfidenceInput(query="Who?", answer="base") + ] + assert context_calls == [ + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="alpha", + answer="context", + ) + ] + + +def test_confidence_gain_e2e_smoke_from_artifacts_through_azure_facade( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + base_artifact_path = tmp_path / "query_answerability.joblib" + context_artifact_path = tmp_path / "query_context_answerability.joblib" + _install_artifact_joblib( + monkeypatch, + { + base_artifact_path: { + "metadata": _artifact_metadata("query_answerability_confidence"), + "scorer": ArtifactScorer([0.3]), + }, + context_artifact_path: { + "metadata": _artifact_metadata( + "query_context_answerability_confidence" + ), + "scorer": ArtifactScorer([0.45, 0.9]), + }, + }, + ) + + def fake_from_pretrained(**kwargs: object) -> ArtifactEncoder: + return ArtifactEncoder(max_length=cast(int, kwargs["max_length"])) + + monkeypatch.setattr( + "ranksmith.confidence.structural.FrozenAutoEncoder.from_pretrained", + fake_from_pretrained, + ) + + strategy = ConfidenceGainStrategy( + base_estimator=StructuralConfidenceEstimator.from_artifact(base_artifact_path), + context_estimator=StructuralConfidenceEstimator.from_artifact( + context_artifact_path + ), + answer_generator=FakeGenerator( + base_answer="base answer", + context_answers=["low answer", "high answer"], + ), + ) + reranker = AzureOpenAIReranker( + model_client=_unused_model_client(), + strategy=strategy, + ) + + results = reranker.rerank( + "who played karen in married to the mob?", + [ + Document( + id="similar-but-weak", + text="Michelle Pfeiffer appears in the film.", + ), + Document(id="direct-evidence", text="Nancy Travis played Karen."), + ], + ) + + assert [result.document.id for result in results] == [ + "direct-evidence", + "similar-but-weak", + ] + assert [result.metadata["base_confidence"] for result in results] == [0.3, 0.3] + assert [result.metadata["context_confidence"] for result in results] == [0.9, 0.45] + assert [result.metadata["confidence_gain"] for result in results] == pytest.approx( + [0.6, 0.15] + ) diff --git a/tests/test_confidence_generation_answerability.py b/tests/test_confidence_generation_answerability.py new file mode 100644 index 0000000..ec46c4e --- /dev/null +++ b/tests/test_confidence_generation_answerability.py @@ -0,0 +1,508 @@ +from __future__ import annotations + +import importlib +import json +from pathlib import Path +from typing import Any + +import pytest + +from ranksmith.confidence_generation.errors import ( + ConfidenceGenerationInputError, + ConfidenceGenerationParseError, +) +from ranksmith.confidence_generation.io import ( + load_completed_ids, + load_query_answerability_generation_samples, + load_query_context_answerability_generation_samples, +) +from ranksmith.confidence_generation.prompts import ( + QUERY_ANSWERABILITY_SYSTEM_PROMPT, + QUERY_CONTEXT_ANSWERABILITY_SYSTEM_PROMPT, + build_query_answerability_prompt, + build_query_context_answerability_prompt, +) +from ranksmith.confidence_generation.types import ( + QueryAnswerabilityGenerationSample, + QueryContextAnswerabilityGenerationSample, +) +from ranksmith.model import ModelMessage, ModelRequest, ModelResponse +from ranksmith.types import RerankUsage + +ANSWERABILITY_PUBLIC_NAMES = ( + "QueryAnswerabilityGenerationConfig", + "QueryContextAnswerabilityGenerationConfig", + "generate_query_answerability_confidence_dataset", + "generate_query_context_answerability_confidence_dataset", +) + + +class RecordingProvider: + def __init__(self, outputs: list[ModelResponse]) -> None: + self.outputs = list(outputs) + self.requests: list[ModelRequest] = [] + + def complete(self, request: ModelRequest) -> ModelResponse: + self.requests.append(request) + return self.outputs.pop(0) + + +def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None: + path.write_text( + "".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows), + encoding="utf-8", + ) + + +def _read_jsonl(path: Path) -> list[dict[str, Any]]: + return [ + json.loads(line) + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + + +def test_query_answerability_prompt_uses_parametric_knowledge_contract() -> None: + prompt = build_query_answerability_prompt( + QueryAnswerabilityGenerationSample( + id="q1", + query="Who played Karen?", + gold_answer="Nancy Travis", + ), + no_answer_value="UNKNOWN", + ) + + assert "Question:\nWho played Karen?" in prompt + assert "Context:" not in prompt + assert '{"answer":"short answer"}' in prompt + assert '{"answer":"UNKNOWN"}' in prompt + assert "parametric knowledge" in prompt + + +def test_query_context_answerability_prompt_uses_context_contract() -> None: + prompt = build_query_context_answerability_prompt( + QueryContextAnswerabilityGenerationSample( + id="c1", + query="Who played Karen?", + context="Nancy Travis played Karen.", + gold_answer="Nancy Travis", + ), + no_answer_value="NO_CONTEXT_ANSWER", + ) + + assert "Question:\nWho played Karen?" in prompt + assert "Context:\nNancy Travis played Karen." in prompt + assert '{"answer":"short answer"}' in prompt + assert '{"answer":"NO_CONTEXT_ANSWER"}' in prompt + assert "Use only the context" in prompt + + +def test_answerability_public_exports_are_submodule_only() -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + ranksmith = importlib.import_module("ranksmith") + + for name in ANSWERABILITY_PUBLIC_NAMES: + assert hasattr(generation, name) + assert not hasattr(ranksmith, name) + + +def test_query_answerability_config_rejects_invalid_options(tmp_path: Path) -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + provider = RecordingProvider([]) + + with pytest.raises(generation.ConfidenceGenerationInputError): + generation.QueryAnswerabilityGenerationConfig( + input_path=tmp_path / "in.jsonl", + output_path=tmp_path / "out.jsonl", + provider=object(), + ) + + with pytest.raises(generation.ConfidenceGenerationInputError): + generation.QueryAnswerabilityGenerationConfig( + input_path=tmp_path / "in.jsonl", + output_path=tmp_path / "out.jsonl", + provider=provider, + overwrite=True, + resume=True, + ) + + with pytest.raises(generation.ConfidenceGenerationInputError): + generation.QueryAnswerabilityGenerationConfig( + input_path=tmp_path / "in.jsonl", + output_path=tmp_path / "out.jsonl", + provider=provider, + max_items=0, + ) + + with pytest.raises(generation.ConfidenceGenerationInputError): + generation.QueryAnswerabilityGenerationConfig( + input_path=tmp_path / "in.jsonl", + output_path=tmp_path / "out.jsonl", + provider=provider, + no_answer_value=" ", + ) + + +def test_query_context_answerability_config_rejects_invalid_options( + tmp_path: Path, +) -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + + with pytest.raises(generation.ConfidenceGenerationInputError): + generation.QueryContextAnswerabilityGenerationConfig( + input_path=tmp_path / "in.jsonl", + output_path=tmp_path / "out.jsonl", + provider=RecordingProvider([]), + max_context_chars=0, + ) + + +def test_answerability_raw_loaders_validate_and_preserve_text( + tmp_path: Path, +) -> None: + query_path = tmp_path / "query.jsonl" + context_path = tmp_path / "context.jsonl" + _write_jsonl( + query_path, + [ + { + "id": " q1 ", + "query": " Who? ", + "gold_answer": [" Gold "], + "source": " source ", + "group_id": " group ", + "metadata": {"dataset": "unit"}, + } + ], + ) + _write_jsonl( + context_path, + [ + { + "id": "c1", + "query": "q", + "context": " 12345 ", + "gold_answer": "g", + } + ], + ) + + query_samples = load_query_answerability_generation_samples(query_path) + assert query_samples[0].id == " q1 " + assert query_samples[0].query == " Who? " + assert query_samples[0].gold_answer == [" Gold "] + assert query_samples[0].source == " source " + assert query_samples[0].group_id == " group " + assert query_samples[0].metadata["dataset"] == "unit" + + with pytest.raises(ConfidenceGenerationInputError, match="context"): + load_query_context_answerability_generation_samples( + context_path, + max_context_chars=5, + ) + + +@pytest.mark.parametrize( + "row", + [ + {"id": "q1", "query": "q"}, + {"id": "q1", "query": " ", "gold_answer": "g"}, + {"id": "q1", "query": "q", "gold_answer": "", "extra": "nope"}, + {"id": "q1", "query": "q", "gold_answer": "g", "metadata": []}, + ], +) +def test_query_answerability_loader_rejects_invalid_rows( + tmp_path: Path, + row: dict[str, object], +) -> None: + path = tmp_path / "query.jsonl" + _write_jsonl(path, [row]) + + with pytest.raises(ConfidenceGenerationInputError): + load_query_answerability_generation_samples(path) + + +def test_generate_answerability_datasets_write_canonical_rows_and_usage( + tmp_path: Path, +) -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + query_input = tmp_path / "query_in.jsonl" + query_output = tmp_path / "query_out.jsonl" + context_input = tmp_path / "context_in.jsonl" + context_output = tmp_path / "context_out.jsonl" + _write_jsonl( + query_input, + [ + { + "id": "q1", + "query": "Who played Karen?", + "gold_answer": ["nancy travis"], + "metadata": {"dataset": "unit"}, + } + ], + ) + _write_jsonl( + context_input, + [ + { + "id": "c1", + "query": "Who played Karen?", + "context": "Nancy Travis played Karen.", + "gold_answer": "Nancy Travis", + "source": "row-source", + } + ], + ) + usage = RerankUsage(prompt_tokens=1, completion_tokens=2, total_tokens=3) + query_provider = RecordingProvider( + [ModelResponse(content='{"answer":" Nancy Travis "}', usage=usage)] + ) + context_provider = RecordingProvider( + [ModelResponse(content='{"answer":"__NO_ANSWER__"}')] + ) + seen_usage: list[RerankUsage] = [] + + query_result = generation.generate_query_answerability_confidence_dataset( + generation.QueryAnswerabilityGenerationConfig( + input_path=query_input, + output_path=query_output, + provider=query_provider, + source="config-source", + on_usage=seen_usage.append, + ) + ) + context_result = generation.generate_query_context_answerability_confidence_dataset( + generation.QueryContextAnswerabilityGenerationConfig( + input_path=context_input, + output_path=context_output, + provider=context_provider, + include_raw_model_output=False, + ) + ) + + query_rows = _read_jsonl(query_output) + context_rows = _read_jsonl(context_output) + assert query_result.generated_count == 1 + assert query_result.positive_count == 1 + assert query_rows[0]["task_type"] == "query_answerability_confidence" + assert query_rows[0]["id"] == "q1" + assert query_rows[0]["query"] == "Who played Karen?" + assert query_rows[0]["answer"] == " Nancy Travis " + assert query_rows[0]["gold_answer"] == ["nancy travis"] + assert query_rows[0]["label"] == 1 + assert query_rows[0]["source"] == "config-source" + assert query_rows[0]["metadata"]["input_metadata"] == {"dataset": "unit"} + assert query_rows[0]["metadata"]["generation"]["generation_task"] == ( + "query_answerability" + ) + assert query_rows[0]["metadata"]["generation"]["match_policy"] == ( + "normalized_exact" + ) + assert query_rows[0]["metadata"]["generation"]["raw_model_output"] == ( + '{"answer":" Nancy Travis "}' + ) + assert context_result.negative_count == 1 + assert context_rows[0]["task_type"] == "query_context_answerability_confidence" + assert context_rows[0]["context"] == "Nancy Travis played Karen." + assert context_rows[0]["source"] == "row-source" + assert "raw_model_output" not in context_rows[0]["metadata"]["generation"] + assert seen_usage == [usage] + + +def test_answerability_pipelines_use_expected_model_request_messages( + tmp_path: Path, +) -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + query_input = tmp_path / "query_in.jsonl" + query_output = tmp_path / "query_out.jsonl" + context_input = tmp_path / "context_in.jsonl" + context_output = tmp_path / "context_out.jsonl" + _write_jsonl( + query_input, + [{"id": "q1", "query": "Who played Karen?", "gold_answer": "Nancy Travis"}], + ) + _write_jsonl( + context_input, + [ + { + "id": "c1", + "query": "Who played Karen?", + "context": "Nancy Travis played Karen.", + "gold_answer": "Nancy Travis", + } + ], + ) + query_provider = RecordingProvider([ModelResponse(content='{"answer":"Nancy"}')]) + context_provider = RecordingProvider([ModelResponse(content='{"answer":"Nancy"}')]) + + generation.generate_query_answerability_confidence_dataset( + generation.QueryAnswerabilityGenerationConfig( + input_path=query_input, + output_path=query_output, + provider=query_provider, + no_answer_value="UNKNOWN", + ) + ) + generation.generate_query_context_answerability_confidence_dataset( + generation.QueryContextAnswerabilityGenerationConfig( + input_path=context_input, + output_path=context_output, + provider=context_provider, + no_answer_value="NO_CONTEXT_ANSWER", + ) + ) + + assert query_provider.requests[0].messages == [ + ModelMessage(role="system", content=QUERY_ANSWERABILITY_SYSTEM_PROMPT), + ModelMessage( + role="user", + content=build_query_answerability_prompt( + QueryAnswerabilityGenerationSample( + id="q1", + query="Who played Karen?", + gold_answer="Nancy Travis", + ), + no_answer_value="UNKNOWN", + ), + ), + ] + assert context_provider.requests[0].messages == [ + ModelMessage( + role="system", + content=QUERY_CONTEXT_ANSWERABILITY_SYSTEM_PROMPT, + ), + ModelMessage( + role="user", + content=build_query_context_answerability_prompt( + QueryContextAnswerabilityGenerationSample( + id="c1", + query="Who played Karen?", + context="Nancy Travis played Karen.", + gold_answer="Nancy Travis", + ), + no_answer_value="NO_CONTEXT_ANSWER", + ), + ), + ] + + +def test_answerability_resume_accepts_completed_rows_and_skips( + tmp_path: Path, +) -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + input_path = tmp_path / "query_in.jsonl" + output_path = tmp_path / "query_out.jsonl" + _write_jsonl( + input_path, + [ + {"id": "q1", "query": "q", "gold_answer": "g"}, + {"id": "q2", "query": "q", "gold_answer": "g"}, + ], + ) + _write_jsonl( + output_path, + [ + { + "id": "q1", + "task_type": "query_answerability_confidence", + "query": "q", + "answer": "g", + "label": 1, + } + ], + ) + provider = RecordingProvider([ModelResponse(content='{"answer":"g"}')]) + + result = generation.generate_query_answerability_confidence_dataset( + generation.QueryAnswerabilityGenerationConfig( + input_path=input_path, + output_path=output_path, + provider=provider, + resume=True, + ) + ) + + rows = _read_jsonl(output_path) + assert result.skipped_count == 1 + assert result.generated_count == 1 + assert [row["id"] for row in rows] == ["q1", "q2"] + + +def test_query_context_answerability_resume_after_partial_parse_failure( + tmp_path: Path, +) -> None: + generation = importlib.import_module("ranksmith.confidence_generation") + input_path = tmp_path / "context_in.jsonl" + output_path = tmp_path / "context_out.jsonl" + _write_jsonl( + input_path, + [ + { + "id": "c1", + "query": "q", + "context": "gold answer appears here", + "gold_answer": "gold", + }, + { + "id": "c2", + "query": "q", + "context": "next answer appears here", + "gold_answer": "next", + }, + ], + ) + first_provider = RecordingProvider( + [ + ModelResponse(content='{"answer":"gold"}'), + ModelResponse(content='{"answer":}'), + ] + ) + + with pytest.raises(ConfidenceGenerationParseError, match="valid JSON"): + generation.generate_query_context_answerability_confidence_dataset( + generation.QueryContextAnswerabilityGenerationConfig( + input_path=input_path, + output_path=output_path, + provider=first_provider, + ) + ) + + assert len(first_provider.requests) == 2 + assert [row["id"] for row in _read_jsonl(output_path)] == ["c1"] + + second_provider = RecordingProvider([ModelResponse(content='{"answer":"next"}')]) + result = generation.generate_query_context_answerability_confidence_dataset( + generation.QueryContextAnswerabilityGenerationConfig( + input_path=input_path, + output_path=output_path, + provider=second_provider, + resume=True, + ) + ) + + rows = _read_jsonl(output_path) + assert len(second_provider.requests) == 1 + assert result.skipped_count == 1 + assert result.generated_count == 1 + assert [row["id"] for row in rows] == ["c1", "c2"] + + +def test_answerability_resume_rejects_mismatched_task_type( + tmp_path: Path, +) -> None: + path = tmp_path / "out.jsonl" + _write_jsonl( + path, + [ + { + "id": "q1", + "task_type": "query_context_answerability_confidence", + "query": "q", + "answer": "a", + "label": 1, + } + ], + ) + + with pytest.raises(ConfidenceGenerationInputError, match="task_type"): + load_completed_ids(path, task_type="query_answerability_confidence") diff --git a/tests/test_confidence_scorer.py b/tests/test_confidence_scorer.py index 613c7c6..49fc260 100644 --- a/tests/test_confidence_scorer.py +++ b/tests/test_confidence_scorer.py @@ -8,7 +8,7 @@ import pytest -from ranksmith.confidence import ConfidenceArtifactError, ScorerMetadata +from ranksmith.confidence import ConfidenceArtifactError, ScorerMetadata, TaskType from ranksmith.confidence.scorer import ( ARTIFACT_SCHEMA_VERSION, JoblibScorerWrapper, @@ -138,6 +138,33 @@ def test_validate_scorer_metadata_accepts_matching_metadata() -> None: ) +@pytest.mark.parametrize( + "task_type", + [ + "query_answerability_confidence", + "query_context_answerability_confidence", + ], +) +def test_metadata_parser_and_validator_accept_answerability_task_types( + task_type: TaskType, +) -> None: + parsed = metadata_from_dict(metadata_dict(task_type=task_type)) + + assert parsed.task_type == task_type + validate_scorer_metadata( + parsed, + encoder_name="bert-base-uncased", + encoder_revision=None, + tokenizer_name="bert-base-uncased", + tokenizer_revision=None, + task_type=task_type, + max_length=256, + input_template_version="structural-template-v1", + feature_schema_version="structural-v1", + feature_dim=70, + ) + + def test_validate_scorer_metadata_rejects_mismatch() -> None: with pytest.raises(ConfidenceArtifactError): validate_scorer_metadata( diff --git a/tests/test_confidence_templates.py b/tests/test_confidence_templates.py index 513c951..c2451af 100644 --- a/tests/test_confidence_templates.py +++ b/tests/test_confidence_templates.py @@ -6,6 +6,9 @@ AnswerConfidenceInput, ConfidenceInputError, JudgmentConfidenceInput, + QueryAnswerabilityConfidenceInput, + QueryContextAnswerabilityConfidenceInput, + TaskType, ) from ranksmith.confidence.templates import ( format_confidence_input, @@ -34,6 +37,31 @@ def test_formats_judgment_confidence_template() -> None: assert text == "Query:\nquery\n\nDocument:\ndocument\n\nJudgment:\ndirect evidence" +def test_formats_query_answerability_confidence_template() -> None: + text = format_confidence_input( + "query_answerability_confidence", + QueryAnswerabilityConfidenceInput(query="Who?", answer="Nancy Travis"), + ) + + assert text == "Query:\nWho?\n\nAnswer:\nNancy Travis" + + +def test_formats_query_context_answerability_confidence_template() -> None: + text = format_confidence_input( + "query_context_answerability_confidence", + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="Karen was played by Nancy Travis.", + answer="Nancy Travis", + ), + ) + + assert text == ( + "Query:\nWho?\n\nContext:\nKaren was played by Nancy Travis." + "\n\nAnswer:\nNancy Travis" + ) + + def test_rejects_mismatched_input_type() -> None: with pytest.raises(ConfidenceInputError): format_confidence_input( @@ -46,9 +74,79 @@ def test_rejects_mismatched_input_type() -> None: ) +@pytest.mark.parametrize( + ("task_type", "item"), + [ + ( + "query_answerability_confidence", + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="Karen was played by Nancy Travis.", + answer="Nancy Travis", + ), + ), + ( + "query_context_answerability_confidence", + QueryAnswerabilityConfidenceInput(query="Who?", answer="Nancy Travis"), + ), + ], +) +def test_rejects_mismatched_answerability_input_type( + task_type: TaskType, + item: QueryAnswerabilityConfidenceInput | QueryContextAnswerabilityConfidenceInput, +) -> None: + with pytest.raises(ConfidenceInputError): + format_confidence_input(task_type, item) + + def test_rejects_whitespace_required_field() -> None: with pytest.raises(ConfidenceInputError): format_confidence_input( "answer_confidence", AnswerConfidenceInput(context=" ", answer="answer"), ) + + +@pytest.mark.parametrize( + ("task_type", "item"), + [ + ( + "query_answerability_confidence", + QueryAnswerabilityConfidenceInput(query=" ", answer="Nancy Travis"), + ), + ( + "query_answerability_confidence", + QueryAnswerabilityConfidenceInput(query="Who?", answer="\t"), + ), + ( + "query_context_answerability_confidence", + QueryContextAnswerabilityConfidenceInput( + query=" ", + context="Karen was played by Nancy Travis.", + answer="Nancy Travis", + ), + ), + ( + "query_context_answerability_confidence", + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="\n", + answer="Nancy Travis", + ), + ), + ( + "query_context_answerability_confidence", + QueryContextAnswerabilityConfidenceInput( + query="Who?", + context="Karen was played by Nancy Travis.", + answer="\t", + ), + ), + ], +) +def test_answerability_templates_reject_whitespace_required_fields( + task_type: TaskType, + item: QueryAnswerabilityConfidenceInput | QueryContextAnswerabilityConfidenceInput, +) -> None: + with pytest.raises(ConfidenceInputError): + format_confidence_input(task_type, item) diff --git a/tests/test_confidence_training_answerability.py b/tests/test_confidence_training_answerability.py new file mode 100644 index 0000000..8627a1b --- /dev/null +++ b/tests/test_confidence_training_answerability.py @@ -0,0 +1,184 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from ranksmith.confidence_training import ( + ConfidenceDatasetError, + ConfidenceTrainingConfig, +) +from ranksmith.confidence_training.dataset import load_canonical_dataset +from ranksmith.confidence_training.features import extract_feature_rows +from ranksmith.confidence_training.types import CanonicalConfidenceSample + + +class RecordingEncoder: + max_length = 34 + + def __init__(self) -> None: + self.texts: list[str] = [] + + def encode(self, text: str) -> tuple[list[list[float]], list[int]]: + self.texts.append(text) + return [[0.0, 1.0], [1.0, 2.0]], [1, 1] + + +def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None: + path.write_text( + "".join(json.dumps(row) + "\n" for row in rows), + encoding="utf-8", + ) + + +def test_config_accepts_query_answerability_confidence(tmp_path: Path) -> None: + config = ConfidenceTrainingConfig( + task_type="query_answerability_confidence", + dataset_path=tmp_path / "dataset.jsonl", + output_dir=tmp_path / "run", + export_path=tmp_path / "artifact.joblib", + ) + + assert config.task_type == "query_answerability_confidence" + + +def test_config_accepts_query_context_answerability_confidence( + tmp_path: Path, +) -> None: + config = ConfidenceTrainingConfig( + task_type="query_context_answerability_confidence", + dataset_path=tmp_path / "dataset.jsonl", + output_dir=tmp_path / "run", + export_path=tmp_path / "artifact.joblib", + ) + + assert config.task_type == "query_context_answerability_confidence" + + +def test_load_query_answerability_confidence_canonical_jsonl( + tmp_path: Path, +) -> None: + path = tmp_path / "query-answerability.jsonl" + _write_jsonl( + path, + [ + { + "id": "qa1", + "query": "Who played Karen?", + "answer": "Nancy Travis", + "label": 1, + "gold_answer": ["Nancy Travis"], + "metadata": {"split": "fixture"}, + } + ], + ) + + samples = load_canonical_dataset( + path, + task_type="query_answerability_confidence", + ) + + assert len(samples) == 1 + assert samples[0].task_type == "query_answerability_confidence" + assert samples[0].query == "Who played Karen?" + assert samples[0].answer == "Nancy Travis" + assert samples[0].gold_answer == ["Nancy Travis"] + assert samples[0].metadata["split"] == "fixture" + + +def test_load_query_context_answerability_confidence_canonical_jsonl( + tmp_path: Path, +) -> None: + path = tmp_path / "query-context-answerability.jsonl" + _write_jsonl( + path, + [ + { + "id": "qca1", + "query": "Who played Karen?", + "context": "Karen was played by Nancy Travis.", + "answer": "Nancy Travis", + "label": 1, + "gold_answer": "Nancy Travis", + "group_id": "question-1", + } + ], + ) + + samples = load_canonical_dataset( + path, + task_type="query_context_answerability_confidence", + ) + + assert len(samples) == 1 + assert samples[0].task_type == "query_context_answerability_confidence" + assert samples[0].query == "Who played Karen?" + assert samples[0].context == "Karen was played by Nancy Travis." + assert samples[0].answer == "Nancy Travis" + assert samples[0].gold_answer == "Nancy Travis" + assert samples[0].group_id == "question-1" + + +def test_query_context_answerability_missing_context_fails( + tmp_path: Path, +) -> None: + path = tmp_path / "missing-context.jsonl" + _write_jsonl( + path, + [ + { + "id": "qca1", + "query": "Who played Karen?", + "answer": "Nancy Travis", + "label": 1, + } + ], + ) + + with pytest.raises(ConfidenceDatasetError, match="missing required field: context"): + load_canonical_dataset( + path, + task_type="query_context_answerability_confidence", + ) + + +def test_extract_feature_rows_formats_answerability_samples( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + "ranksmith.confidence_training.features.extract_structural_features", + lambda hidden_states, attention_mask, *, max_length: [0.0] * 70, + ) + encoder = RecordingEncoder() + samples = [ + CanonicalConfidenceSample( + id="qa1", + task_type="query_answerability_confidence", + label=1, + query="Who played Karen?", + answer="Nancy Travis", + ), + CanonicalConfidenceSample( + id="qca1", + task_type="query_context_answerability_confidence", + label=0, + query="Who played Karen?", + context="Karen was played by Nancy Travis.", + answer="Nancy Travis", + ), + ] + + rows = extract_feature_rows(samples, encoder=encoder) + + assert [row.task_type for row in rows] == [ + "query_answerability_confidence", + "query_context_answerability_confidence", + ] + assert encoder.texts == [ + "Query:\nWho played Karen?\n\nAnswer:\nNancy Travis", + ( + "Query:\nWho played Karen?\n\nContext:\n" + "Karen was played by Nancy Travis.\n\nAnswer:\nNancy Travis" + ), + ] diff --git a/tests/test_confidence_training_dataset_report.py b/tests/test_confidence_training_dataset_report.py new file mode 100644 index 0000000..870c4ce --- /dev/null +++ b/tests/test_confidence_training_dataset_report.py @@ -0,0 +1,160 @@ +from __future__ import annotations + +import importlib.util +import json +import subprocess +from pathlib import Path +from typing import Any + +import pytest + +from ranksmith.confidence_training import ConfidenceDatasetError +from ranksmith.confidence_training.dataset_report import build_dataset_report + +REPORT_SCRIPT_PATH = Path("scripts/report_confidence_dataset.py") + + +def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None: + path.write_text( + "".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows), + encoding="utf-8", + ) + + +def _load_report_script() -> Any: + spec = importlib.util.spec_from_file_location( + "report_confidence_dataset", + REPORT_SCRIPT_PATH, + ) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_report_confidence_dataset_help_exits_successfully() -> None: + result = subprocess.run( + ["uv", "run", "python", str(REPORT_SCRIPT_PATH), "--help"], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0 + assert "--task" in result.stdout + + +def test_build_dataset_report_counts_sources_groups_and_missing_values( + tmp_path: Path, +) -> None: + dataset = tmp_path / "dataset.jsonl" + _write_jsonl( + dataset, + [ + { + "id": "q1", + "task_type": "query_answerability_confidence", + "query": "q1", + "answer": "a", + "label": 1, + "source": "alpha", + "group_id": "g1", + }, + { + "id": "q2", + "task_type": "query_answerability_confidence", + "query": "q2", + "answer": "a", + "label": 0, + "source": "alpha", + }, + { + "id": "q3", + "task_type": "query_answerability_confidence", + "query": "q3", + "answer": "a", + "label": 1, + "source": "beta", + "group_id": "g2", + }, + { + "id": "q4", + "task_type": "query_answerability_confidence", + "query": "q4", + "answer": "a", + "label": 0, + }, + ], + ) + + report = build_dataset_report(dataset, "query_answerability_confidence") + + assert report["task_type"] == "query_answerability_confidence" + assert report["sample_count"] == 4 + assert report["positive_count"] == 2 + assert report["negative_count"] == 2 + assert report["positive_rate"] == 0.5 + assert report["source_count"] == 2 + assert report["group_count"] == 2 + assert report["missing_source_count"] == 1 + assert report["missing_source_rate"] == 0.25 + assert report["missing_group_id_count"] == 2 + assert report["missing_group_id_rate"] == 0.5 + assert report["sources"] == { + "__MISSING__": { + "sample_count": 1, + "positive_count": 0, + "negative_count": 1, + "positive_rate": 0.0, + }, + "alpha": { + "sample_count": 2, + "positive_count": 1, + "negative_count": 1, + "positive_rate": 0.5, + }, + "beta": { + "sample_count": 1, + "positive_count": 1, + "negative_count": 0, + "positive_rate": 1.0, + }, + } + + +def test_build_dataset_report_inherits_duplicate_id_error(tmp_path: Path) -> None: + dataset = tmp_path / "duplicate.jsonl" + _write_jsonl( + dataset, + [ + {"id": "q1", "query": "q", "answer": "a", "label": 1}, + {"id": "q1", "query": "q", "answer": "a", "label": 0}, + ], + ) + + with pytest.raises(ConfidenceDatasetError, match="duplicate id"): + build_dataset_report(dataset, "query_answerability_confidence") + + +def test_report_cli_prints_pretty_json(tmp_path: Path, capsys: Any) -> None: + script = _load_report_script() + dataset = tmp_path / "dataset.jsonl" + _write_jsonl( + dataset, + [{"id": "q1", "query": "q", "answer": "a", "label": 1, "source": "fixture"}], + ) + + status = script.main( + [ + "--task", + "query_answerability_confidence", + "--dataset", + str(dataset), + ] + ) + + assert status == 0 + output = capsys.readouterr().out + assert '\n "sample_count": 1' in output + assert json.loads(output)["sources"]["fixture"]["positive_rate"] == 1.0 diff --git a/tests/test_generate_confidence_dataset_script.py b/tests/test_generate_confidence_dataset_script.py new file mode 100644 index 0000000..21b1bb0 --- /dev/null +++ b/tests/test_generate_confidence_dataset_script.py @@ -0,0 +1,237 @@ +from __future__ import annotations + +import importlib.util +import json +import subprocess +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import pytest + +SCRIPT_PATH = Path("scripts/generate_confidence_dataset.py") + + +@dataclass(frozen=True) +class FakeGenerationResult: + output_path: Path + input_count: int = 2 + generated_count: int = 2 + skipped_count: int = 0 + positive_count: int = 1 + negative_count: int = 1 + + +class FakeLMStudioProvider: + instances: list[FakeLMStudioProvider] = [] + + def __init__( + self, + *, + base_url: str | None, + model: str | None, + api_key: str | None, + max_tokens: int, + timeout: float | None, + ) -> None: + self.base_url = base_url + self.model = model + self.api_key = api_key + self.max_tokens = max_tokens + self.timeout = timeout + self.instances.append(self) + + def complete(self, request: object) -> object: + raise AssertionError("live provider must not be called in CLI tests") + + +def _load_script() -> Any: + spec = importlib.util.spec_from_file_location( + "generate_confidence_dataset", + SCRIPT_PATH, + ) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_generate_confidence_dataset_help_exits_successfully() -> None: + result = subprocess.run( + ["uv", "run", "python", str(SCRIPT_PATH), "--help"], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0 + assert "--task" in result.stdout + + +def test_query_answerability_cli_builds_lmstudio_provider_and_config( + monkeypatch: Any, + tmp_path: Path, + capsys: Any, +) -> None: + script = _load_script() + calls: list[Any] = [] + + def fake_generate(config: Any) -> FakeGenerationResult: + calls.append(config) + return FakeGenerationResult(output_path=Path(config.output_path)) + + monkeypatch.setattr(script, "LMStudioModelProvider", FakeLMStudioProvider) + monkeypatch.setattr( + script, + "generate_query_answerability_confidence_dataset", + fake_generate, + ) + + status = script.main( + [ + "--task", + "query_answerability_confidence", + "--provider", + "lmstudio", + "--lmstudio-base-url", + "http://localhost:1234/v1", + "--lmstudio-model", + "local-model", + "--lmstudio-api-key", + "key", + "--timeout", + "15", + "--input", + str(tmp_path / "raw.jsonl"), + "--output", + str(tmp_path / "canonical.jsonl"), + "--overwrite", + "--max-items", + "3", + "--source", + "unit", + ] + ) + + assert status == 0 + provider = FakeLMStudioProvider.instances[-1] + assert provider.base_url == "http://localhost:1234/v1" + assert provider.model == "local-model" + assert provider.api_key == "key" + assert provider.max_tokens == 128 + assert provider.timeout == 15 + config = calls[0] + assert config.overwrite is True + assert config.resume is False + assert config.max_items == 3 + assert config.source == "unit" + summary = json.loads(capsys.readouterr().out) + assert summary["output_path"] == str(tmp_path / "canonical.jsonl") + assert summary["generated_count"] == 2 + + +def test_query_context_cli_passes_max_context_chars_and_resume( + monkeypatch: Any, + tmp_path: Path, + capsys: Any, +) -> None: + script = _load_script() + calls: list[Any] = [] + + def fake_generate(config: Any) -> FakeGenerationResult: + calls.append(config) + return FakeGenerationResult(output_path=Path(config.output_path)) + + monkeypatch.setattr(script, "LMStudioModelProvider", FakeLMStudioProvider) + monkeypatch.setattr( + script, + "generate_query_context_answerability_confidence_dataset", + fake_generate, + ) + + status = script.main( + [ + "--task", + "query_context_answerability_confidence", + "--provider", + "lmstudio", + "--lmstudio-model", + "local-model", + "--lmstudio-max-tokens", + "64", + "--input", + str(tmp_path / "raw.jsonl"), + "--output", + str(tmp_path / "canonical.jsonl"), + "--resume", + "--max-context-chars", + "1234", + ] + ) + + assert status == 0 + assert FakeLMStudioProvider.instances[-1].max_tokens == 64 + config = calls[0] + assert config.resume is True + assert config.max_context_chars == 1234 + assert json.loads(capsys.readouterr().out)["output_path"] == str( + tmp_path / "canonical.jsonl" + ) + + +def test_query_context_cli_defaults_max_context_chars( + monkeypatch: Any, + tmp_path: Path, +) -> None: + script = _load_script() + calls: list[Any] = [] + + def fake_generate(config: Any) -> FakeGenerationResult: + calls.append(config) + return FakeGenerationResult(output_path=Path(config.output_path)) + + monkeypatch.setattr(script, "LMStudioModelProvider", FakeLMStudioProvider) + monkeypatch.setattr( + script, + "generate_query_context_answerability_confidence_dataset", + fake_generate, + ) + + status = script.main( + [ + "--task", + "query_context_answerability_confidence", + "--provider", + "lmstudio", + "--input", + str(tmp_path / "raw.jsonl"), + "--output", + str(tmp_path / "canonical.jsonl"), + ] + ) + + assert status == 0 + assert calls[0].max_context_chars == 8000 + + +def test_query_answerability_cli_rejects_max_context_chars(tmp_path: Path) -> None: + script = _load_script() + + with pytest.raises(SystemExit) as exc_info: + script._parse_args( + [ + "--task", + "query_answerability_confidence", + "--provider", + "lmstudio", + "--input", + str(tmp_path / "raw.jsonl"), + "--output", + str(tmp_path / "canonical.jsonl"), + "--max-context-chars", + "1234", + ] + ) + + assert exc_info.value.code != 0 diff --git a/tests/test_lmstudio_provider.py b/tests/test_lmstudio_provider.py new file mode 100644 index 0000000..3f35cb3 --- /dev/null +++ b/tests/test_lmstudio_provider.py @@ -0,0 +1,327 @@ +from __future__ import annotations + +import importlib +from dataclasses import dataclass +from typing import Any, cast + +import pytest + +from ranksmith.errors import RerankInputError, RerankProviderError +from ranksmith.model import ModelMessage, ModelRequest +from ranksmith.types import RerankUsage + + +@dataclass +class FakeUsage: + prompt_tokens: int + completion_tokens: int + total_tokens: int + + +@dataclass +class FakeMessage: + content: str | None + + +@dataclass +class FakeChoice: + message: FakeMessage + + +@dataclass +class FakeResponse: + choices: list[FakeChoice] + usage: FakeUsage | None = None + + +class FakeCompletions: + def __init__(self, response: object | None = None, error: Exception | None = None): + self.response = response + self.error = error + self.calls: list[dict[str, Any]] = [] + + def create(self, **kwargs: Any) -> object: + self.calls.append(kwargs) + if self.error is not None: + raise self.error + return self.response + + +class FakeClient: + def __init__(self, completions: FakeCompletions): + self.chat = type( + "FakeChat", + (), + {"completions": completions}, + )() + + +class RecordingOpenAI(FakeClient): + instances: list[RecordingOpenAI] = [] + + def __init__(self, *, api_key: str, base_url: str, timeout: float | None): + self.api_key = api_key + self.base_url = base_url + self.timeout = timeout + self.completions = FakeCompletions( + FakeResponse(choices=[FakeChoice(FakeMessage('{"ranking": [1]}'))]) + ) + super().__init__(self.completions) + self.instances.append(self) + + +def _request(response_format: str = "json_object") -> ModelRequest: + return ModelRequest( + messages=[ + ModelMessage(role="system", content="system"), + ModelMessage(role="user", content="user"), + ], + response_format=cast(Any, response_format), + temperature=0.2, + ) + + +def test_lmstudio_provider_export_is_submodule_only() -> None: + integrations = importlib.import_module("ranksmith.integrations") + root = importlib.import_module("ranksmith") + + assert integrations.LMStudioModelProvider is not None + assert not hasattr(root, "LMStudioModelProvider") + + +def test_lmstudio_provider_converts_json_object_to_json_schema() -> None: + from ranksmith.integrations import LMStudioModelProvider + + completions = FakeCompletions( + FakeResponse( + choices=[FakeChoice(FakeMessage('{"ranking": [1]}'))], + usage=FakeUsage(prompt_tokens=3, completion_tokens=4, total_tokens=7), + ) + ) + provider = LMStudioModelProvider( + model="local-model", + client=FakeClient(completions), + ) + + response = provider.complete(_request()) + + assert response.content == '{"ranking": [1]}' + assert response.usage == RerankUsage( + prompt_tokens=3, + completion_tokens=4, + total_tokens=7, + ) + assert completions.calls == [ + { + "model": "local-model", + "messages": [ + {"role": "system", "content": "system"}, + {"role": "user", "content": "user"}, + ], + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "ranksmith_json_response", + "schema": {"type": "object"}, + }, + }, + "temperature": 0.2, + "max_tokens": 128, + "reasoning_effort": "none", + } + ] + + +def test_lmstudio_provider_passes_custom_max_tokens() -> None: + from ranksmith.integrations import LMStudioModelProvider + + completions = FakeCompletions( + FakeResponse(choices=[FakeChoice(FakeMessage('{"ranking": [1]}'))]) + ) + provider = LMStudioModelProvider( + model="local-model", + max_tokens=64, + client=FakeClient(completions), + ) + + provider.complete(_request()) + + assert completions.calls[0]["max_tokens"] == 64 + + +def test_lmstudio_provider_uses_lmstudio_model_env_fallback( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from ranksmith.integrations import LMStudioModelProvider + + monkeypatch.setenv("LMSTUDIO_MODEL", "env-model") + completions = FakeCompletions( + FakeResponse(choices=[FakeChoice(FakeMessage('{"ranking": [1]}'))]) + ) + provider = LMStudioModelProvider(client=FakeClient(completions)) + + provider.complete(_request()) + + assert completions.calls[0]["model"] == "env-model" + + +def test_lmstudio_provider_constructs_client_from_env_fallbacks( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import ranksmith.integrations.lmstudio_provider as lmstudio_provider + from ranksmith.integrations import LMStudioModelProvider + + RecordingOpenAI.instances = [] + monkeypatch.setattr(lmstudio_provider, "OpenAI", RecordingOpenAI) + monkeypatch.setenv("LMSTUDIO_BASE_URL", "http://localhost:4321/v1") + monkeypatch.setenv("LMSTUDIO_API_KEY", "env-key") + monkeypatch.setenv("LMSTUDIO_MODEL", "env-model") + + provider = LMStudioModelProvider(timeout=2.5) + provider.complete(_request()) + + assert len(RecordingOpenAI.instances) == 1 + constructed = RecordingOpenAI.instances[0] + assert constructed.base_url == "http://localhost:4321/v1" + assert constructed.api_key == "env-key" + assert constructed.timeout == 2.5 + assert constructed.completions.calls[0]["model"] == "env-model" + + +def test_lmstudio_provider_constructs_client_from_default_fallbacks( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import ranksmith.integrations.lmstudio_provider as lmstudio_provider + from ranksmith.integrations import LMStudioModelProvider + + RecordingOpenAI.instances = [] + monkeypatch.setattr(lmstudio_provider, "OpenAI", RecordingOpenAI) + monkeypatch.setenv("LMSTUDIO_MODEL", "env-model") + monkeypatch.delenv("LMSTUDIO_BASE_URL", raising=False) + monkeypatch.delenv("LMSTUDIO_API_KEY", raising=False) + + provider = LMStudioModelProvider() + + assert len(RecordingOpenAI.instances) == 1 + constructed = RecordingOpenAI.instances[0] + assert constructed.base_url == "http://localhost:1234/v1" + assert constructed.api_key == "lm-studio" + assert provider.model == "env-model" + assert provider.base_url == "http://localhost:1234/v1" + assert provider.api_key_configured is True + assert provider.max_tokens == 128 + + +def test_lmstudio_provider_missing_model_raises( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from ranksmith.integrations import LMStudioModelProvider + + monkeypatch.delenv("LMSTUDIO_MODEL", raising=False) + + with pytest.raises(RerankInputError, match="LMSTUDIO_MODEL is required"): + LMStudioModelProvider(model=" ") + + +def test_lmstudio_provider_rejects_blank_explicit_base_url() -> None: + from ranksmith.integrations import LMStudioModelProvider + + with pytest.raises(RerankInputError, match="base_url"): + LMStudioModelProvider(model="local-model", base_url=" ") + + +def test_lmstudio_provider_rejects_blank_explicit_api_key() -> None: + from ranksmith.integrations import LMStudioModelProvider + + with pytest.raises(RerankInputError, match="api_key"): + LMStudioModelProvider(model="local-model", api_key=" ") + + +@pytest.mark.parametrize( + ("env_name", "match"), + [ + ("LMSTUDIO_BASE_URL", "LMSTUDIO_BASE_URL"), + ("LMSTUDIO_API_KEY", "LMSTUDIO_API_KEY"), + ], +) +def test_lmstudio_provider_rejects_blank_env_runtime_options( + monkeypatch: pytest.MonkeyPatch, + env_name: str, + match: str, +) -> None: + from ranksmith.integrations import LMStudioModelProvider + + monkeypatch.setenv(env_name, " ") + + with pytest.raises(RerankInputError, match=match): + LMStudioModelProvider(model="local-model") + + +@pytest.mark.parametrize("max_tokens", [0, -1]) +def test_lmstudio_provider_rejects_non_positive_max_tokens(max_tokens: int) -> None: + from ranksmith.integrations import LMStudioModelProvider + + with pytest.raises(RerankInputError, match="max_tokens"): + LMStudioModelProvider( + model="local-model", + max_tokens=max_tokens, + client=FakeClient(FakeCompletions()), + ) + + +def test_lmstudio_provider_wraps_client_errors() -> None: + from ranksmith.integrations import LMStudioModelProvider + + provider = LMStudioModelProvider( + model="local-model", + client=FakeClient(FakeCompletions(error=RuntimeError("boom"))), + ) + + with pytest.raises(RerankProviderError, match="boom"): + provider.complete(_request()) + + +def test_lmstudio_provider_wraps_empty_client_error_with_context() -> None: + from ranksmith.integrations import LMStudioModelProvider + + provider = LMStudioModelProvider( + model="local-model", + client=FakeClient(FakeCompletions(error=RuntimeError())), + ) + + with pytest.raises(RerankProviderError, match="LM Studio request failed"): + provider.complete(_request()) + + +def test_lmstudio_provider_rejects_unexpected_response_format() -> None: + from ranksmith.integrations import LMStudioModelProvider + + provider = LMStudioModelProvider( + model="local-model", + client=FakeClient(FakeCompletions()), + ) + + with pytest.raises(RerankProviderError, match="response_format"): + provider.complete(_request("text")) + + +@pytest.mark.parametrize( + "response", + [ + FakeResponse(choices=[]), + FakeResponse(choices=[FakeChoice(FakeMessage(None))]), + FakeResponse(choices=[FakeChoice(FakeMessage(""))]), + ], +) +def test_lmstudio_provider_rejects_invalid_or_empty_content( + response: FakeResponse, +) -> None: + from ranksmith.integrations import LMStudioModelProvider + + provider = LMStudioModelProvider( + model="local-model", + client=FakeClient(FakeCompletions(response)), + ) + + with pytest.raises(RerankProviderError): + provider.complete(_request()) diff --git a/tests/test_provider_answer_generator.py b/tests/test_provider_answer_generator.py new file mode 100644 index 0000000..41783d0 --- /dev/null +++ b/tests/test_provider_answer_generator.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +import importlib +from dataclasses import dataclass + +import pytest + +from ranksmith.errors import RerankParseError, RerankProviderError +from ranksmith.model import ModelRequest, ModelResponse + + +@dataclass +class FakeProvider: + responses: list[str] + requests: list[ModelRequest] + + def complete(self, request: ModelRequest) -> ModelResponse: + self.requests.append(request) + return ModelResponse(content=self.responses.pop(0)) + + +def test_provider_answer_generator_export_is_submodule_only() -> None: + integrations = importlib.import_module("ranksmith.integrations") + root = importlib.import_module("ranksmith") + + assert integrations.ProviderAnswerGenerator is not None + assert not hasattr(root, "ProviderAnswerGenerator") + + +def test_provider_answer_generator_parses_query_answer_json() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + provider = FakeProvider(responses=['{"answer": "Paris"}'], requests=[]) + generator = ProviderAnswerGenerator(provider=provider) + + assert generator.answer_query("capital of france?") == "Paris" + assert provider.requests[0].response_format == "json_object" + assert provider.requests[0].temperature == 0 + assert provider.requests[0].messages[0].role == "system" + assert provider.requests[0].messages[1].role == "user" + assert "capital of france?" in provider.requests[0].messages[1].content + assert "Answer from your parametric knowledge" in ( + provider.requests[0].messages[1].content + ) + assert "__NO_ANSWER__" in provider.requests[0].messages[1].content + assert "best concise answer" not in provider.requests[0].messages[0].content + + +def test_provider_answer_generator_parses_context_answer_json() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + provider = FakeProvider(responses=['{"answer": "Nancy Travis"}'], requests=[]) + generator = ProviderAnswerGenerator(provider=provider) + + assert ( + generator.answer_with_context( + "who played karen?", + "Nancy Travis played Karen.", + ) + == "Nancy Travis" + ) + assert "Nancy Travis played Karen." in provider.requests[0].messages[1].content + assert "Use only the context" in provider.requests[0].messages[1].content + assert "__NO_ANSWER__" in provider.requests[0].messages[1].content + + +def test_provider_answer_generator_uses_configured_no_answer_value() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + provider = FakeProvider(responses=['{"answer": "UNKNOWN"}'], requests=[]) + generator = ProviderAnswerGenerator(provider=provider, no_answer_value="UNKNOWN") + + assert generator.answer_query("query") == "UNKNOWN" + assert '{"answer":"UNKNOWN"}' in provider.requests[0].messages[1].content + + +def test_provider_answer_generator_rejects_empty_no_answer_value() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + with pytest.raises(ValueError, match="no_answer_value"): + ProviderAnswerGenerator( + provider=FakeProvider(responses=[], requests=[]), + no_answer_value=" ", + ) + + +@pytest.mark.parametrize( + "content", + [ + "not json", + "[]", + "{}", + '{"answer": ""}', + '{"answer": " "}', + '{"answer": 123}', + ], +) +def test_provider_answer_generator_rejects_invalid_answer_json(content: str) -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + generator = ProviderAnswerGenerator( + provider=FakeProvider(responses=[content], requests=[]), + ) + + with pytest.raises(RerankParseError): + generator.answer_query("query") + + +def test_provider_answer_generator_preserves_provider_error() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + class FailingProvider: + def complete(self, request: ModelRequest) -> ModelResponse: + del request + raise RerankProviderError("provider failed") + + generator = ProviderAnswerGenerator(provider=FailingProvider()) + + with pytest.raises(RerankProviderError, match="provider failed"): + generator.answer_query("query") + + +def test_provider_answer_generator_wraps_unexpected_provider_error() -> None: + from ranksmith.integrations import ProviderAnswerGenerator + + class FailingProvider: + def complete(self, request: ModelRequest) -> ModelResponse: + del request + raise RuntimeError("boom") + + generator = ProviderAnswerGenerator(provider=FailingProvider()) + + with pytest.raises(RerankProviderError, match="boom"): + generator.answer_query("query") diff --git a/tests/test_train_confidence_scorer_script.py b/tests/test_train_confidence_scorer_script.py new file mode 100644 index 0000000..1b836a3 --- /dev/null +++ b/tests/test_train_confidence_scorer_script.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import importlib.util +import json +import subprocess +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +SCRIPT_PATH = Path("scripts/train_confidence_scorer.py") + + +@dataclass(frozen=True) +class FakeTrainingResult: + output_dir: Path + export_path: Path + report_path: Path + metadata_path: Path + + +def _load_script() -> Any: + spec = importlib.util.spec_from_file_location( + "train_confidence_scorer", + SCRIPT_PATH, + ) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_train_confidence_scorer_help_exits_successfully() -> None: + result = subprocess.run( + ["uv", "run", "python", str(SCRIPT_PATH), "--help"], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0 + assert "--task" in result.stdout + + +def test_training_cli_builds_config_and_prints_result_paths( + monkeypatch: Any, + tmp_path: Path, + capsys: Any, +) -> None: + script = _load_script() + calls: list[Any] = [] + + def fake_train(config: Any) -> FakeTrainingResult: + calls.append(config) + return FakeTrainingResult( + output_dir=Path(config.output_dir), + export_path=Path(config.export_path), + report_path=Path(config.output_dir) / "report.json", + metadata_path=Path(config.output_dir) / "metadata.json", + ) + + monkeypatch.setattr(script, "train_confidence_scorer", fake_train) + + status = script.main( + [ + "--task", + "query_context_answerability_confidence", + "--dataset", + str(tmp_path / "dataset.jsonl"), + "--output-dir", + str(tmp_path / "training"), + "--export-path", + str(tmp_path / "artifact.joblib"), + "--encoder-name", + "encoder", + "--encoder-revision", + "rev1", + "--tokenizer-name", + "tokenizer", + "--tokenizer-revision", + "rev2", + "--cache-dir", + str(tmp_path / "cache"), + "--local-files-only", + "--max-length", + "384", + "--allow-truncation", + "--seed", + "7", + "--train-ratio", + "0.6", + "--valid-ratio", + "0.2", + "--test-ratio", + "0.2", + "--calibration-method", + "sigmoid", + ] + ) + + assert status == 0 + config = calls[0] + assert config.task_type == "query_context_answerability_confidence" + assert config.dataset_path == tmp_path / "dataset.jsonl" + assert config.output_dir == tmp_path / "training" + assert config.export_path == tmp_path / "artifact.joblib" + assert config.encoder_name == "encoder" + assert config.encoder_revision == "rev1" + assert config.tokenizer_name == "tokenizer" + assert config.tokenizer_revision == "rev2" + assert config.cache_dir == str(tmp_path / "cache") + assert config.local_files_only is True + assert config.max_length == 384 + assert config.allow_truncation is True + assert config.seed == 7 + assert config.train_ratio == 0.6 + assert config.valid_ratio == 0.2 + assert config.test_ratio == 0.2 + assert config.calibration_method == "sigmoid" + summary = json.loads(capsys.readouterr().out) + assert summary == { + "output_dir": str(tmp_path / "training"), + "export_path": str(tmp_path / "artifact.joblib"), + "report_path": str(tmp_path / "training" / "report.json"), + "metadata_path": str(tmp_path / "training" / "metadata.json"), + }