Skip to main content

AI_INTEGRATION — pricing-service

Sibling: APPLICATION_LOGIC · EVENT_SCHEMAS · SECURITY_MODEL

Strategic anchors: 08 AI Architecture · 02 §11 AI Layer

The pricing-service consumes one AI capability — Dynamic Pricing Suggestion — provided by ai-orchestrator-service (which routes to Vertex AI Gemini models on GCP). Suggestions are advisory only. They never mutate live rates without an explicit human approval (HITL) action; they never directly affect a guest-facing quote.


1. Capability summary

PropertyValue
Capability idpricing.dynamic_suggestion
Providerai-orchestrator-service (Vertex AI Gemini-1.5-Pro, region me-central2)
ModeAdvisory + HITL gate
OutputDynamicPricingSuggestion aggregate with rate range and rationale
TriggerManual (revenue manager UI) and scheduled (nightly batch per property)
SLA< 8 s p95 per suggestion request
Cost budget≤ $0.04 per suggestion (input ≤ 4 KB, output ≤ 1 KB)
Auditmelmastoon.pricing.dynamic_suggestion.generated.v1 + AIProvenance block

The pricing-service is never the AI executor. It only:

  1. Builds the request signal payload (deterministic, redacted).
  2. Calls ai-orchestrator-service via the AIClient port.
  3. Persists the response as a DynamicPricingSuggestion.
  4. Validates and applies the human-accepted choice as a RateRule.

2. Signal inputs (built locally, redacted)

The signal payload is assembled by SuggestionSignalCollector, which queries siblings through their public/internal APIs (no direct DB reads) and applies a fixed redaction policy.

interface SuggestionSignals {
occupancyRatio: number; // 0..1, from inventory-service
bookingPaceWow: number; // week-over-week pace ratio, from analytics-service
avgDailyRateMicro: bigint; // ADR for the property/room-type, last 28 days
competitorRateProxyMicro?: bigint; // optional, from rate-shopping-connector-service (anonymised)
eventCalendar: Array<{ name: string; date: string; weight: number }>; // events-service
weather?: { tempC: number; precipMm: number }; // optional, weather connector
shariaCompliantPlan: boolean; // local — narrows model output
currency: CurrencyCode;
historicalElasticity?: number; // estimated from past suggestion outcomes
}

Redaction rules (enforced at the boundary, not in the model):

  • No booker PII ever leaves the pricing service. Signals are aggregated; per-reservation data is forbidden in the prompt.
  • Competitor proxy values are pre-rounded to the nearest 5% and capped at the 95th percentile to remove identifiability.
  • Tenant id is never sent to the model; the orchestrator handles tenant isolation via its own session token.

3. Request envelope to ai-orchestrator-service

POST /v1/inferences:invoke
X-Capability-Id: pricing.dynamic_suggestion
Authorization: Bearer <service-mesh-jwt>

{
"capabilityId": "pricing.dynamic_suggestion",
"tenantContext": { "tenantId": "tnt_…", "propertyId": "pty_…", "roomTypeId": "rmt_…", "date": "2026-05-12" },
"signals": { /* see §2 */ },
"constraints": {
"currency": "USD",
"minMicro": "100000000", // safety floor; never suggest below
"maxMicro": "500000000", // safety ceiling; never suggest above
"maxRangeWidthPct": 25, // refuse low/high spread > 25%
"shariaCompliant": false
},
"preferences": {
"languages": ["en"],
"rationaleStyle": "concise"
},
"idempotencyKey": "01H8Z…"
}

The orchestrator routes to Vertex AI, applies pricing/dynamic.v3 prompt, validates the model output against the output schema, records provenance, and returns:

{
"result": {
"baselineMicro": "150000000",
"range": { "lowMicro": "160000000", "highMicro": "180000000" },
"currency": "USD",
"rationale": "Occupancy projected to 92%; competitor mean +12%; Eid demand window.",
"signals": { /* echoed */ }
},
"provenance": {
"modelId": "vertex.gemini-1.5-pro@2026-03",
"promptVersion": "pricing/dynamic.v3",
"inputDigest": "sha256:abcd…",
"outputDigest": "sha256:wxyz…",
"redactionPolicy": "pii_v1",
"latencyMs": 4123,
"costMicroUsd": 38000
}
}

4. HITL gate

A suggestion never becomes a price by itself. The AcceptDynamicPricingSuggestionUseCase enforces:

  1. Actor must hold a HITL role (revenue_manager, gm, owner).
  2. The chosenMicro must lie within [range.lowMicro, range.highMicro] (bounded by safety constraints from §3).
  3. The suggestion must be status: "generated" and not past expiresAt (default 24 h).
  4. The resulting RateRule carries source: "ai_accepted" and inherits the suggestion's aiProvenance so every guest-facing quote can be traced back through: Quote.derivation.steps[*].notes → RateRule.aiProvenance → DynamicSuggestion.aiProvenance → AI orchestrator log.

Rejected suggestions are persisted with status: "rejected", the rejecting actor, and an optional reason; they are surfaced in the MELMASTOON.PRICING.DPS_REJECTED analytics dashboard so the AI prompt/version can be tuned over time.


5. Output schema (validated by orchestrator AND by pricing-service)

{
"type": "object",
"required": ["baselineMicro","range","currency","rationale"],
"properties": {
"baselineMicro": { "type": "string", "pattern": "^[0-9]+$" },
"range": {
"type": "object",
"required": ["lowMicro","highMicro"],
"properties": {
"lowMicro": { "type": "string", "pattern": "^[0-9]+$" },
"highMicro": { "type": "string", "pattern": "^[0-9]+$" }
}
},
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
"rationale": { "type": "string", "minLength": 16, "maxLength": 1000 }
}
}

Defensive checks performed in pricing-service after deserialisation:

  • low ≤ baseline ≤ high
  • (high - low) / baseline ≤ maxRangeWidthPct
  • low ≥ minMicro && high ≤ maxMicro
  • currency matches the room type's plan currency

Any failure → MELMASTOON.AI.OUTPUT_INVALID, suggestion is not persisted, an audit event melmastoon.audit.ai_output_rejected.v1 is published, and the operator UI shows "AI suggestion unavailable; please retry".


6. AIProvenance and auditing

Every DynamicPricingSuggestion and every AI-derived RateRule carries the canonical AIProvenance block defined in 08 §5:

interface AIProvenance {
modelId: string; // immutable model+version pin
promptVersion: string; // immutable prompt version
inputDigest: string; // sha256 of redacted prompt
outputDigest: string; // sha256 of model output
redactionPolicy: string; // 'pii_v1' etc.
latencyMs?: number;
costMicroUsd?: number;
}

Provenance is written to two places:

  1. The pricing.dynamic_suggestions.ai_provenance JSONB column.
  2. The audit-service via melmastoon.audit.ai_invocation.v1 (emitted by the orchestrator) and melmastoon.audit.ai_decision.v1 (emitted by pricing-service on accept).

Auditors can replay any historical guest quote and see exactly which model produced the rule, which signals were fed in (digests can be matched against orchestrator-side stored prompts during a 90-day retention window), and which human approved it.


7. Safety, compliance, and refusal policy

The model must refuse (return a structured rejection that the orchestrator translates to MELMASTOON.AI.REFUSED) when:

  • Constraint payload violates Sharia rules (shariaCompliant=true and prompt would suggest interest-bearing logic).
  • Signals indicate insufficient data (occupancy unknown for > 50% of the lookback window).
  • The orchestrator detects that the input includes prohibited content (PII tokens, profanity, prompt-injection markers).

Refusals are persisted as dynamic_suggestions.status='rejected' with rejected_by={type:'system',id:'ai-orchestrator-service'} and a redacted reason. They never block authoring of a manual rule.


8. Cost & rate-limit guardrails

GuardrailLimitEnforcement
Per-property generation rate10 / minuteAPI Gateway rate-limit
Per-tenant generation rate60 / minuteAPI Gateway rate-limit
Per-tenant daily cost cap$200/dayai-orchestrator-service budget gate; over-budget → MELMASTOON.AI.BUDGET_EXCEEDED
Auto-trigger nightly batch1 per (property × room_type × date)scheduler dedupe key
Suggestion expiry24 hDB expires_at

9. Future capabilities (deferred)

  • Promo-code suggestion (pricing.promo_suggestion): suggest codes/caps to maximise revenue with elasticity-aware constraints. Not implemented; see SERVICE_RISK_REGISTER for the rationale (data sparsity).
  • Length-of-stay incentive optimiser: same shape as dynamic pricing but advises LOS discount curves. Tracked in roadmap.

Both will land as additional capability ids on ai-orchestrator-service and additional use cases here, mirroring the patterns above.