| """ |
| Backfill framework alignment scores for existing items and rebuild the |
| AlignmentAggregate table (per institution + global). |
| |
| Usage: |
| python scripts/backfill_alignment.py # DRY RUN — counts only |
| python scripts/backfill_alignment.py --apply |
| python scripts/backfill_alignment.py --apply --force # re-score current-version items |
| |
| Safe to re-run: items already at ALIGNMENT_VERSION are skipped unless --force. |
| No network needed beyond the one-time embedding-model download. |
| """ |
|
|
| import argparse |
| import json |
| import os |
| import sys |
|
|
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
| from uraas.config.alignment_frameworks import ALIGNMENT_VERSION |
| from uraas.database import Item, SessionLocal |
| from uraas.services.alignment_engine import ( |
| recompute_aggregates, |
| score_item_alignment, |
| scoring_mode, |
| ) |
| from uraas.utils.analytics_cache import analytics_cache |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("--apply", action="store_true", help="Write changes (default: dry run)") |
| parser.add_argument("--force", action="store_true", help="Re-score items already at current version") |
| parser.add_argument("--batch", type=int, default=500) |
| args = parser.parse_args() |
|
|
| session = SessionLocal() |
| try: |
| q = session.query(Item) |
| if not args.force: |
| q = q.filter( |
| (Item.alignment_version.is_(None)) |
| | (Item.alignment_version < ALIGNMENT_VERSION) |
| ) |
| todo = q.count() |
| total = session.query(Item).count() |
| print("=" * 64) |
| print(f"Scoring mode: {scoring_mode()} | version: {ALIGNMENT_VERSION}") |
| print(f"Items to score: {todo} / {total}") |
| print("=" * 64) |
| if not args.apply: |
| print("[DRY RUN] No writes. Re-run with --apply.") |
| return 0 |
|
|
| scored = aligned = 0 |
| framework_hits = {} |
| while True: |
| batch = q.limit(args.batch).all() |
| if not batch: |
| break |
| for it in batch: |
| j, v = score_item_alignment( |
| it.title or "", it.abstract or "", it.dc_subject or "" |
| ) |
| it.alignment_scores = j |
| it.alignment_version = v |
| scored += 1 |
| if j: |
| aligned += 1 |
| for fk in json.loads(j): |
| framework_hits[fk] = framework_hits.get(fk, 0) + 1 |
| session.commit() |
| print(f" scored {scored}/{todo}") |
|
|
| print("\nPer-framework items with alignment:") |
| for fk, n in sorted(framework_hits.items(), key=lambda kv: -kv[1]): |
| print(f" {fk:24s} {n}") |
|
|
| |
| rows = recompute_aggregates(session, None) |
| institutions = [ |
| i for (i,) in session.query(Item.institution).distinct() if i |
| ] |
| for inst in institutions: |
| rows += recompute_aggregates(session, inst) |
| print(f"\nAggregate rows written: {rows} ({1 + len(institutions)} scopes)") |
|
|
| analytics_cache.invalidate_all() |
| print("\n" + "=" * 64) |
| print(f"DONE. scored={scored} with_alignment={aligned}") |
| print("=" * 64) |
| return 0 |
| finally: |
| session.close() |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|