| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- from __future__ import annotations
- from sqlalchemy import delete, func, select, tuple_, update
- from sqlalchemy.dialects.mysql import insert
- from supply_infra.db.models.multi_demand_pool_di import MultiDemandPoolDi
- from supply_infra.db.repositories.base import BaseRepository
- _BATCH_SIZE = 1000
- RowKey = tuple[str, str]
- RealMetric = tuple[float | None, float | None]
- class MultiDemandPoolDiRepository(BaseRepository[MultiDemandPoolDi]):
- """策略需求天级表 repository。"""
- model = MultiDemandPoolDi
- def get_latest_biz_dt(self) -> str | None:
- """返回需求池中最新业务日;无数据时返回 None。"""
- stmt = select(func.max(MultiDemandPoolDi.biz_dt))
- return self.session.scalar(stmt)
- def list_distinct_demand_name_summaries(
- self,
- biz_dt: str,
- *,
- limit: int = 50,
- offset: int = 0,
- exclude_names: list[str] | None = None,
- ) -> list[dict]:
- """
- 按业务日分页列出去重需求词及聚合统计。
- 返回每项包含:demand_name、row_count(出现行数)、strategies(策略列表)、
- total_weight、total_video_count、max_real_rov_7d、max_real_vov_7d。
- 按 max_real_rov_7d 降序、total_weight 降序排列,优先暴露有后验数据/高权重的需求词。
- """
- stmt = (
- select(
- MultiDemandPoolDi.demand_name,
- func.count().label("row_count"),
- func.group_concat(MultiDemandPoolDi.strategy.distinct()).label("strategies"),
- func.sum(MultiDemandPoolDi.weight).label("total_weight"),
- func.sum(MultiDemandPoolDi.video_count).label("total_video_count"),
- func.max(MultiDemandPoolDi.real_rov_7d).label("max_real_rov_7d"),
- func.max(MultiDemandPoolDi.real_vov_7d).label("max_real_vov_7d"),
- )
- .where(MultiDemandPoolDi.biz_dt == biz_dt)
- )
- if exclude_names:
- stmt = stmt.where(MultiDemandPoolDi.demand_name.notin_(exclude_names))
- stmt = (
- stmt.group_by(MultiDemandPoolDi.demand_name)
- .order_by(
- func.max(MultiDemandPoolDi.real_rov_7d).desc(),
- func.sum(MultiDemandPoolDi.weight).desc(),
- )
- .limit(limit)
- .offset(offset)
- )
- rows = self.session.execute(stmt).all()
- return [
- {
- "demand_name": name,
- "row_count": int(row_count or 0),
- "strategies": (strategies or "").split(",") if strategies else [],
- "total_weight": float(total_weight) if total_weight is not None else None,
- "total_video_count": int(total_video_count) if total_video_count is not None else None,
- "max_real_rov_7d": float(max_rov) if max_rov is not None else None,
- "max_real_vov_7d": float(max_vov) if max_vov is not None else None,
- }
- for name, row_count, strategies, total_weight, total_video_count, max_rov, max_vov in rows
- ]
- def count_distinct_demand_names(self, biz_dt: str) -> int:
- """统计指定业务日去重需求词总数。"""
- stmt = select(func.count(func.distinct(MultiDemandPoolDi.demand_name))).where(
- MultiDemandPoolDi.biz_dt == biz_dt
- )
- return int(self.session.scalar(stmt) or 0)
- def list_by_biz_dt(self, biz_dt: str) -> list[MultiDemandPoolDi]:
- """返回业务日全部需求池行,供来源内排名等全局计算使用。"""
- stmt = (
- select(MultiDemandPoolDi)
- .where(MultiDemandPoolDi.biz_dt == biz_dt)
- .order_by(MultiDemandPoolDi.strategy, MultiDemandPoolDi.demand_name, MultiDemandPoolDi.id)
- )
- return list(self.session.scalars(stmt).all())
- def search_rows_by_name_fragment(self, biz_dt: str, keyword: str) -> list[dict]:
- """
- 按业务日 + 需求名双向包含关系搜索明细行。
- 匹配 demand_name LIKE %keyword% 或 keyword LIKE %demand_name%(互相包含),
- 用于把同语义、措辞不同的需求词合并到一起判断。返回按 demand_name 去重后的明细。
- """
- keyword = (keyword or "").strip()
- if not keyword:
- return []
- stmt = select(MultiDemandPoolDi).where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- MultiDemandPoolDi.demand_name.like(f"%{keyword}%"),
- )
- rows = list(self.session.scalars(stmt).all())
- if len(keyword) >= 2:
- broad_stmt = select(MultiDemandPoolDi).where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- )
- seen_ids = {int(r.id) for r in rows}
- for row in self.session.scalars(broad_stmt).all():
- if int(row.id) in seen_ids:
- continue
- name = row.demand_name or ""
- if name and name in keyword:
- rows.append(row)
- seen_ids.add(int(row.id))
- return [
- {
- "id": int(row.id),
- "demand_name": row.demand_name,
- "strategy": row.strategy,
- "weight": None if row.weight is None or row.weight == 0 else float(row.weight),
- "video_count": int(row.video_count) if row.video_count is not None else None,
- "real_rov_7d": float(row.real_rov_7d) if row.real_rov_7d is not None else None,
- "real_vov_7d": float(row.real_vov_7d) if row.real_vov_7d is not None else None,
- }
- for row in rows
- ]
- def get_by_ids(self, ids: list[int]) -> list[MultiDemandPoolDi]:
- """按 id 批量查询完整行。"""
- if not ids:
- return []
- rows: list[MultiDemandPoolDi] = []
- for start in range(0, len(ids), _BATCH_SIZE):
- batch = ids[start : start + _BATCH_SIZE]
- stmt = select(MultiDemandPoolDi).where(MultiDemandPoolDi.id.in_(batch))
- rows.extend(self.session.scalars(stmt).all())
- return rows
- def count_by_biz_dt(self, biz_dt: str) -> int:
- """统计指定业务日期去重行数(strategy + demand_id)。"""
- stmt = (
- select(func.count())
- .select_from(
- select(MultiDemandPoolDi.strategy, MultiDemandPoolDi.demand_id)
- .where(MultiDemandPoolDi.biz_dt == biz_dt)
- .distinct()
- .subquery()
- )
- )
- return int(self.session.scalar(stmt) or 0)
- def list_keys_by_biz_dt(self, biz_dt: str) -> set[RowKey]:
- """查询指定业务日期的 (strategy, demand_id) 键集合。"""
- stmt = select(MultiDemandPoolDi.strategy, MultiDemandPoolDi.demand_id).where(
- MultiDemandPoolDi.biz_dt == biz_dt
- )
- return {(str(s), str(d)) for s, d in self.session.execute(stmt).all()}
- def delete_by_biz_dt(self, biz_dt: str) -> int:
- """删除指定业务日期的全部数据,便于按日重跑。"""
- stmt = delete(MultiDemandPoolDi).where(MultiDemandPoolDi.biz_dt == biz_dt)
- result = self.session.execute(stmt)
- return result.rowcount or 0
- def delete_by_keys(self, biz_dt: str, keys: list[RowKey]) -> int:
- """按 (strategy, demand_id) 批量删除指定业务日期数据。"""
- if not keys:
- return 0
- deleted = 0
- for i in range(0, len(keys), _BATCH_SIZE):
- batch = keys[i : i + _BATCH_SIZE]
- stmt = delete(MultiDemandPoolDi).where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- tuple_(MultiDemandPoolDi.strategy, MultiDemandPoolDi.demand_id).in_(batch),
- )
- result = self.session.execute(stmt)
- deleted += result.rowcount or 0
- return deleted
- def list_demand_names_by_biz_dt(self, biz_dt: str) -> list[str]:
- """查询指定业务日期的全部 demand_name。"""
- stmt = select(MultiDemandPoolDi.demand_name).where(
- MultiDemandPoolDi.biz_dt == biz_dt
- )
- return [name for name in self.session.scalars(stmt).all() if name]
- def list_all_video_lists(self) -> list[str]:
- """查询全表非空 video_list(JSON 文本)。"""
- stmt = select(MultiDemandPoolDi.video_list).where(
- MultiDemandPoolDi.video_list.isnot(None),
- MultiDemandPoolDi.video_list != "",
- )
- return [text for text in self.session.scalars(stmt).all() if text]
- def list_id_name_video_lists(self) -> list[tuple[int, str, str | None]]:
- """查询全表 (id, demand_name, video_list)。"""
- stmt = select(
- MultiDemandPoolDi.id,
- MultiDemandPoolDi.demand_name,
- MultiDemandPoolDi.video_list,
- )
- return [
- (int(row_id), str(name), video_list)
- for row_id, name, video_list in self.session.execute(stmt).all()
- if name is not None
- ]
- def list_weights_by_name_like(
- self,
- biz_dt: str,
- keyword: str,
- strategies: list[str],
- ) -> list[tuple[str, float | None]]:
- """按日期 + demand_name LIKE + 指定策略,返回 (strategy, weight)。"""
- if not keyword or not strategies:
- return []
- stmt = select(MultiDemandPoolDi.strategy, MultiDemandPoolDi.weight).where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- MultiDemandPoolDi.demand_name.like(f"%{keyword}%"),
- MultiDemandPoolDi.strategy.in_(strategies),
- )
- return [
- (str(strategy), None if weight is None or weight == 0 else float(weight))
- for strategy, weight in self.session.execute(stmt).all()
- ]
- def list_real_metrics_by_name_like(
- self,
- biz_dt: str,
- keyword: str,
- ) -> list[tuple[float | None, float | None]]:
- """
- 按日期 + demand_name LIKE 返回 rov_diff / vov_diff(real_rov_7d / real_vov_7d 字段)。
- 同一 demand_name 只取一条(MAX),避免多策略重复膨胀 count。
- """
- if not keyword:
- return []
- stmt = (
- select(
- func.max(MultiDemandPoolDi.real_rov_7d),
- func.max(MultiDemandPoolDi.real_vov_7d),
- )
- .where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- MultiDemandPoolDi.demand_name.like(f"%{keyword}%"),
- )
- .group_by(MultiDemandPoolDi.demand_name)
- )
- return [
- (
- float(rov) if rov is not None else None,
- float(vov) if vov is not None else None,
- )
- for rov, vov in self.session.execute(stmt).all()
- ]
- def bulk_insert(self, rows: list[dict]) -> int:
- """批量插入。"""
- if not rows:
- return 0
- inserted = 0
- for i in range(0, len(rows), _BATCH_SIZE):
- batch = rows[i : i + _BATCH_SIZE]
- stmt = insert(MultiDemandPoolDi).values(batch)
- result = self.session.execute(stmt)
- inserted += result.rowcount
- return inserted
- def update_real_metrics_by_demand_name(
- self,
- biz_dt: str,
- metrics_by_name: dict[str, RealMetric],
- ) -> int:
- """按 demand_name(特征值)批量更新 rov_diff / vov_diff(real_rov_7d / real_vov_7d)。"""
- if not metrics_by_name:
- return 0
- updated = 0
- for demand_name, (real_rov_7d, real_vov_7d) in metrics_by_name.items():
- stmt = (
- update(MultiDemandPoolDi)
- .where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- MultiDemandPoolDi.demand_name == demand_name,
- )
- .values(real_rov_7d=real_rov_7d, real_vov_7d=real_vov_7d)
- )
- result = self.session.execute(stmt)
- updated += result.rowcount or 0
- return updated
- def update_video_fields(self, biz_dt: str, rows: list[dict]) -> int:
- """按 (strategy, demand_id) 批量更新 video_list / video_count / weight。"""
- if not rows:
- return 0
- updated = 0
- for row in rows:
- stmt = (
- update(MultiDemandPoolDi)
- .where(
- MultiDemandPoolDi.biz_dt == biz_dt,
- MultiDemandPoolDi.strategy == row["strategy"],
- MultiDemandPoolDi.demand_id == row["demand_id"],
- )
- .values(
- video_list=row.get("video_list"),
- video_count=row.get("video_count"),
- weight=row.get("weight"),
- )
- )
- result = self.session.execute(stmt)
- updated += result.rowcount or 0
- return updated
|