93 lines
2.7 KiB
Python
93 lines
2.7 KiB
Python
from datetime import datetime
|
|
|
|
from sqlmodel import Session, col, select
|
|
|
|
from innercontext.models import PricingRecalcJob, Product
|
|
from innercontext.models.base import utc_now
|
|
|
|
|
|
def enqueue_pricing_recalc(
|
|
session: Session, *, scope: str = "global"
|
|
) -> PricingRecalcJob:
|
|
existing = session.exec(
|
|
select(PricingRecalcJob)
|
|
.where(PricingRecalcJob.scope == scope)
|
|
.where(col(PricingRecalcJob.status).in_(["pending", "running"]))
|
|
.order_by(col(PricingRecalcJob.created_at).asc())
|
|
).first()
|
|
if existing is not None:
|
|
return existing
|
|
|
|
job = PricingRecalcJob(scope=scope, status="pending")
|
|
session.add(job)
|
|
return job
|
|
|
|
|
|
def claim_next_pending_pricing_job(session: Session) -> PricingRecalcJob | None:
|
|
stmt = (
|
|
select(PricingRecalcJob)
|
|
.where(PricingRecalcJob.status == "pending")
|
|
.order_by(col(PricingRecalcJob.created_at).asc())
|
|
)
|
|
bind = session.get_bind()
|
|
if bind is not None and bind.dialect.name == "postgresql":
|
|
stmt = stmt.with_for_update(skip_locked=True)
|
|
|
|
job = session.exec(stmt).first()
|
|
if job is None:
|
|
return None
|
|
|
|
job.status = "running"
|
|
job.attempts += 1
|
|
job.started_at = utc_now()
|
|
job.finished_at = None
|
|
job.error = None
|
|
session.add(job)
|
|
session.commit()
|
|
session.refresh(job)
|
|
return job
|
|
|
|
|
|
def _apply_pricing_snapshot(session: Session, computed_at: datetime) -> int:
|
|
from innercontext.api.products import _compute_pricing_outputs
|
|
|
|
products = list(session.exec(select(Product)).all())
|
|
pricing_outputs = _compute_pricing_outputs(products)
|
|
|
|
for product in products:
|
|
tier, price_per_use_pln, tier_source = pricing_outputs.get(
|
|
product.id, (None, None, None)
|
|
)
|
|
product.price_tier = tier
|
|
product.price_per_use_pln = price_per_use_pln
|
|
product.price_tier_source = tier_source
|
|
product.pricing_computed_at = computed_at
|
|
|
|
return len(products)
|
|
|
|
|
|
def process_pricing_job(session: Session, job: PricingRecalcJob) -> int:
|
|
try:
|
|
updated_count = _apply_pricing_snapshot(session, computed_at=utc_now())
|
|
job.status = "succeeded"
|
|
job.finished_at = utc_now()
|
|
job.error = None
|
|
session.add(job)
|
|
session.commit()
|
|
return updated_count
|
|
except Exception as exc:
|
|
session.rollback()
|
|
job.status = "failed"
|
|
job.finished_at = utc_now()
|
|
job.error = str(exc)[:512]
|
|
session.add(job)
|
|
session.commit()
|
|
raise
|
|
|
|
|
|
def process_one_pending_pricing_job(session: Session) -> bool:
|
|
job = claim_next_pending_pricing_job(session)
|
|
if job is None:
|
|
return False
|
|
process_pricing_job(session, job)
|
|
return True
|