会员积分改版V1

This commit is contained in:
2026-08-11 09:24:18 +08:00
parent fe24e51b97
commit b9fd07f293
111 changed files with 9355 additions and 3900 deletions
@@ -16,7 +16,6 @@ from app.enums.credit_record import (
CreditRecordSourceModule,
CreditRecordSourceStepCode,
)
from app.enums.llm_billing import LlmBillingConfigKey
from sqlalchemy import String, case, cast, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -56,8 +55,8 @@ from app.schemas.shot_replicate import (
from app.services.module_generation_log_service import log_module_event_file
from app.services.llm_billing import (
LlmBillingContext,
release_on_failure,
start_hold,
refund_on_final_failure,
pre_deduct,
validate_retryable_previous_attempt,
)
from app.services.resource_accounting_service import SOURCE_MODEL_SHOT_SEGMENT, soft_delete_resources_by_source
@@ -94,8 +93,7 @@ def build_task_set_analysis_billing_context(task_set: ShotReplicateTaskSet) -> L
source_step_id=str(task_set.id),
source_step_code=CreditRecordSourceStepCode.VIDEO_ANALYSIS.value,
related_id=str(task_set.id),
hold_config_key=LlmBillingConfigKey.HOLD_SHOT_VIDEO_ANALYSIS.value,
description_prefix="拆镜复刻原视频AI分析",
description_prefix="拆镜复刻原视频AI分析",
trace_id=f"shot-task-set-analysis:{task_set.id}:attempt:{int(task_set.analysis_attempt_no or 1)}",
)
@@ -113,8 +111,7 @@ def build_segment_analysis_billing_context(segment: ShotReplicateSegment) -> Llm
source_step_id=str(segment.id),
source_step_code=CreditRecordSourceStepCode.VIDEO_ANALYSIS.value,
related_id=str(segment.id),
hold_config_key=LlmBillingConfigKey.HOLD_SHOT_VIDEO_ANALYSIS.value,
description_prefix="拆镜复刻片段视频AI分析",
description_prefix="拆镜复刻片段视频AI分析",
trace_id=f"shot-segment-analysis:{segment.id}:attempt:{int(segment.analysis_attempt_no or 1)}",
)
@@ -268,7 +265,7 @@ async def create_task_set(
)
db.add(task_set)
await db.flush()
await start_hold(db, build_task_set_analysis_billing_context(task_set))
await pre_deduct(db, build_task_set_analysis_billing_context(task_set))
log_module_event_file(
module=MODULE,
event_type="SHOT_TASK_SET_CREATED",
@@ -751,7 +748,7 @@ async def create_custom_segment(
task_set.status = ShotTaskSetStatusEnum.SPLITTING.value
task_set.split_status = ShotSplitStatusEnum.PROCESSING.value
await db.flush()
await start_hold(db, build_segment_analysis_billing_context(segment))
await pre_deduct(db, build_segment_analysis_billing_context(segment))
await refresh_task_set_split_summary(db, task_set.id)
await db.flush()
log_module_event_file(
@@ -920,7 +917,7 @@ async def delete_segment(
raise HTTPException(status_code=409, detail="当前拆镜片段关联的复刻流程正在处理中,暂不能删除")
if segment.source_mode == ShotSegmentSourceModeEnum.CUSTOM.value:
await release_on_failure(
await refund_on_final_failure(
db,
build_segment_analysis_billing_context(segment),
error="用户删除自定义拆镜片段,释放未结算的片段分析预扣",
@@ -982,7 +979,7 @@ async def delete_segment(
"pending_delete_resource_count": len(pending_delete_resource_ids),
"physical_file_delete": "after_commit",
"media_refund": False,
"llm_hold_release_on_cancel": segment.source_mode == ShotSegmentSourceModeEnum.CUSTOM.value,
"llm_pre_deduct_refund_on_cancel": segment.source_mode == ShotSegmentSourceModeEnum.CUSTOM.value,
},
)
@@ -1037,14 +1034,14 @@ async def delete_task_set(
# 删除是对未执行/失败任务的最终取消动作;处理中的任务已在上方拦截。
# 这里仅做账务补偿,不改变现有逐项目删除流程。
await release_on_failure(
await refund_on_final_failure(
db,
build_task_set_analysis_billing_context(task_set),
error="用户删除拆镜任务集,释放未结算的原视频分析预扣",
)
for segment in segments:
if segment.source_mode == ShotSegmentSourceModeEnum.CUSTOM.value:
await release_on_failure(
await refund_on_final_failure(
db,
build_segment_analysis_billing_context(segment),
error="用户删除拆镜任务集,释放未结算的片段分析预扣",
@@ -1113,7 +1110,7 @@ async def delete_task_set(
"segment_upload_release": {k: v for k, v in segment_upload_release.items() if k != "released_resource_ids"},
"physical_file_delete": "after_commit",
"media_refund": False,
"llm_hold_release_on_cancel": True,
"llm_pre_deduct_refund_on_cancel": True,
},
)
return ShotTaskSetDeleteOut(
@@ -1198,7 +1195,7 @@ async def prepare_reanalyze_task_set(
task_set.analysis_raw_json = None
task_set.analysis_result_json = None
await db.flush()
await start_hold(db, build_task_set_analysis_billing_context(task_set))
await pre_deduct(db, build_task_set_analysis_billing_context(task_set))
log_module_event_file(
module=MODULE,
event_type=ShotReplicateLogEventEnum.TASK_SET_REANALYZE_RECEIVED.value,
@@ -1304,7 +1301,7 @@ async def prepare_reanalyze_segment(
segment.segment_category = None
segment.segment_audience = None
await db.flush()
await start_hold(db, build_segment_analysis_billing_context(segment))
await pre_deduct(db, build_segment_analysis_billing_context(segment))
log_module_event_file(
module=MODULE,
event_type=ShotReplicateLogEventEnum.SEGMENT_REANALYZE_RECEIVED.value,
@@ -1357,7 +1354,7 @@ async def mark_task_set_analysis_dispatch_failed(
task_set.analysis_claim_token = None
task_set.analysis_lease_until = None
task_set.analysis_error_message = error_message
await release_on_failure(db, build_task_set_analysis_billing_context(task_set), error=error_message)
await refund_on_final_failure(db, build_task_set_analysis_billing_context(task_set), error=error_message)
log_module_event_file(
module=MODULE,
event_type=ShotReplicateLogEventEnum.CELERY_DISPATCH_FAILED.value,
@@ -1394,7 +1391,7 @@ async def mark_segment_analysis_dispatch_failed(
segment.analysis_claim_token = None
segment.analysis_lease_until = None
segment.analysis_error_message = error_message
await release_on_failure(db, build_segment_analysis_billing_context(segment), error=error_message)
await refund_on_final_failure(db, build_segment_analysis_billing_context(segment), error=error_message)
log_module_event_file(
module=MODULE,
event_type=ShotReplicateLogEventEnum.CELERY_DISPATCH_FAILED.value,
@@ -1423,7 +1420,7 @@ async def mark_custom_segment_split_dispatch_failed(
segment.split_next_retry_at = None
segment.split_last_error = error_message
# 切片投递失败不改变 LLM attempt 的冻结状态。用户重试切片时继续沿用
# 原 active HOLD;只有片段分析最终失败或用户删除片段时才释放
# 原固定预扣;只有片段分析最终失败或用户删除片段时才按原来源退款
log_module_event_file(
module=MODULE,
event_type=ShotReplicateLogEventEnum.CELERY_DISPATCH_FAILED.value,