devrajsinh2012 commited on
Commit
2d35f29
·
verified ·
1 Parent(s): 06f3edb

Delete MEXAR_Phase2_Dataset_Collection.md

Browse files
Files changed (1) hide show
  1. MEXAR_Phase2_Dataset_Collection.md +0 -291
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.