- Collection hierarchy: 20 sub-collections under "Skin Substitutes" (Clinical Evidence, CMS Policy, Enforcement, etc.) - COI/funding enrichment: 2,862 PubMed articles tagged with coi:single-product (2,003), stance:skeptical (598), stance:favorable (510), coi:industry-linked (11) - All 22 expected tag types verified present - Methods documentation: search queries, PRISMA flow, tag schema, collection hierarchy, limitations
This commit is contained in:
167
dev/docs/skin_subs_literature_methods.md
Normal file
167
dev/docs/skin_subs_literature_methods.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Skin Substitutes Literature Review: Methods
|
||||
|
||||
## Search Strategy
|
||||
|
||||
### Databases
|
||||
- **PubMed** (MEDLINE) via NCBI E-utilities (esearch + efetch XML)
|
||||
- **Grey literature**: OIG, CMS, GAO, MedPAC, DOJ, court filings, MAC LCDs, industry
|
||||
|
||||
### Date Range
|
||||
- PubMed: inception through 2026-03-25
|
||||
- Grey literature: 2015-current (enforcement), 2012-current (CMS rules)
|
||||
|
||||
### Search Date
|
||||
2026-03-25
|
||||
|
||||
## PubMed Search Queries
|
||||
|
||||
### Domain 1: Clinical Efficacy
|
||||
```
|
||||
("skin substitutes"[MeSH] OR "biological dressings"[MeSH]
|
||||
OR "tissue scaffolds"[MeSH] OR "bioengineered skin"[tiab]
|
||||
OR "cellular tissue product"[tiab] OR "wound matrix"[tiab]
|
||||
OR "skin substitute"[tiab] OR "skin substitutes"[tiab]
|
||||
OR "Apligraf"[tiab] OR "EpiFix"[tiab] OR "Grafix"[tiab]
|
||||
OR "DermACELL"[tiab] OR "Dermagraft"[tiab])
|
||||
AND ("wound healing"[MeSH] OR "treatment outcome"[MeSH]
|
||||
OR "clinical trial"[pt] OR "randomized controlled trial"[pt]
|
||||
OR "systematic review"[pt] OR "meta-analysis"[pt]
|
||||
OR "efficacy"[tiab] OR "effectiveness"[tiab])
|
||||
```
|
||||
**Result: 7,448 records**
|
||||
|
||||
### Domain 2: Cost-Effectiveness
|
||||
```
|
||||
("skin substitutes"[MeSH] OR "biological dressings"[MeSH]
|
||||
OR "skin substitute"[tiab] OR "skin substitutes"[tiab]
|
||||
OR "cellular tissue product"[tiab])
|
||||
AND ("costs and cost analysis"[MeSH]
|
||||
OR "cost-benefit analysis"[MeSH]
|
||||
OR "health care costs"[MeSH] OR "medicare"[MeSH]
|
||||
OR "reimbursement"[tiab] OR "cost-effectiveness"[tiab]
|
||||
OR "economic"[tiab] OR "spending"[tiab]
|
||||
OR "average sales price"[tiab] OR "ASP"[tiab])
|
||||
```
|
||||
**Result: 123 records**
|
||||
|
||||
### Domain 3: Fraud, Waste, and Abuse
|
||||
```
|
||||
("skin substitutes"[MeSH] OR "biological dressings"[MeSH]
|
||||
OR "skin substitute"[tiab] OR "skin substitutes"[tiab]
|
||||
OR "cellular tissue product"[tiab] OR "wound care"[tiab])
|
||||
AND ("fraud"[MeSH] OR "waste"[tiab] OR "abuse"[tiab]
|
||||
OR "inappropriate"[tiab] OR "overutilization"[tiab]
|
||||
OR "billing"[tiab] OR "enforcement"[tiab]
|
||||
OR "compliance"[tiab] OR "medically unnecessary"[tiab]
|
||||
OR "upcoding"[tiab] OR "kickback"[tiab])
|
||||
```
|
||||
**Result: 358 records**
|
||||
|
||||
## PRISMA Flow
|
||||
|
||||
```
|
||||
Records identified through PubMed:
|
||||
Clinical efficacy: 7,448
|
||||
Cost-effectiveness: 123
|
||||
Fraud/waste/abuse: 358
|
||||
─────────────────────────────
|
||||
TOTAL identified: 7,929
|
||||
|
||||
Duplicates removed: 112
|
||||
Unique articles: 7,817
|
||||
|
||||
By publication type:
|
||||
Meta-analyses: 85
|
||||
Systematic reviews: 1,395
|
||||
RCTs: 455
|
||||
Other (observational,
|
||||
case reports, etc.): 5,882
|
||||
```
|
||||
|
||||
## Grey Literature Sources (20 documents)
|
||||
|
||||
| Source | Count | Description |
|
||||
|-----------|------:|------------------------------------------------------|
|
||||
| OIG | 3 | Sept 2025 payment trends, SAB, audit reports |
|
||||
| CMS | 4 | OPPS/PFS final rules (2024-2026), Benefit Policy |
|
||||
| DOJ | 1 | National healthcare fraud enforcement action |
|
||||
| Court | 3 | Jenson (S.D. Tex.), Gehrke/King (D. Ariz.), Vohra |
|
||||
| GAO | 1 | Part B biologicals spending (GAO-23-105537) |
|
||||
| MedPAC | 2 | June 2024, March 2025 Reports to Congress |
|
||||
| MAC LCD | 4 | Noridian, CGS, First Coast, Palmetto |
|
||||
| Industry | 2 | Alliance position statement, WHS guidelines |
|
||||
|
||||
## Conflict of Interest / Stance Enrichment
|
||||
|
||||
PubMed articles were enriched with automated COI and stance detection:
|
||||
|
||||
| Tag | Count | Method |
|
||||
|-----------------------|------:|-------------------------------------------------|
|
||||
| coi:single-product | 2,003 | Only one brand name mentioned in title/abstract |
|
||||
| stance:skeptical | 598 | Negative outcome language in abstract |
|
||||
| stance:favorable | 510 | Positive outcome language in title |
|
||||
| coi:industry-linked | 11 | Funding/employment patterns in extra metadata |
|
||||
|
||||
**Enrichment coverage:** 2,862 / 7,817 PubMed articles (36.6%)
|
||||
|
||||
## Tag Schema
|
||||
|
||||
All items tagged with `module:skin-subs` plus:
|
||||
|
||||
- **Source tags:** `source:pubmed`, `source:oig`, `source:cms`, `source:court`,
|
||||
`source:doj`, `source:gao`, `source:medpac`, `source:mac-lcd`, `source:industry`
|
||||
- **Type tags:** `type:clinical`, `type:economic`, `type:fraud`, `type:rct`,
|
||||
`type:review`, `type:meta-analysis`, `type:report`, `type:rule`, `type:filing`,
|
||||
`type:lcd`, `type:position`, `type:press-release`, `type:manual`
|
||||
- **Year tags:** `year:YYYY`
|
||||
- **COI tags:** `coi:industry-linked`, `coi:single-product`
|
||||
- **Stance tags:** `stance:skeptical`, `stance:favorable`
|
||||
- **Entity tags:** `entity:oig`, `entity:doj`, `entity:gao`, `entity:medpac`,
|
||||
`entity:noridian`, `entity:cgs`, `entity:first-coast`, `entity:palmetto`
|
||||
- **Case tags:** `case:jenson`, `case:gehrke-king`, `case:vohra`
|
||||
- **Rule tags:** `rule:cy2026-opps`, `rule:cy2025-opps`, `rule:cy2024-pfs`
|
||||
|
||||
## Collection Hierarchy
|
||||
|
||||
```
|
||||
Skin Substitutes/
|
||||
Clinical Evidence/
|
||||
RCTs (455 items)
|
||||
Systematic Reviews (1,395 items)
|
||||
Meta-Analyses (85 items)
|
||||
Observational Studies (5,547 items)
|
||||
CMS Policy/
|
||||
Final Rules (OPPS/PFS) (3 items)
|
||||
Benefit Policy Manuals (1 item)
|
||||
ASP Pricing Files
|
||||
OIG Reports (3 items)
|
||||
GAO & MedPAC (3 items)
|
||||
Enforcement/
|
||||
DOJ Press Releases (1 item)
|
||||
Court Filings (3 items)
|
||||
MAC LCDs (4 items)
|
||||
Market Data
|
||||
Industry & Societies (2 items)
|
||||
Cost-Effectiveness (70 items)
|
||||
Fraud & Abuse Literature (265 items)
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `dev/scripts/search_pubmed_skin_subs.py` | PubMed E-utilities search, XML parsing, bib.sqlite storage |
|
||||
| `dev/scripts/collect_grey_lit_skin_subs.py` | Curated grey literature catalogue, bib.sqlite storage |
|
||||
| `dev/scripts/build_skin_subs_evidence_base.py` | Collection hierarchy, COI enrichment, verification |
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **No full-text screening**: Articles included based on PubMed metadata only; no manual
|
||||
title/abstract screening for relevance (full PRISMA would require human review)
|
||||
2. **COI detection is heuristic**: Based on keyword patterns in abstracts and metadata,
|
||||
not full-text disclosure sections
|
||||
3. **Grey literature is curated, not systematic**: Known key documents captured; no
|
||||
systematic search of OIG/GAO/PACER databases
|
||||
4. **No Cochrane Library**: Only PubMed searched for journal literature
|
||||
5. **No citation network analysis**: Forward/backward snowball not yet performed (#237)
|
||||
6. **No quantitative meta-analysis**: Study data extraction and pooling not yet done (#237)
|
||||
386
dev/scripts/build_skin_subs_evidence_base.py
Normal file
386
dev/scripts/build_skin_subs_evidence_base.py
Normal file
@@ -0,0 +1,386 @@
|
||||
"""Build the Skin Substitutes evidence base in bib.sqlite.
|
||||
|
||||
Creates collection hierarchy, assigns items to sub-collections,
|
||||
enriches PubMed articles with conflict-of-interest and funding
|
||||
analysis, and verifies tag schema coverage.
|
||||
|
||||
Addresses issues #244 (Zotero evidence base) and #237 (partial —
|
||||
COI/funding enrichment of PubMed results).
|
||||
|
||||
Usage:
|
||||
uv run python dev/scripts/build_skin_subs_evidence_base.py
|
||||
uv run python dev/scripts/build_skin_subs_evidence_base.py --dry-run
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from bib.store import Store
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Collection hierarchy for skin-subs research
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SKIN_SUBS_COLLECTIONS = {
|
||||
"Skin Substitutes": {
|
||||
"Clinical Evidence": {
|
||||
"RCTs": {},
|
||||
"Systematic Reviews": {},
|
||||
"Meta-Analyses": {},
|
||||
"Observational Studies": {},
|
||||
},
|
||||
"CMS Policy": {
|
||||
"Final Rules (OPPS/PFS)": {},
|
||||
"Benefit Policy Manuals": {},
|
||||
"ASP Pricing Files": {},
|
||||
},
|
||||
"OIG Reports": {},
|
||||
"GAO & MedPAC": {},
|
||||
"Enforcement": {
|
||||
"DOJ Press Releases": {},
|
||||
"Court Filings": {},
|
||||
},
|
||||
"MAC LCDs": {},
|
||||
"Market Data": {},
|
||||
"Industry & Societies": {},
|
||||
"Cost-Effectiveness": {},
|
||||
"Fraud & Abuse Literature": {},
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Known manufacturer names for COI detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
MANUFACTURERS = [
|
||||
"organogenesis", "mimedx", "smith nephew", "smith & nephew",
|
||||
"integra", "solsys", "amnioexcel", "derma sciences",
|
||||
"healthpoint", "shire", "acelity", "kci", "3m",
|
||||
"molnlycke", "medline", "hollister", "coloplast",
|
||||
"stryker", "zimmer biomet", "wright medical",
|
||||
"solventum", "apria", "anika", "musculoskeletal transplant",
|
||||
"surmodics", "tissue regenix", "nuo therapeutics",
|
||||
"sanara medtech", "kerecis", "human bioprocessing",
|
||||
"alphatec", "biosig technologies",
|
||||
]
|
||||
|
||||
# Brand names that indicate manufacturer-linked studies
|
||||
BRAND_NAMES = [
|
||||
"apligraf", "dermagraft", "epifix", "grafix", "amnioexcel",
|
||||
"dermacell", "oasis", "primatrix", "integra", "graftjacket",
|
||||
"dermapure", "affinity", "biovance", "cytal", "endoform",
|
||||
"kerecis omega3", "novafix", "puraply", "restorigin",
|
||||
"surgicraft", "theraskin", "amnioburn", "clarix",
|
||||
"epicord", "genesis", "grafix core", "grafix prime",
|
||||
"innovamatrix", "nushield", "stravix", "woundex",
|
||||
]
|
||||
|
||||
# Patterns suggesting industry funding
|
||||
FUNDING_PATTERNS = [
|
||||
r"funded by .{0,50}(organogenesis|mimedx|smith|integra|solsys|amnio)",
|
||||
r"grant from .{0,50}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"financial support.{0,50}(organogenesis|mimedx|smith|integra)",
|
||||
r"supported by .{0,50}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"sponsored by .{0,50}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"employee of .{0,50}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"consultant.{0,30}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"speaker.{0,30}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"advisory board.{0,30}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"honorari.{0,30}(organogenesis|mimedx|smith|integra|solsys)",
|
||||
r"conflict.{0,50}interest",
|
||||
r"disclosur.{0,80}(stock|equity|consult|employ|honorar|speaker|grant)",
|
||||
]
|
||||
|
||||
# Patterns suggesting independent/skeptical perspective
|
||||
SKEPTICAL_PATTERNS = [
|
||||
r"no (significant )?difference",
|
||||
r"lack.{0,20}evidence",
|
||||
r"insufficient evidence",
|
||||
r"low.{0,15}quality evidence",
|
||||
r"limited evidence",
|
||||
r"no.{0,15}superiority",
|
||||
r"similar (outcome|result|efficacy|effectiveness)",
|
||||
r"(waste|fraud|abuse|overutiliz|unnecessary|inappropriate)",
|
||||
r"(overspend|excessive.{0,15}cost|cost concern)",
|
||||
r"(marketing|promotional|commercial bias)",
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Collection assignment rules
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def assign_collection(tags: list[str], title: str, abstract: str) -> str:
|
||||
"""Return the most specific sub-collection name for an item."""
|
||||
tag_set = set(tags)
|
||||
|
||||
# Grey literature — by source tag
|
||||
if "source:oig" in tag_set:
|
||||
return "OIG Reports"
|
||||
if "source:gao" in tag_set or "source:medpac" in tag_set:
|
||||
return "GAO & MedPAC"
|
||||
if "source:doj" in tag_set:
|
||||
return "DOJ Press Releases"
|
||||
if "source:court" in tag_set:
|
||||
return "Court Filings"
|
||||
if "source:mac-lcd" in tag_set:
|
||||
return "MAC LCDs"
|
||||
if "source:industry" in tag_set:
|
||||
return "Industry & Societies"
|
||||
if "source:cms" in tag_set:
|
||||
for t in tag_set:
|
||||
if t.startswith("type:rule"):
|
||||
return "Final Rules (OPPS/PFS)"
|
||||
if t.startswith("type:manual"):
|
||||
return "Benefit Policy Manuals"
|
||||
return "CMS Policy"
|
||||
|
||||
# PubMed — by type tag
|
||||
if "type:meta-analysis" in tag_set:
|
||||
return "Meta-Analyses"
|
||||
if "type:review" in tag_set:
|
||||
return "Systematic Reviews"
|
||||
if "type:rct" in tag_set:
|
||||
return "RCTs"
|
||||
if "type:economic" in tag_set:
|
||||
return "Cost-Effectiveness"
|
||||
if "type:fraud" in tag_set:
|
||||
return "Fraud & Abuse Literature"
|
||||
if "type:clinical" in tag_set:
|
||||
return "Observational Studies"
|
||||
|
||||
return "Skin Substitutes" # fallback to root
|
||||
|
||||
|
||||
def detect_coi(extra: str, abstract: str, title: str) -> list[str]:
|
||||
"""Detect conflict-of-interest indicators, return enrichment tags."""
|
||||
tags: list[str] = []
|
||||
text = f"{extra}\n{abstract}\n{title}".lower()
|
||||
|
||||
# Check for manufacturer mentions in author affiliations
|
||||
authors_section = ""
|
||||
for line in extra.split("\n"):
|
||||
if line.startswith("Authors:"):
|
||||
authors_section = line.lower()
|
||||
break
|
||||
|
||||
# Industry funding patterns
|
||||
for pattern in FUNDING_PATTERNS:
|
||||
if re.search(pattern, text, re.IGNORECASE):
|
||||
tags.append("coi:industry-linked")
|
||||
break
|
||||
|
||||
# Single-product studies (often manufacturer-funded)
|
||||
brand_count = sum(1 for b in BRAND_NAMES if b in text)
|
||||
if brand_count == 1:
|
||||
tags.append("coi:single-product")
|
||||
|
||||
# Skeptical / critical perspective
|
||||
for pattern in SKEPTICAL_PATTERNS:
|
||||
if re.search(pattern, text, re.IGNORECASE):
|
||||
tags.append("stance:skeptical")
|
||||
break
|
||||
|
||||
# Pro-product sentiment (positive claims in title)
|
||||
title_lower = title.lower()
|
||||
pro_patterns = [
|
||||
r"(effective|superior|promising|excellent|favorable|beneficial)",
|
||||
r"(accelerat|improv|enhanc|advanc).{0,20}(heal|wound|outcome)",
|
||||
r"(novel|innovative|breakthrough).{0,20}(treatment|therapy|approach)",
|
||||
]
|
||||
for pattern in pro_patterns:
|
||||
if re.search(pattern, title_lower):
|
||||
tags.append("stance:favorable")
|
||||
break
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build skin-subs evidence base"
|
||||
)
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 70)
|
||||
print("Skin Substitutes Evidence Base Builder")
|
||||
print(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
print("=" * 70)
|
||||
|
||||
store = Store()
|
||||
con = store._con()
|
||||
|
||||
# --- Step 1: Create collection hierarchy ---
|
||||
print("\n--- Step 1: Creating collection hierarchy ---")
|
||||
col_map = store.ensure_collections(SKIN_SUBS_COLLECTIONS)
|
||||
for name, key in sorted(col_map.items()):
|
||||
print(f" {key} {name}")
|
||||
print(f" Total collections created/verified: {len(col_map)}")
|
||||
|
||||
# --- Step 2: Load all skin-subs items ---
|
||||
print("\n--- Step 2: Loading skin-subs items ---")
|
||||
rows = con.execute(
|
||||
"""SELECT DISTINCT i.id, i.key, i.title, i.abstract, i.extra
|
||||
FROM items i
|
||||
JOIN item_tags it ON i.id = it.item_id
|
||||
JOIN tags t ON it.tag_id = t.id
|
||||
WHERE t.name = 'module:skin-subs'"""
|
||||
).fetchall()
|
||||
print(f" Total items: {len(rows)}")
|
||||
|
||||
# Pre-load tags for each item
|
||||
item_tags: dict[int, list[str]] = {}
|
||||
tag_rows = con.execute(
|
||||
"""SELECT it.item_id, t.name
|
||||
FROM item_tags it
|
||||
JOIN tags t ON it.tag_id = t.id
|
||||
WHERE it.item_id IN (
|
||||
SELECT DISTINCT i.id FROM items i
|
||||
JOIN item_tags it2 ON i.id = it2.item_id
|
||||
JOIN tags t2 ON it2.tag_id = t2.id
|
||||
WHERE t2.name = 'module:skin-subs'
|
||||
)"""
|
||||
).fetchall()
|
||||
for tr in tag_rows:
|
||||
item_tags.setdefault(tr["item_id"], []).append(tr["name"])
|
||||
|
||||
# --- Step 3: Assign to collections ---
|
||||
print("\n--- Step 3: Assigning items to collections ---")
|
||||
collection_counts: dict[str, int] = {}
|
||||
assignments: list[tuple[int, str]] = [] # (item_id, collection_key)
|
||||
|
||||
for row in rows:
|
||||
tags = item_tags.get(row["id"], [])
|
||||
col_name = assign_collection(
|
||||
tags, row["title"] or "", row["abstract"] or ""
|
||||
)
|
||||
if col_name in col_map:
|
||||
assignments.append((row["id"], col_map[col_name]))
|
||||
collection_counts[col_name] = (
|
||||
collection_counts.get(col_name, 0) + 1
|
||||
)
|
||||
|
||||
print(" Assignment distribution:")
|
||||
for name, count in sorted(
|
||||
collection_counts.items(), key=lambda x: -x[1]
|
||||
):
|
||||
print(f" {name:30s}: {count:>5}")
|
||||
|
||||
# --- Step 4: COI / funding enrichment ---
|
||||
print("\n--- Step 4: COI / funding / stance enrichment ---")
|
||||
coi_tags_to_add: dict[int, list[str]] = {}
|
||||
coi_counts: dict[str, int] = {}
|
||||
|
||||
for row in rows:
|
||||
tags = item_tags.get(row["id"], [])
|
||||
# Only enrich PubMed items
|
||||
if "source:pubmed" not in tags:
|
||||
continue
|
||||
new_tags = detect_coi(
|
||||
row["extra"] or "", row["abstract"] or "", row["title"] or ""
|
||||
)
|
||||
if new_tags:
|
||||
coi_tags_to_add[row["id"]] = new_tags
|
||||
for t in new_tags:
|
||||
coi_counts[t] = coi_counts.get(t, 0) + 1
|
||||
|
||||
print(" Enrichment tag counts:")
|
||||
for tag, count in sorted(coi_counts.items(), key=lambda x: -x[1]):
|
||||
print(f" {tag:30s}: {count:>5}")
|
||||
print(f" Items enriched: {len(coi_tags_to_add)} / {len(rows)}")
|
||||
|
||||
if args.dry_run:
|
||||
print("\n[DRY RUN] Skipping writes")
|
||||
store.close()
|
||||
return
|
||||
|
||||
# --- Step 5: Write collection assignments ---
|
||||
print("\n--- Step 5: Writing collection assignments ---")
|
||||
for item_id, col_key in assignments:
|
||||
col_row = con.execute(
|
||||
"SELECT id FROM collections WHERE key = ?", (col_key,)
|
||||
).fetchone()
|
||||
if col_row:
|
||||
con.execute(
|
||||
"INSERT OR IGNORE INTO collection_items "
|
||||
"(collection_id, item_id) VALUES (?, ?)",
|
||||
(col_row["id"], item_id),
|
||||
)
|
||||
con.commit()
|
||||
print(f" Assigned {len(assignments)} items to collections")
|
||||
|
||||
# --- Step 6: Write COI enrichment tags ---
|
||||
print("\n--- Step 6: Writing COI enrichment tags ---")
|
||||
total_tags_added = 0
|
||||
for item_id, new_tags in coi_tags_to_add.items():
|
||||
for tag in new_tags:
|
||||
tag_id = store._ensure_tag(tag)
|
||||
con.execute(
|
||||
"INSERT OR IGNORE INTO item_tags (item_id, tag_id) "
|
||||
"VALUES (?, ?)",
|
||||
(item_id, tag_id),
|
||||
)
|
||||
total_tags_added += 1
|
||||
con.commit()
|
||||
print(f" Added {total_tags_added} enrichment tags")
|
||||
|
||||
# --- Step 7: Verify ---
|
||||
print("\n--- Step 7: Verification ---")
|
||||
|
||||
# Tag schema coverage
|
||||
expected_tags = [
|
||||
"module:skin-subs",
|
||||
"source:pubmed", "source:oig", "source:cms", "source:court",
|
||||
"source:doj", "source:gao", "source:medpac",
|
||||
"source:mac-lcd", "source:industry",
|
||||
"type:clinical", "type:economic", "type:fraud",
|
||||
"type:rct", "type:review", "type:meta-analysis",
|
||||
"type:report", "type:rule", "type:filing",
|
||||
"type:lcd", "type:position", "type:press-release",
|
||||
]
|
||||
for tag in expected_tags:
|
||||
count = con.execute(
|
||||
"SELECT count(*) FROM item_tags it "
|
||||
"JOIN tags t ON it.tag_id = t.id WHERE t.name = ?",
|
||||
(tag,),
|
||||
).fetchone()[0]
|
||||
status = "OK" if count > 0 else "MISSING"
|
||||
print(f" {status:7s} {tag:30s}: {count:>5}")
|
||||
|
||||
# Collection item counts
|
||||
print("\n Collection item counts:")
|
||||
for name, key in sorted(col_map.items()):
|
||||
count = con.execute(
|
||||
"SELECT count(*) FROM collection_items ci "
|
||||
"JOIN collections c ON ci.collection_id = c.id "
|
||||
"WHERE c.key = ?",
|
||||
(key,),
|
||||
).fetchone()[0]
|
||||
if count > 0:
|
||||
print(f" {name:30s}: {count:>5}")
|
||||
|
||||
# Total
|
||||
total = con.execute(
|
||||
"""SELECT count(DISTINCT i.id) FROM items i
|
||||
JOIN item_tags it ON i.id = it.item_id
|
||||
JOIN tags t ON it.tag_id = t.id
|
||||
WHERE t.name = 'module:skin-subs'"""
|
||||
).fetchone()[0]
|
||||
print(f"\n Total module:skin-subs items: {total}")
|
||||
|
||||
store.close()
|
||||
print("\nDone.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user