feat(job): expose estimated and actual cost on jobs#658
Conversation
Add cost properties to the SDK's job objects, backed by the existing cost-estimate and billing endpoints in the generated client: - RapidataJob.estimated_cost / RapidataJobDefinition.estimated_cost: approximate cost estimates. Both endpoints return HTTP 409 while the job's first batch of rapids is still being created and priced, so the properties poll until the estimate is available (bounded by a timeout) and distinguish that transient state from genuine errors. - RapidataJob.actual_cost: the billed cost, derived by paging the customer's billing groups and matching the job's AudienceJob group; None when the job is not billed yet. New CostEstimate / ActualCost dataclasses keep the public surface thin instead of leaking the generated models, and are re-exported from the package roots. A BillingService wrapper exposes the billing API on OpenAPIService, mirroring the other service accessors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Review:
|
The billing costs-groups endpoint ranks a customer's groups by cost descending and caps page size at 100, so an unscoped scan for a cheap job paged through nearly the customer's entire billing history to reach its group at the bottom of the ranking (tens of sequential requests). Pass start_date=created_at so the backend (billable_hour >= toStartOfHour(startDate)) drops every group older than the job. A job's responses are only billed at or after creation, so no cost is missed. Also terminate on items-seen rather than an assumed page size, so a server-side page cap can't end the scan early. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
|
Review: expose estimated/actual cost on jobs Nice feature — clean dataclass wrapper around the generated models rather than leaking them, and the polling behavior for the 409 "not priced yet" window is a reasonable mirror of the SDK's existing wait/poll style. What the PR does
Potential issues
Minor / non-blocking
Summary |
Remove RapidataJob.actual_cost, the ActualCost dataclass and the BillingService wiring - the billed-cost lookup was slow and its value changed between reads, which is surprising for a property. Keep the estimated_cost property (fast, stable once priced) and slim CostEstimate to estimated_cost / datapoint_count / required_responses. Rewrite the docstrings for first-time SDK readers, dropping internal implementation detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
ReviewLooked at the full diff (all 3 commits squashed down to: Potential issue: unbounded-referee jobs poll for the full 5-minute timeout with a misleading errorPer the generated client's docstrings for both endpoints (
Since the response body doesn't appear to carry a distinguishing error code (at least none is referenced in the generated client), the SDK may not be able to tell the two 409 causes apart cheaply. Worth at least softening the Minor / non-blocking notes
Things that look solid
Nice, tightly-scoped feature. The unbounded-referee 409 case is the one thing I'd want addressed or at least explicitly called out before merge. |
Immediately after a definition/job is created the estimate is not yet priced. Besides the documented 409, the endpoint can signal this with a success + empty body, which the generated client returns as None. That None flowed into CostEstimate._from_model and raised AttributeError: 'NoneType' object has no attribute 'estimated_cost'. Treat a None result as "not ready" and keep polling until the estimate is available (or the timeout elapses), same as the 409 case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Review: feat(job): expose estimated and actual cost on jobsThanks for adding this — the
|
Document estimated_cost on job definitions and jobs: how to read it, the CostEstimate fields, and the caveat that it is an estimate rather than the final bill. Register the page in the Guides nav. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
ReviewOverall this is a clean, well-scoped addition — thin One correctness issue worth fixing before merge, plus a couple of minor/optional notes: 1. In def __init__(self, ...):
...
self.__estimated_cost: CostEstimate | None = None
@property
def estimated_cost(self) -> CostEstimate:
if self.__estimated_cost is None:
...
self.__estimated_cost = CostEstimate._from_model(model)
return self.__estimated_costBut Repro: job_definition = client.job.create_compare_job_definition(...)
_ = job_definition.estimated_cost # caches estimate for revision A
job_definition.update_dataset(new_datapoints) # revision B, totally different dataset
job_definition.estimated_cost # returns the stale revision-A estimate, silentlyThis fails silently (no exception, just wrong numbers), which is worse than the timeout/poll failure modes that were clearly designed for elsewhere in this PR. Suggest resetting 2. Blocking property with only debug-level feedback
Minor, and not unprecedented — 3. No automated tests for the polling/error branching
Nits
|
What
Adds cost properties to the SDK's job objects, backed by the cost-estimate and billing endpoints already present in the generated client (no backend changes).
RapidataJob.estimated_cost— approximate cost estimate for running the job to completion (job_job_id_cost_estimate_get).RapidataJobDefinition.estimated_cost— same, pre-run, via the definition endpoint (job_definition_definition_id_cost_estimate_get).RapidataJob.actual_cost— the billed cost, derived from the customer's billing groups (billing_costs_groups_get), matched on this job's id.Nonewhen the job isn't billed yet.Notes
CostEstimate/ActualCostdataclasses (re-exported fromrapidataandrapidata.rapidata_client) rather than leaking the generated models.BillingServicewrapper exposes the billing API onOpenAPIService, matching the existing service-accessor pattern.RapidataOrder(order creation is deprecated; no order estimate endpoint).Validation
python -c "from rapidata import RapidataClient, CostEstimate, ActualCost"→ OKpyright src/rapidata/rapidata_client→ 0 errorsmkdocs build(docs) → OK; auto-generated API reference picks up the new module/properties🔗 Session: https://node-4b6e8bdf.poseidon.rapidata.internal/