Skip to main content

EVENT_SCHEMAS — pricing-service

Sibling: API_CONTRACTS · APPLICATION_LOGIC · SYNC_CONTRACT

Strategic anchors: 04 Event-Driven Architecture · Standards / NAMING — Events

All events follow the canonical envelope (04 §3). Subjects use melmastoon.<service>.<aggregate>.<verb-past-tense>.v<n>. Pub/Sub topic-per-aggregate (e.g. melmastoon.pricing.rate_plan, melmastoon.pricing.quote, melmastoon.pricing.promotion, melmastoon.pricing.tax_rule, melmastoon.pricing.fee_rule, melmastoon.pricing.fx_snapshot, melmastoon.pricing.dynamic_suggestion).

Schemas live in JSON Schema 2020-12 under services/pricing-service/events/<subject>.schema.json and TypeScript types are generated to @melmastoon/contracts.


1. Envelope (recap)

{
"id": "01H8Z4N12345…", // ULID, unique per delivery attempt
"subject": "melmastoon.pricing.quote.created.v1",
"specVersion": "1.0",
"occurredAt": "2026-04-22T10:14:09.123Z",
"producer": "pricing-service@1.42.0",
"tenantId": "tnt_…",
"correlationId": "01H8Z4M…",
"causationId": "01H8Z4L…",
"traceparent": "00-…-…-01",
"schemaUri": "https://schemas.melmastoon.tech/pricing/quote.created.v1.json",
"data": { /* domain payload — see below */ }
}

Ordering key: <tenantId>:<aggregateId> (e.g. tnt_…:rate_…). Quotes use <tenantId>:<quoteId> so a created event always precedes its expired event.


2. melmastoon.pricing.rate_plan.created.v1

{
"ratePlanId": "rate_01H8Z4M0CNJW9N7QM4D3K6BVAN",
"tenantId": "tnt_01H8Z4M1QCV5RNSP2X4N6BYYGE",
"propertyId": "pty_01H8Z4M2QY8NQ4M5RX9V0Y6T9P",
"code": "BAR",
"category": "BAR",
"channelScope": "all",
"currency": "USD",
"shariaCompliant": false,
"status": "draft",
"version": 0,
"createdAt": "2026-04-22T10:14:09Z",
"createdBy": { "type": "user", "id": "usr_…", "displayName": "Layla M." }
}

JSON Schema:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "melmastoon.pricing.rate_plan.created.v1",
"type": "object",
"required": ["ratePlanId","tenantId","propertyId","code","category","channelScope","currency","shariaCompliant","status","version","createdAt","createdBy"],
"properties": {
"ratePlanId": { "type": "string", "pattern": "^rate_[0-9A-HJKMNP-TV-Z]{26}$" },
"tenantId": { "type": "string", "pattern": "^tnt_[0-9A-HJKMNP-TV-Z]{26}$" },
"propertyId": { "type": "string", "pattern": "^pty_[0-9A-HJKMNP-TV-Z]{26}$" },
"code": { "type": "string", "minLength": 1, "maxLength": 64 },
"category": { "enum": ["BAR","weekly","government","corporate","non_refundable","package","group"] },
"channelScope": { "enum": ["direct","ota","corporate","walk_in","group","all"] },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
"shariaCompliant": { "type": "boolean" },
"status": { "const": "draft" },
"version": { "type": "integer", "minimum": 0 },
"createdAt": { "type": "string", "format": "date-time" },
"createdBy": { "$ref": "common/actor-ref.schema.json" }
}
}

3. melmastoon.pricing.rate_plan.updated.v1

{
"ratePlanId": "rate_…",
"tenantId": "tnt_…",
"propertyId": "pty_…",
"version": 15,
"previousVersion": 14,
"changes": [
{ "field": "displayName", "before": { "en": "BAR" }, "after": { "en": "Best Available Rate" } },
{ "field": "refundability.cutoffHoursBefore", "before": 24, "after": 48 }
],
"status": "published",
"updatedAt": "2026-04-22T10:14:09Z",
"updatedBy": { "type": "user", "id": "usr_…" }
}

status reflects the current status after the change. Field-level changes are sufficient for downstream caches (pricing, sync, search) to invalidate without a full re-read.

4. melmastoon.pricing.rate_plan.archived.v1

{
"ratePlanId": "rate_…",
"tenantId": "tnt_…",
"propertyId": "pty_…",
"version": 16,
"reason": "deprecated",
"futureBookingsAtArchive": 12,
"futureBookingsHonoredUntil": "2026-09-30",
"archivedAt": "2026-04-22T10:14:09Z",
"archivedBy": { "type": "user", "id": "usr_…" }
}

futureBookingsAtArchive is the count of reservations holding a quote against this plan at archive time, surfaced for downstream auditing (legal/finance dashboards).

5. melmastoon.pricing.rate_rule.created.v1

{
"rateRuleId": "rru_01H8Z4M5VKBT8GZN3M9C5K0RZX",
"ratePlanId": "rate_01H8Z4M0CNJW9N7QM4D3K6BVAN",
"tenantId": "tnt_…",
"priority": 100,
"scope": {
"dateRange": { "start": "2026-05-01", "end": "2026-09-30" },
"daysOfWeek": ["fri","sat"],
"roomTypeIds": ["rmt_…"],
"occupancyBands": [{ "minAdults": 1, "maxAdults": 2 }]
},
"baseMicro": "150000000",
"currency": "USD",
"multiplier": 1.20,
"surchargeMicro": "0",
"los": { "minNights": 2, "discountPct": 5.0 },
"source": "manual",
"version": 0,
"createdAt": "2026-04-22T10:14:09Z",
"createdBy": { "type": "user", "id": "usr_…" }
}

source: "ai_accepted" carries an additional aiProvenance block — see §15.

6. melmastoon.pricing.rate_rule.updated.v1

Mirrors §5 plus a previousVersion and changes[] array (same shape as §3).

7. melmastoon.pricing.promotion.created.v1

{
"promotionId": "prm_01H8Z4M7…",
"tenantId": "tnt_…",
"code": "SUMMER10",
"discountKind": "percent",
"discountPct": 10.0,
"applicableRatePlanIds": ["rate_…"],
"applicableChannels": ["direct"],
"validFrom": "2026-05-01",
"validTo": "2026-09-30",
"usageCap": 1000,
"status": "draft",
"createdAt": "2026-04-22T10:14:09Z",
"createdBy": { "type": "user", "id": "usr_…" }
}

8. melmastoon.pricing.promotion.activated.v1 / .deactivated.v1

{
"promotionId": "prm_…",
"tenantId": "tnt_…",
"code": "SUMMER10",
"previousStatus": "draft",
"status": "active",
"transitionedAt": "2026-04-22T10:14:09Z",
"transitionedBy": { "type": "user", "id": "usr_…" },
"reason": "go_live"
}

9. melmastoon.pricing.quote.requested.v1

Fired at the start of CalculateQuote for analytics on funnel dropoff. Contains the request only, never derivation outputs.

{
"requestedQuoteId": "qte_…",
"tenantId": "tnt_…",
"propertyId": "pty_…",
"ratePlanCodeRequested": "BAR",
"stayWindow": { "start": "2026-05-12", "end": "2026-05-15" },
"roomTypeIds": ["rmt_…"],
"occupancy": { "adults": 2, "children": 0 },
"displayCurrency": "USD",
"promoCode": "SUMMER10",
"channel": "direct",
"bookerSessionRef": "bks_…",
"requestedAt": "2026-04-22T10:14:09Z"
}

10. melmastoon.pricing.quote.created.v1

The full pinned quote (canonical persistence event for reservation-service and analytics).

{
"quoteId": "qte_…",
"tenantId": "tnt_…",
"propertyId": "pty_…",
"ratePlanId": "rate_…",
"ratePlanVersion": 14,
"stayWindow": { "start": "2026-05-12", "end": "2026-05-15" },
"roomTypeIds": ["rmt_…"],
"occupancy": { "adults": 2, "children": 0 },
"currency": "USD",
"displayCurrency": "USD",
"fxSnapshotId": "fxs_…",
"fxRate": 0.014,
"promoApplied": { "id": "prm_…", "code": "SUMMER10", "redemptionId": "01H8Z4M9…" },
"totals": {
"nightCount": 3,
"subtotalMicro": "375000000",
"discountMicro": "37500000",
"feesMicro": "15000000",
"taxesMicro": "30000000",
"grandTotalMicro": "382500000"
},
"expiresAt": "2026-04-22T10:44:09Z",
"ttlSeconds": 1800,
"shariaGuardPasses": true,
"createdAt": "2026-04-22T10:14:09Z"
}

11. melmastoon.pricing.quote.expired.v1

{
"quoteId": "qte_…",
"tenantId": "tnt_…",
"propertyId": "pty_…",
"reason": "ttl", // "ttl" | "manual" | "inventory_failed" | "fx_invalidated" | "rate_plan_archived"
"expiredAt": "2026-04-22T10:44:09Z"
}

12. melmastoon.pricing.tax_rule.updated.v1

{
"taxRuleId": "tax_…",
"tenantId": "tnt_…",
"jurisdiction": { "country": "AF", "region": "Kabul" },
"scope": "room",
"rate": { "kind": "percent", "value": 10.0, "inclusive": false },
"validFrom": "2026-04-01",
"validTo": null,
"previousVersion": 3,
"version": 4,
"supersededRuleId": "tax_…prev",
"updatedAt": "2026-04-22T10:14:09Z",
"updatedBy": { "type": "user", "id": "usr_…" }
}

Subject is .updated.v1 for both creation of a successor and edits within an open window — consumers reconcile via validFrom/validTo/supersededRuleId.

13. melmastoon.pricing.fx_snapshot.updated.v1

{
"fxSnapshotId": "fxs_…",
"tenantId": "tnt_…", // null when global
"base": "AFN",
"quote": "USD",
"rate": 0.014,
"providerRef": "ecb_2026-04-22",
"capturedAt": "2026-04-22T06:00:00Z",
"staleAfter": "2026-04-23T06:00:00Z",
"hardExpireAt": "2026-04-25T06:00:00Z",
"publishedAt": "2026-04-22T06:00:01Z"
}

14. melmastoon.pricing.dynamic_suggestion.generated.v1

{
"suggestionId": "dps_…",
"tenantId": "tnt_…",
"propertyId": "pty_…",
"roomTypeId": "rmt_…",
"date": "2026-05-12",
"currency": "USD",
"baselineRateMicro": "150000000",
"suggestedRange": { "lowMicro": "160000000", "highMicro": "180000000" },
"signals": {
"occupancyRatio": 0.85,
"bookingPaceWow": 0.18,
"competitorRateProxyMicro": "175000000",
"eventCalendar": [{ "name": "Eid al-Fitr", "weight": 0.7 }]
},
"rationale": "Occupancy projected to 92%; competitor mean +12%; Eid demand window.",
"expiresAt": "2026-04-23T10:14:09Z",
"aiProvenance": {
"modelId": "vertex.gemini-1.5-pro@2026-03",
"promptVersion": "pricing/dynamic.v3",
"inputDigest": "sha256:abcd…",
"outputDigest": "sha256:wxyz…",
"redactionPolicy": "pii_v1"
},
"generatedAt": "2026-04-22T10:14:09Z"
}

15. melmastoon.pricing.dynamic_suggestion.accepted.v1

{
"suggestionId": "dps_…",
"tenantId": "tnt_…",
"ratePlanId": "rate_…",
"rateRuleId": "rru_…",
"chosenMicro": "168000000",
"currency": "USD",
"acceptedBy": { "type": "user", "id": "usr_…" },
"acceptedAt": "2026-04-22T10:18:09Z",
"reason": "Eid weekend demand"
}

A correlated melmastoon.pricing.rate_rule.created.v1 is published in the same UoW (same correlationId). The aiProvenance block on the generated rule mirrors §14.


16. Compatibility, evolution, and retention

ConcernRule
Backward compatAdd fields only; never remove or repurpose; new required fields ship as .v<n+1>
Forward compatConsumers MUST ignore unknown fields and pass them through to downstream sinks
RetentionAll pricing topics: 7 days hot in Pub/Sub; 365 days warm in BigQuery via Pub/Sub-to-BQ subscription; quote events also persisted to GCS Parquet for analytics
DLQ<topic>.dlq after 5 redeliveries; on-call paged when DLQ depth > 100 in 5 minutes
EncryptionCMEK on all pricing topics; per-tenant subkey for tenantId-scoped events
PIIQuote envelopes contain no booker PII (only opaque bookerSessionRef); promo codes are not considered PII

17. Subscriptions consumed by pricing-service (inbox)

SubjectTopicSubscriptionHandler
melmastoon.tenant.config_updated.v1melmastoon.tenant.configpricing.tenant_config.subRefreshTenantConfigCacheHandler
melmastoon.property.room_type.updated.v1melmastoon.property.room_typepricing.property_room_type.subReconcileRoomTypeLinkagesHandler
melmastoon.inventory.allocation.failed.v1melmastoon.inventory.allocationpricing.inventory_failed.subMarkQuotesStaleHandler
melmastoon.ai.suggestion.dynamic_pricing.v1melmastoon.ai.suggestionpricing.ai_dynamic.subIngestDynamicSuggestionHandler

All subscriptions use enableExactlyOnceDelivery=true plus an inbox dedupe table (pricing.inbox_messages keyed by messageId) to guarantee idempotency end-to-end.