File size: 5,502 Bytes
74bf532 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | """
Database Cleanup Script
Removes bad/misattributed data from the URAAS database.
Cleanup rules:
1. Remove items from institutions no longer in the registry
2. Remove items with no title, no DOI, no URL, and no authors
3. Remove exact DOI duplicates (keep first by id)
4. Remove items whose title is fewer than 10 chars
5. Flag (do not delete) items with institution mismatch in affiliation
6. Report before/after counts
"""
import logging
import os
import sys
from datetime import datetime
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
def run_cleanup(dry_run: bool = False):
from sqlalchemy import func
from uraas.config.institutions import get_registry
from uraas.database import Author, Collection, Community, Item, SessionLocal
registry = get_registry()
valid_inst_names = {c.name for c in registry.list_all()}
session = SessionLocal()
try:
total_before = session.query(Item).count()
log.info(f"Starting cleanup | Items before: {total_before}")
removed = 0
# ββ Rule 1: Remove items from removed institutions ββββββββββββββββββββ
all_institutions_in_db = session.query(Item.institution).distinct().all()
stale_insts = [
r[0]
for r in all_institutions_in_db
if r[0] and r[0] not in valid_inst_names
]
if stale_insts:
log.info(f"Found stale institutions: {stale_insts}")
for stale in stale_insts:
stale_items = (
session.query(Item).filter(Item.institution == stale).all()
)
log.info(f" [{stale}] {len(stale_items)} items to remove")
if not dry_run:
for item in stale_items:
session.delete(item)
session.commit()
removed += len(stale_items)
# ββ Rule 2: Remove items with no title ββββββββββββββββββββββββββββββββ
no_title = (
session.query(Item)
.filter(
(Item.title == None) | (Item.title == "") | (Item.title == "Untitled")
)
.all()
)
log.info(f"Items with no/empty title: {len(no_title)}")
if not dry_run:
for item in no_title:
session.delete(item)
session.commit()
removed += len(no_title)
# ββ Rule 3: Remove items with title < 10 chars and no DOI ββββββββββββ
all_short = session.query(Item).filter(Item.doi == None).all()
short_items = [i for i in all_short if i.title and len(i.title.strip()) < 10]
log.info(
f"Items with very short title (<10 chars) and no DOI: {len(short_items)}"
)
if not dry_run:
for item in short_items:
session.delete(item)
session.commit()
removed += len(short_items)
# ββ Rule 4: Remove exact DOI duplicates (keep lowest id) βββββββββββββ
doi_subq = (
session.query(Item.doi, func.min(Item.id).label("min_id"))
.filter(Item.doi != None)
.group_by(Item.doi)
.subquery()
)
dup_dois = (
session.query(Item)
.filter(Item.doi != None, Item.id.notin_(session.query(doi_subq.c.min_id)))
.all()
)
log.info(f"Duplicate DOI items to remove: {len(dup_dois)}")
if not dry_run:
for item in dup_dois:
session.delete(item)
session.commit()
removed += len(dup_dois)
# ββ Rule 5: Remove items with no institution tag ββββββββββββββββββββββ
no_inst = (
session.query(Item)
.filter((Item.institution == None) | (Item.institution == ""))
.all()
)
# Only remove those that have no authors either
truly_orphan = [i for i in no_inst if not i.authors]
log.info(f"Items with no institution AND no authors: {len(truly_orphan)}")
if not dry_run:
for item in truly_orphan:
session.delete(item)
session.commit()
removed += len(truly_orphan)
total_after = session.query(Item).count()
log.info(f"\n{'='*50}")
log.info(f"CLEANUP COMPLETE {'(DRY RUN)' if dry_run else ''}")
log.info(f" Items before: {total_before}")
log.info(f" Items removed: {removed}")
log.info(f" Items after: {total_after}")
log.info(f"{'='*50}")
return {
"total_before": total_before,
"removed": removed,
"total_after": total_after,
"dry_run": dry_run,
}
except Exception as e:
log.error(f"Cleanup error: {e}")
session.rollback()
raise
finally:
session.close()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="URAAS Database Cleanup")
parser.add_argument(
"--dry-run", action="store_true", help="Report without deleting"
)
args = parser.parse_args()
run_cleanup(dry_run=args.dry_run)
|