PMFlow/backend/scripts/backfill_orphan_signal_link...

100 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""回填 signals.matched_project_id 对应的 project_signal_links。"""
from __future__ import annotations
import argparse
import asyncio
from sqlalchemy import and_, select
from app.database import AsyncSessionLocal
from app.models import ProjectSignalLink, Signal
async def _query_orphans(*, tenant_id: int, signal_id: int | None) -> list[tuple[int, int, int, str]]:
async with AsyncSessionLocal() as db:
stmt = (
select(
Signal.id,
Signal.tenant_id,
Signal.matched_project_id,
Signal.title,
)
.outerjoin(
ProjectSignalLink,
and_(
ProjectSignalLink.tenant_id == Signal.tenant_id,
ProjectSignalLink.signal_id == Signal.id,
),
)
.where(
and_(
Signal.tenant_id == int(tenant_id),
Signal.matched_project_id.is_not(None),
ProjectSignalLink.id.is_(None),
)
)
.order_by(Signal.id.asc())
)
if signal_id is not None:
stmt = stmt.where(Signal.id == int(signal_id))
rows = (await db.execute(stmt)).all()
return [
(
int(row[0]),
int(row[1]),
int(row[2]),
str(row[3] or ""),
)
for row in rows
if row[0] is not None and row[1] is not None and row[2] is not None
]
async def _apply_backfill(*, tenant_id: int, signal_id: int | None) -> int:
rows = await _query_orphans(tenant_id=tenant_id, signal_id=signal_id)
if not rows:
return 0
async with AsyncSessionLocal() as db:
for sid, tid, pid, _ in rows:
db.add(
ProjectSignalLink(
tenant_id=tid,
signal_id=sid,
project_id=pid,
link_type="matched_project",
relevance_score=1.0,
)
)
await db.commit()
return len(rows)
async def _main() -> None:
parser = argparse.ArgumentParser(description="Backfill orphan project_signal_links from signals.matched_project_id")
parser.add_argument("--tenant-id", type=int, default=1, help="目标租户 ID,默认 1")
parser.add_argument("--signal-id", type=int, default=None, help="仅修复指定 signal_id")
parser.add_argument("--apply", action="store_true", help="执行写入;不传时仅预览")
args = parser.parse_args()
rows = await _query_orphans(tenant_id=args.tenant_id, signal_id=args.signal_id)
print(f"[preview] tenant_id={args.tenant_id} orphan_count={len(rows)}")
for sid, _, pid, title in rows[:20]:
print(f"- signal_id={sid} -> project_id={pid} title={title[:80]}")
if len(rows) > 20:
print(f"... ({len(rows) - 20} more)")
if not args.apply:
print("[dry-run] 未执行写入。加 --apply 以回填。")
return
inserted = await _apply_backfill(tenant_id=args.tenant_id, signal_id=args.signal_id)
remaining = await _query_orphans(tenant_id=args.tenant_id, signal_id=args.signal_id)
print(f"[apply] inserted={inserted} remaining_orphans={len(remaining)}")
if __name__ == "__main__":
asyncio.run(_main())