fix(rules): recompute current asset pri under referenced rule groups for upgrades
Slot units and movie assets recorded pri from slot soft.tiers only, so a rule group ladder would either never upgrade (pri 100) or re-upgrade forever. The current pri is now re-evaluated from the asset's source release with the same chain (fallback: pri recorded in the plan evaluation). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d28686ed02
commit
36027a4820
2 files changed
+118
-3
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
checkBudget,
|
||||
computeUnitGaps,
|
||||
determineMode,
|
||||
effectiveLadder,
|
||||
type EvaluationContext,
|
||||
episodeKey,
|
||||
evaluateCandidate,
|
||||
@@ -20,7 +21,9 @@ import {
|
||||
parseRelease,
|
||||
priorityOf,
|
||||
type QualityFacts,
|
||||
ruleChainOf,
|
||||
type SlotEvaluation,
|
||||
type SlotSpec,
|
||||
type SlotState,
|
||||
seasonWildcard,
|
||||
transition,
|
||||
@@ -228,6 +231,8 @@ interface Ctx {
|
||||
rules?: Promise<RuleCatalog>;
|
||||
/** 作品原语言缓存(规则组 CNSUB/CNVOI 放行) */
|
||||
langs?: Map<string, Promise<string | undefined>>;
|
||||
/** 现有资产在规则组下的 pri 缓存 */
|
||||
assetPri?: Map<string, number | undefined>;
|
||||
}
|
||||
|
||||
async function note(c: Ctx, base: Omit<CandidateEvaluation, "evaluatedAt">) {
|
||||
@@ -574,6 +579,10 @@ async function evaluateForSlot(c: Ctx, p: SlotInput): Promise<{ planId: string |
|
||||
if (isMovie) {
|
||||
if (inflight.length > 0 || BUSY.includes(slot.status)) return defer("slot_busy", []);
|
||||
const current = await currentFacts(db, slot.current_asset_id);
|
||||
if (current && slot.current_asset_id && rulesLaddered(spec)) {
|
||||
const pri = await rulePriOfAsset(c, spec, slot.current_asset_id, ctx.originalLanguage);
|
||||
if (pri !== undefined) current.pri = pri;
|
||||
}
|
||||
ev = evaluateCandidate(spec, parsed, slotSatisfied ? current : null, ctx);
|
||||
} else {
|
||||
const cov = await slotCoveredUnits(db, slot, { entityWide: p.slotCount <= 1 });
|
||||
@@ -623,7 +632,12 @@ async function evaluateForSlot(c: Ctx, p: SlotInput): Promise<{ planId: string |
|
||||
return defer("units_inflight", reservedHit);
|
||||
} else {
|
||||
// 都已覆盖:分集洗版,只取能提升的单元(对照 MP get_best_version_interested_episodes)
|
||||
const unitCurrent = await unitQuality(db, slot, releaseKeys);
|
||||
const unitCurrent = await unitQuality(
|
||||
db,
|
||||
slot,
|
||||
releaseKeys,
|
||||
rulesLaddered(spec) ? (assetId) => rulePriOfAsset(c, spec, assetId, ctx.originalLanguage) : undefined,
|
||||
);
|
||||
const withCurrent = releaseKeys.filter((k) => unitCurrent[k]);
|
||||
const firstCurrent = withCurrent.length
|
||||
? unitCurrent[withCurrent[0]!]!
|
||||
@@ -743,11 +757,13 @@ async function unitQuality(
|
||||
exec: Exec,
|
||||
slot: AcquisitionSlotRow,
|
||||
keys: readonly string[],
|
||||
/** 版本目标引用了带层的规则组时,按规则组重算该单元现有资产的 pri(覆盖入库时按 soft.tiers 记下的值) */
|
||||
priOf?: (assetId: string) => Promise<number | undefined>,
|
||||
): Promise<Record<string, QualityFacts | undefined>> {
|
||||
const rows = await exec
|
||||
.selectFrom("slot_unit as su")
|
||||
.innerJoin("content_unit as u", "u.id", "su.content_unit_id")
|
||||
.select(["u.season_number", "u.episode_number", "su.quality", "su.pri"])
|
||||
.select(["u.season_number", "u.episode_number", "su.quality", "su.pri", "su.media_asset_id"])
|
||||
.where("su.slot_id", "=", slot.id)
|
||||
.where("su.media_asset_id", "is not", null)
|
||||
.execute();
|
||||
@@ -757,11 +773,50 @@ async function unitQuality(
|
||||
if (r.episode_number === null) continue;
|
||||
const k = episodeKey(r.season_number ?? 1, r.episode_number);
|
||||
if (!want.has(k)) continue;
|
||||
out[k] = { ...((r.quality ?? {}) as QualityFacts), ...(r.pri !== null ? { pri: r.pri } : {}) };
|
||||
const pri = priOf && r.media_asset_id ? ((await priOf(r.media_asset_id)) ?? r.pri) : r.pri;
|
||||
out[k] = { ...((r.quality ?? {}) as QualityFacts), ...(pri !== null ? { pri } : {}) };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const rulesLaddered = (spec: SlotSpec) => !!spec.rules?.groups.some((g) => g.layers.length > 0);
|
||||
|
||||
/**
|
||||
* 现有资产在规则组下的 pri:有来源资源 → 按当前规则组对其标题/副标题/站点事实重算(同 MP 对种子求 pri_order);
|
||||
* 否则取下载计划评估时记下的 pri。一次评估内按资产缓存。
|
||||
*/
|
||||
async function rulePriOfAsset(c: Ctx, spec: SlotSpec, assetId: string, lang?: string): Promise<number | undefined> {
|
||||
c.assetPri ??= new Map();
|
||||
const key = `${spec.id ?? ""}:${assetId}`;
|
||||
if (c.assetPri.has(key)) return c.assetPri.get(key);
|
||||
const db = c.d.ctx.db;
|
||||
let pri: number | undefined;
|
||||
const a = await db
|
||||
.selectFrom("media_asset")
|
||||
.select(["source_release_id", "download_task_id"])
|
||||
.where("id", "=", assetId)
|
||||
.executeTakeFirst();
|
||||
const rel = a?.source_release_id
|
||||
? await db.selectFrom("source_release").selectAll().where("id", "=", a.source_release_id).executeTakeFirst()
|
||||
: undefined;
|
||||
if (rel) {
|
||||
const parsed = parsedOf(rel);
|
||||
const ctx = { ...contextOf(rel, c.now), ...(lang ? { originalLanguage: lang } : {}) };
|
||||
pri = effectiveLadder(spec, parsed, ctx, ruleChainOf(spec, parsed, ctx)).pri;
|
||||
} else if (a?.download_task_id) {
|
||||
const p = await db
|
||||
.selectFrom("download_task as t")
|
||||
.innerJoin("acquisition_plan as p", "p.id", "t.acquisition_plan_id")
|
||||
.select("p.evaluation")
|
||||
.where("t.id", "=", a.download_task_id)
|
||||
.executeTakeFirst();
|
||||
const v = (p?.evaluation as { pri?: unknown } | undefined)?.pri;
|
||||
if (typeof v === "number") pri = v;
|
||||
}
|
||||
c.assetPri.set(key, pri);
|
||||
return pri;
|
||||
}
|
||||
|
||||
interface GateOk {
|
||||
ok: true;
|
||||
instanceId: string;
|
||||
|
||||
Reference in new issue
Block a user