End To End Flow
1. Happy Path
DEC
The initial activities come from CPP,
which produces them to a topic called {{ dc }}.{{ brand }}.cpp.activity. Once consumed by DEC, these activities are converted to DEC's domain object, CPPActivity.
DEC then calls the service PPH with the promoCode, and PPH returns some extra static information regarding that promotion, which allows DEC to enrich the different rewardProgress that a single CPPActivity object can have with products, rewardType, productRewardType, tierDefinition and expirationDate of the matching rewardScheme coming from PPH. PromoName, promoUrl, tags and brand are also enriched in the CPPActivity object itself.
The enriched CPPActivity objects are then converted to JSON and put inside a CloudEvent (a kind of wrapper object, you can think
of it like an envelope) with some metadata that identifies the contained CPPActivity object (timestamps, id, type, source, subject and brand) without the
need to deserialise the CPPActivity object, and they are produced to the Kafka topic {{ brand }}.cpp.enriched.events

In all of our applications, timestamps are propagated by using a binder that wraps the domain object and either an object that stores timestamps or the CloudEvent wrapper it came from (also containing the timestamps as metadata), which will then be used to create the new CloudEvent by coping over the data. These timestamps allow us to get metrics and give us an idea of how long the whole or a part of the flow takes at any given time. All these wrapper classes exist in the common library
TIM
TIM consumes the data produced by DEC, and filters the relevant ones by their tags (defined as TIM_FILTER_TAG in the
environment configMap). Then, they are keyed by the accountId, promoCode and attemptId, which allows TIM to keep a state
and retrieve those values by their keys (this state is persisted using RocksDB). This allows TIM
to make sure that when the same event comes in more than once, we will ignore the subsequent events instead of propagating them again (deduping stage).
The logic inside TIM's RewardStateProcessor for different event statuses:

The processed events are then separated by their tags into regular events or unqualification events and put into
CloudEvents once more before being produced to the output topic {{ brand }}.relevant.events.
https://miro.com/app/board/uXjVKv-MmDM=/
FRED
FRED receives the events from upstream and filters them based on the config. If according to the config the event needs to be processed further, it would call DES to get a decision. Once it gets the decision back, if the has-decision header is set to 'true', this event will be sent to BCG.
DES
Takes each RewardProgress in the CloudEvent data, and if the activityType is either "FREE_SPINS", "FREE_BET" or "GAMING_BONUS", a DECISION is returned for that CloudEvent, otherwise, the STATUS in the CloudEvent returned will be NO_DECISION. DES includes the delivery preferences for message type (e.g. toast/full screen). The response from DES has a has-decision header set to 'true' or 'false', so that FRED doesn't need to deserialise it.
BCG, OSG & MSQ
BCG filters and removes expired messages as well as those lacking a delivery preference. Onsite message content is resolved by retrieving message templates from Prismic via CSL. BCG will send an onsite message to OSG, which will respond with a 200 code if the customer was onsite and message was delivered or a 404 if the customer is not on-site and the message couldn't be delivered. In the later case, the message is queued in MSQ. Queued messages are stored in a Dynamo DB before being resent when a customer comes back online.
For the REWARD_AWARDED message only, once it gets to the new cycle on Monday, BCG doesn’t try to send it directly to OSG, and instead it always goes to the queue. This is not to interrupt customer journey with a full screen message. For UNQUALIFICATION, it will go through to OSG.
2. Customer spends reward before message has been seen
This scenario shouldn't really be possible for SBG since messages will be received in both desktop and mobile when
the customer is online, but can happen for other flavours.
In this case, a DeleteMessageInstruction is produced in TIM to the topic msq.inbound.delete.{{ env }}, so that MSQ
deletes the REWARD_AWARDED message from the queue and the customer doesn't receive it (since we consider they are already aware
of the reward).
3. Customer doesn't qualify for week one
These messages come from the topic {{ dc }}.{{ brand }}.eppm.activity.
DEC has a specific EPPM processor for the Week One Unqualification. These messages will be produced to the topic
{{ brand }}.relevant.unqualification.events which is directly consumed by FRED (skipping TIM since no filtering is necessary).
4. Customer doesn't qualify for week two
These messages come from the same topic as activity progress messages ({{ dc }}.{{ brand }}.cpp.activity) and are consumed by the cpp-activity-consumer in DEC.
These messages will be produced to the topic {{ brand }}.cpp.enriched.events and sent to TIM, which will filter these messages to a separate
output and send them to FRED to continue a similar flow as in the happy path.
