Spaces:
Sleeping
Sleeping
Delete MEXAR_Phase2_Dataset_Collection.md
Browse files
MEXAR_Phase2_Dataset_Collection.md
DELETED
|
@@ -1,291 +0,0 @@
|
|
| 1 |
-
# MEXAR Phase 2 — Real Datasets at Reduced, Disclosed Scale
|
| 2 |
-
|
| 3 |
-
**Purpose of this document:** hand this to an AI coding agent (Antigravity or similar) working directly in the `Mexar` repository (`github.com/devrajsinh2012/Mexar`). This is Phase 2 only: it brings in real documents and real queries so the numbers in the paper are reproducible. Assumes Phase 1 (architecture fixes: TF-IDF/NER guardrail, HNSW index, DeBERTa faithfulness scorer) is already done and verified.
|
| 4 |
-
|
| 5 |
-
**Presentation date:** July 30-31, 2026. Target for this phase: days 4-11 (about a week).
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## 2.0 Scope decision (already agreed with Dev)
|
| 10 |
-
|
| 11 |
-
Target **500-600 total documents** across the three domains for the presentation, not the 5,226 stated in the current paper draft. This is a resource-constrained pilot, and the paper's dataset section (IV-A) gets rewritten to say so honestly. Query sets are also reduced and single-annotator (Dev/Om/Jay), not the 900+600 two-expert-annotated set currently described. This is a normal, defensible thing to disclose in a paper, and far safer than presenting numbers nobody can reproduce live.
|
| 12 |
-
|
| 13 |
-
**Target split:**
|
| 14 |
-
|
| 15 |
-
| Domain | Target docs | Source |
|
| 16 |
-
|---|---|---|
|
| 17 |
-
| Medical | ~220 | PubMed Central OA subset |
|
| 18 |
-
| Legal | ~150 | CourtListener (Free Law Project) |
|
| 19 |
-
| Financial | ~130 | SEC EDGAR full-text search |
|
| 20 |
-
| **Total** | **~500** | |
|
| 21 |
-
|
| 22 |
-
**Important for the agent:** these fetch scripts hit external APIs (PubMed E-utilities, CourtListener, SEC EDGAR) that are outside a typical restricted sandbox's allowed network domains. Run these scripts in Dev's own local dev environment or a CI runner with normal internet access, not inside a network-restricted container.
|
| 23 |
-
|
| 24 |
-
---
|
| 25 |
-
|
| 26 |
-
## 2.1 Medical corpus — PubMed Central OA
|
| 27 |
-
|
| 28 |
-
**New script:** `backend/scripts/fetch_pubmed.py`
|
| 29 |
-
|
| 30 |
-
- Use the NCBI E-utilities API (`esearch` + `efetch`), no key required for low-volume use, but request one from NCBI if fetching more than 3 requests/sec.
|
| 31 |
-
- Query terms per subdomain, run three separate searches and cap results:
|
| 32 |
-
- `"cardiology treatment guideline"[Title/Abstract] AND "open access"[filter]`
|
| 33 |
-
- `"oncology treatment protocol"[Title/Abstract] AND "open access"[filter]`
|
| 34 |
-
- `"internal medicine diagnostic criteria"[Title/Abstract] AND "open access"[filter]`
|
| 35 |
-
- Pull ~75 docs per subdomain (≈220 total).
|
| 36 |
-
- Save each as plain text (abstract + body if available via the OA subset bulk XML) into `test_data/medical_real/`, one file per document, named by PMCID, e.g. `PMC1234567.txt`.
|
| 37 |
-
- Respect NCBI's rate limits (3 req/sec without an API key, 10/sec with one) and identify yourself via the `email` parameter on every request, per NCBI's usage policy.
|
| 38 |
-
- Log a manifest file `test_data/medical_real/manifest.json` mapping PMCID → title → subdomain → source URL, this becomes supporting material for the paper's dataset section and makes the corpus auditable.
|
| 39 |
-
|
| 40 |
-
**Skeleton:**
|
| 41 |
-
```python
|
| 42 |
-
"""
|
| 43 |
-
MEXAR - PubMed Central OA fetcher for the medical corpus.
|
| 44 |
-
Run this locally (not in a network-restricted sandbox).
|
| 45 |
-
"""
|
| 46 |
-
import requests
|
| 47 |
-
import time
|
| 48 |
-
import json
|
| 49 |
-
import os
|
| 50 |
-
|
| 51 |
-
NCBI_EMAIL = "your_email@example.com" # required by NCBI usage policy
|
| 52 |
-
OUTPUT_DIR = "test_data/medical_real"
|
| 53 |
-
SEARCH_TERMS = {
|
| 54 |
-
"cardiology": '"cardiology treatment guideline"[Title/Abstract] AND "open access"[filter]',
|
| 55 |
-
"oncology": '"oncology treatment protocol"[Title/Abstract] AND "open access"[filter]',
|
| 56 |
-
"internal_medicine": '"internal medicine diagnostic criteria"[Title/Abstract] AND "open access"[filter]',
|
| 57 |
-
}
|
| 58 |
-
DOCS_PER_SUBDOMAIN = 75
|
| 59 |
-
|
| 60 |
-
def esearch(term, retmax):
|
| 61 |
-
url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
|
| 62 |
-
params = {"db": "pmc", "term": term, "retmax": retmax, "retmode": "json", "email": NCBI_EMAIL}
|
| 63 |
-
r = requests.get(url, params=params)
|
| 64 |
-
r.raise_for_status()
|
| 65 |
-
return r.json()["esearchresult"]["idlist"]
|
| 66 |
-
|
| 67 |
-
def efetch_fulltext(pmc_id):
|
| 68 |
-
url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"
|
| 69 |
-
params = {"db": "pmc", "id": pmc_id, "rettype": "full", "retmode": "xml", "email": NCBI_EMAIL}
|
| 70 |
-
r = requests.get(url, params=params)
|
| 71 |
-
r.raise_for_status()
|
| 72 |
-
return r.text # parse out body text with an XML parser before saving as plain text
|
| 73 |
-
|
| 74 |
-
def main():
|
| 75 |
-
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 76 |
-
manifest = []
|
| 77 |
-
for subdomain, term in SEARCH_TERMS.items():
|
| 78 |
-
ids = esearch(term, DOCS_PER_SUBDOMAIN)
|
| 79 |
-
for pmc_id in ids:
|
| 80 |
-
time.sleep(0.34) # stay under 3 req/sec
|
| 81 |
-
try:
|
| 82 |
-
xml_text = efetch_fulltext(pmc_id)
|
| 83 |
-
# TODO: parse XML body text out with e.g. lxml, strip tags, save plain text
|
| 84 |
-
plain_text = xml_text # placeholder, replace with real extraction
|
| 85 |
-
out_path = os.path.join(OUTPUT_DIR, f"PMC{pmc_id}.txt")
|
| 86 |
-
with open(out_path, "w", encoding="utf-8") as f:
|
| 87 |
-
f.write(plain_text)
|
| 88 |
-
manifest.append({"pmc_id": pmc_id, "subdomain": subdomain, "path": out_path})
|
| 89 |
-
except Exception as e:
|
| 90 |
-
print(f"Failed to fetch PMC{pmc_id}: {e}")
|
| 91 |
-
with open(os.path.join(OUTPUT_DIR, "manifest.json"), "w") as f:
|
| 92 |
-
json.dump(manifest, f, indent=2)
|
| 93 |
-
print(f"Fetched {len(manifest)} medical documents.")
|
| 94 |
-
|
| 95 |
-
if __name__ == "__main__":
|
| 96 |
-
main()
|
| 97 |
-
```
|
| 98 |
-
The agent should replace the placeholder XML-to-text extraction with a real parser (e.g. `lxml` or `BeautifulSoup`) that pulls the `<body>` text out of the PMC XML format and strips markup, don't save raw XML as the "plain text" corpus.
|
| 99 |
-
|
| 100 |
-
---
|
| 101 |
-
|
| 102 |
-
## 2.2 Legal corpus — CourtListener
|
| 103 |
-
|
| 104 |
-
**New script:** `backend/scripts/fetch_courtlistener.py`
|
| 105 |
-
|
| 106 |
-
- Use the CourtListener REST API (`https://www.courtlistener.com/api/rest/v4/`), requires a free account and API token (set as `COURTLISTENER_TOKEN` env var).
|
| 107 |
-
- Pull opinions/case summaries across contract law, IP, and corporate governance search terms, plus a handful of statutory/regulatory text pages if accessible via their `opinions` and `search` endpoints.
|
| 108 |
-
- Target ~50 docs per subdomain (≈150 total).
|
| 109 |
-
- Save the opinion text (or summary if full text is very long, truncate to a reasonable chunking size, e.g. first 3000 words) into `test_data/legal_real/`, one file per case, named by CourtListener opinion ID.
|
| 110 |
-
- Same manifest pattern as 2.1: `test_data/legal_real/manifest.json` with case name, subdomain, court, date, source URL.
|
| 111 |
-
|
| 112 |
-
**Skeleton:**
|
| 113 |
-
```python
|
| 114 |
-
"""
|
| 115 |
-
MEXAR - CourtListener fetcher for the legal corpus.
|
| 116 |
-
Requires COURTLISTENER_TOKEN env var. Run locally.
|
| 117 |
-
"""
|
| 118 |
-
import requests
|
| 119 |
-
import os
|
| 120 |
-
import json
|
| 121 |
-
|
| 122 |
-
TOKEN = os.environ["COURTLISTENER_TOKEN"]
|
| 123 |
-
HEADERS = {"Authorization": f"Token {TOKEN}"}
|
| 124 |
-
BASE_URL = "https://www.courtlistener.com/api/rest/v4"
|
| 125 |
-
OUTPUT_DIR = "test_data/legal_real"
|
| 126 |
-
SEARCH_TERMS = {
|
| 127 |
-
"contract_law": "breach of contract damages",
|
| 128 |
-
"intellectual_property": "patent infringement trademark",
|
| 129 |
-
"corporate_governance": "fiduciary duty shareholder derivative",
|
| 130 |
-
}
|
| 131 |
-
DOCS_PER_SUBDOMAIN = 50
|
| 132 |
-
|
| 133 |
-
def search_opinions(query, count):
|
| 134 |
-
url = f"{BASE_URL}/search/"
|
| 135 |
-
params = {"q": query, "type": "o", "order_by": "score desc"}
|
| 136 |
-
results = []
|
| 137 |
-
while len(results) < count:
|
| 138 |
-
r = requests.get(url, headers=HEADERS, params=params)
|
| 139 |
-
r.raise_for_status()
|
| 140 |
-
data = r.json()
|
| 141 |
-
results.extend(data["results"])
|
| 142 |
-
if not data.get("next"):
|
| 143 |
-
break
|
| 144 |
-
url = data["next"]
|
| 145 |
-
params = {}
|
| 146 |
-
return results[:count]
|
| 147 |
-
|
| 148 |
-
def main():
|
| 149 |
-
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 150 |
-
manifest = []
|
| 151 |
-
for subdomain, term in SEARCH_TERMS.items():
|
| 152 |
-
opinions = search_opinions(term, DOCS_PER_SUBDOMAIN)
|
| 153 |
-
for op in opinions:
|
| 154 |
-
text = op.get("plain_text") or op.get("snippet", "")
|
| 155 |
-
if not text:
|
| 156 |
-
continue
|
| 157 |
-
op_id = op.get("id") or op.get("cluster_id")
|
| 158 |
-
out_path = os.path.join(OUTPUT_DIR, f"case_{op_id}.txt")
|
| 159 |
-
with open(out_path, "w", encoding="utf-8") as f:
|
| 160 |
-
f.write(text[:15000]) # cap length
|
| 161 |
-
manifest.append({
|
| 162 |
-
"id": op_id, "subdomain": subdomain,
|
| 163 |
-
"case_name": op.get("caseName", ""), "path": out_path,
|
| 164 |
-
})
|
| 165 |
-
with open(os.path.join(OUTPUT_DIR, "manifest.json"), "w") as f:
|
| 166 |
-
json.dump(manifest, f, indent=2)
|
| 167 |
-
print(f"Fetched {len(manifest)} legal documents.")
|
| 168 |
-
|
| 169 |
-
if __name__ == "__main__":
|
| 170 |
-
main()
|
| 171 |
-
```
|
| 172 |
-
The agent should verify the actual current CourtListener v4 search response schema (field names like `plain_text` vs `snippet` vs a separate opinion-text endpoint may have changed) before trusting this skeleton verbatim, API responses drift over time.
|
| 173 |
-
|
| 174 |
-
---
|
| 175 |
-
|
| 176 |
-
## 2.3 Financial corpus — SEC EDGAR
|
| 177 |
-
|
| 178 |
-
**New script:** `backend/scripts/fetch_secedgar.py`
|
| 179 |
-
|
| 180 |
-
- Use SEC EDGAR's full-text search API (`https://efts.sec.gov/LATEST/search-index`) and the regular filing index to pull excerpts from 10-K filings and select analyst/accounting-standard documents.
|
| 181 |
-
- **Mandatory:** SEC requires a descriptive `User-Agent` header identifying the requester (name + email) on every request, or you get blocked. Format: `"YourName YourEmail@example.com"`.
|
| 182 |
-
- Target ~130 docs, mixing filing sections (MD&A, risk factors) rather than entire 10-Ks, which are too long and would dominate the corpus disproportionately relative to medical/legal document sizes.
|
| 183 |
-
- Save into `test_data/financial_real/`, named by CIK + accession number.
|
| 184 |
-
- Same manifest pattern.
|
| 185 |
-
|
| 186 |
-
**Skeleton:**
|
| 187 |
-
```python
|
| 188 |
-
"""
|
| 189 |
-
MEXAR - SEC EDGAR fetcher for the financial corpus.
|
| 190 |
-
Run locally. Requires a descriptive User-Agent per SEC's fair access policy.
|
| 191 |
-
"""
|
| 192 |
-
import requests
|
| 193 |
-
import os
|
| 194 |
-
import json
|
| 195 |
-
import time
|
| 196 |
-
|
| 197 |
-
USER_AGENT = "Devrajsinh Gohil djgohil2012@gmail.com" # SEC requires this format
|
| 198 |
-
HEADERS = {"User-Agent": USER_AGENT}
|
| 199 |
-
OUTPUT_DIR = "test_data/financial_real"
|
| 200 |
-
SEARCH_TERMS = ["risk factors", "management discussion analysis", "GAAP IFRS accounting standards"]
|
| 201 |
-
DOCS_PER_TERM = 45 # ~130 total across 3 terms
|
| 202 |
-
|
| 203 |
-
def full_text_search(query, count):
|
| 204 |
-
url = "https://efts.sec.gov/LATEST/search-index?q=%22{}%22&forms=10-K".format(query.replace(" ", "+"))
|
| 205 |
-
r = requests.get(url, headers=HEADERS)
|
| 206 |
-
r.raise_for_status()
|
| 207 |
-
hits = r.json().get("hits", {}).get("hits", [])
|
| 208 |
-
return hits[:count]
|
| 209 |
-
|
| 210 |
-
def main():
|
| 211 |
-
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 212 |
-
manifest = []
|
| 213 |
-
for term in SEARCH_TERMS:
|
| 214 |
-
hits = full_text_search(term, DOCS_PER_TERM)
|
| 215 |
-
for hit in hits:
|
| 216 |
-
time.sleep(0.2) # be polite to SEC's rate limits
|
| 217 |
-
source = hit.get("_source", {})
|
| 218 |
-
cik = source.get("ciks", ["unknown"])[0]
|
| 219 |
-
accession = source.get("adsh", "unknown")
|
| 220 |
-
# TODO: fetch actual filing document text via the filing index URL,
|
| 221 |
-
# this skeleton only captures search-hit metadata, not full text.
|
| 222 |
-
excerpt = source.get("display_names", [""])[0]
|
| 223 |
-
out_path = os.path.join(OUTPUT_DIR, f"{cik}_{accession}.txt")
|
| 224 |
-
with open(out_path, "w", encoding="utf-8") as f:
|
| 225 |
-
f.write(excerpt)
|
| 226 |
-
manifest.append({"cik": cik, "accession": accession, "term": term, "path": out_path})
|
| 227 |
-
with open(os.path.join(OUTPUT_DIR, "manifest.json"), "w") as f:
|
| 228 |
-
json.dump(manifest, f, indent=2)
|
| 229 |
-
print(f"Fetched {len(manifest)} financial documents.")
|
| 230 |
-
|
| 231 |
-
if __name__ == "__main__":
|
| 232 |
-
main()
|
| 233 |
-
```
|
| 234 |
-
This one needs the most work from the agent: the full-text search endpoint returns hit metadata, not the actual filing body text. The agent needs to follow through to the actual filing document (via the CIK/accession-number-derived URL under `https://www.sec.gov/Archives/edgar/data/{cik}/{accession}/`) and extract the relevant section text (MD&A, risk factors) from the filing HTML, rather than saving just the search snippet.
|
| 235 |
-
|
| 236 |
-
---
|
| 237 |
-
|
| 238 |
-
## 2.4 Ingestion format
|
| 239 |
-
|
| 240 |
-
Each fetch script should output files in whatever format `backend/modules/data_validator.py`'s parsers already expect, check the `.txt`/`.json` handling there first, don't invent a new schema. Simplest path: one `.txt` file per document, plain text body, filename as a stable ID, so the existing agent-compilation flow (`KnowledgeCompiler.compile()`) can ingest them exactly like a user-uploaded file batch, no special-casing needed in the app itself.
|
| 241 |
-
|
| 242 |
-
---
|
| 243 |
-
|
| 244 |
-
## 2.5 Query set authoring
|
| 245 |
-
|
| 246 |
-
Create `test_data/query_sets/{medical,legal,financial}_queries.json` with this schema:
|
| 247 |
-
```json
|
| 248 |
-
[
|
| 249 |
-
{
|
| 250 |
-
"query": "What is the first-line treatment for stage 2 hypertension?",
|
| 251 |
-
"domain": "medical",
|
| 252 |
-
"is_in_domain": true,
|
| 253 |
-
"expected_source_docs": ["PMC1234567"],
|
| 254 |
-
"annotator": "devrajsinh"
|
| 255 |
-
}
|
| 256 |
-
]
|
| 257 |
-
```
|
| 258 |
-
- ~40-50 in-domain queries per domain (authored by whoever on the team is most comfortable with that domain, split the load across Dev/Om/Jay).
|
| 259 |
-
- ~25-35 out-of-domain queries per domain-pair (medical→legal, legal→financial, etc.) for guardrail testing, reusing the structure already implied by Table IV in the paper, just at a smaller count.
|
| 260 |
-
- Single annotator per query is fine at this scale, note this explicitly as a limitation instead of claiming inter-annotator agreement you didn't actually measure.
|
| 261 |
-
|
| 262 |
-
---
|
| 263 |
-
|
| 264 |
-
## 2.6 Suggested paper text for Section IV-A
|
| 265 |
-
|
| 266 |
-
Drafted now so it's ready when the paper gets updated in Phase 4:
|
| 267 |
-
|
| 268 |
-
> *"Due to resource constraints, this study evaluates on a reduced corpus of approximately 500 documents (220 medical, 150 legal, 130 financial) sourced from PubMed Central Open Access, CourtListener, and SEC EDGAR respectively, with 120-150 in-domain queries and approximately 90 out-of-domain queries authored by the paper's co-authors. We report this as a pilot-scale evaluation and note that a larger, multi-annotator study is needed to confirm these findings generalize at production scale."*
|
| 269 |
-
|
| 270 |
-
Adjust the exact counts once the fetch scripts finish and real numbers are known.
|
| 271 |
-
|
| 272 |
-
---
|
| 273 |
-
|
| 274 |
-
## Summary of new files in Phase 2
|
| 275 |
-
|
| 276 |
-
| File | Purpose |
|
| 277 |
-
|---|---|
|
| 278 |
-
| `backend/scripts/fetch_pubmed.py` | pulls ~220 medical docs |
|
| 279 |
-
| `backend/scripts/fetch_courtlistener.py` | pulls ~150 legal docs |
|
| 280 |
-
| `backend/scripts/fetch_secedgar.py` | pulls ~130 financial docs |
|
| 281 |
-
| `test_data/medical_real/`, `test_data/legal_real/`, `test_data/financial_real/` | fetched corpora + manifests |
|
| 282 |
-
| `test_data/query_sets/*.json` | authored query sets with domain/expected-source labels |
|
| 283 |
-
|
| 284 |
-
---
|
| 285 |
-
|
| 286 |
-
## What Phase 2 does NOT cover (handled later)
|
| 287 |
-
|
| 288 |
-
- Running `baseline_runner.py`, `benchmark_runner.py`, `guardrail_analysis.py`, `statistical_tests.py` against this real data to produce actual paper numbers, that's Phase 3.
|
| 289 |
-
- Rewriting Tables I-V and Figures 2-4 in the paper, and rehearsing the live demo, that's Phase 4.
|
| 290 |
-
|
| 291 |
-
Once Phase 1 (architecture) and Phase 2 (data) are both done, the evaluation scripts already in `backend/evaluation/` should be able to run against real inputs with minimal further changes, since Phase 1 kept their function signatures stable.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|