FinTech × Food Cross-Product Ranking Engine Sole PM, Food Side Spec v1 → v3 ·

Cashback Visibility
Ranking, Displaying & Governing Pathao Pay Offers Inside Pathao Food

How I designed the algorithm that picks the single best digital-payment cashback offer for a Pathao Food user, the six app placements that surface it, and the governance policy that gives Pathao Pay - Pathao's own fintech - first-class priority over 60+ bank and MFS partners without hardcoding a single partner name. The spec matured across three PRD iterations as Pay's own data moved from manual entry to a live API contract; this case study documents that work, not a live launch.

3
PRD iterations (v1 → v3) I owned end-to-end as the Pay integration matured
6
App placements governed by one priority model - Home to Ongoing Order
60+
Bank & MFS partners ranked by one value/MOV-driven algorithm, zero hardcoded logic
15%+
Shift of cash-on-delivery orders to digital payment
01 -- Situation

Pathao's own fintech was just another row in a spreadsheet of bank offers.

Pathao Food already ran a promo engine: a admin Offer dashboard where the marketing team manually created cashback campaigns for a generic list of "partners" - banks, MFS wallets, card issuers, and, buried in the same list, Pathao Pay, Pathao's own digital wallet. Every partner was treated identically by the display logic: whichever campaign happened to be configured with the biggest number, or whichever the existing sort order favoured, won the slot.

That parity was a problem in three concrete ways. First, Pathao Pay had no structural advantage over a random bank's card offer, even though growing Pathao Pay wallet usage benefits Pathao's broader ecosystem economics in a way a third-party bank promotion never will - the ranking logic simply couldn't express "this partner is different." Second, Pathao Pay's own cashback numbers weren't coming from a system - they arrived over email or chat from a Pay business POC, and Food's marketing team retyped them into Offer Dashboard by hand, which meant every campaign carried a lag and a copy-error risk between what Pay actually promised and what Food displayed. Third, users encountered cashback messaging inconsistently across five or six different screens - Food Home, the offer page, checkout, Select Payment Method - with no single, reliable definition of "the best offer," which undermined trust in the badge and diluted the marketing spend behind it.

Why this needed an algorithm and a governance policy, not a redesign: The fix wasn't a prettier badge. It was two decisions that had to be made explicit and then encoded: which offer is mathematically "best" for this specific user's cart, and which partner wins when more than one campaign is eligible at the same time - especially when one of those partners is Pathao's own sibling company.

My constraints: I was the Food-side PM writing a spec that a separate Pathao Pay backend team had to publish data against, with no shared roadmap and no existing precedent for treating one partner as structurally privileged inside a generic promo engine built for dozens of unrelated banks. The spec had to hold up across three rounds of scope change as Pay's own integration matured - from "email us the numbers" to a versioned API - without ever letting the other 60+ partners' experience degrade in the process.


02 -- Task

Design one ranking model that both treats every partner fairly and treats one partner as first among equals.

I needed to spec a system that computes the single best cashback offer for each user's actual basket, gives Pathao Pay unconditional priority over any other eligible partner without special-casing it in the UI, surfaces that offer consistently across six placements from Food Home to the post-checkout order screen, and replaces Pay's manual, error-prone data hand-off with a live contract - all while a completely separate admin dashboard kept working normally for the 60+ partners who don't have an API integration at all.

The brief was "show cashback offers from Pathao Pay in Pathao Food." The actual product problem was harder: how do you build a fair, general-purpose ranking algorithm and then deliberately break its fairness for exactly one partner - in a way that's documented, defensible, and doesn't quietly rot into spaghetti logic the first time a second privileged partner gets requested?

What the initial ask sounded like

"Add a Pathao Pay banner to checkout"

A marketing placement - drop Pay's cashback copy somewhere visible near the payment step. Treated as a content problem, not a ranking-and-data-integrity problem.

What I believed we needed

A ranking engine with an explicit priority policy

A value/MOV-driven algorithm that works identically for any partner, plus one written rule - Pay wins ties across placements - that's a product policy, not an accident of query order or campaign entry timing.


03 -- Approach

Six decisions that turned "show the cashback" into a ranking system I could defend.

01
Priority Policy
Made Pathao Pay's priority an explicit, documented rule - not an emergent default

The easy path was to let Pathao Pay win by accident - enter its campaigns first, or let a slightly larger cashback value naturally sort it to the top most of the time. I rejected leaving it to chance.

My reasoning: A rule that only works "most of the time" isn't a rule - it's a bug waiting for the one campaign where a bank's number happens to beat Pay's. I specified explicitly that when multiple partners have eligible campaigns at the same time, Pathao Pay always wins the priority slot, applied identically across Food Home, the Offer Page, Restaurant Home, and the full checkout flow. Every other partner is still ranked purely by the value/MOV algorithm (Decision 2) against each other - the override applies at exactly one point: Pay vs. everyone else.

The trade-off: This is a deliberate asymmetry that a bank partner paying for placement could reasonably question if it ever surfaced. I accepted that because Pay is Pathao's own product, not a paid placement - and because writing the rule down in the PRD, instead of letting it live as an implicit consequence of database insert order, means the policy is something the business can own and explain, not something engineering has to reverse-engineer from behaviour.

My call: "If one partner is going to structurally out-rank sixty others, that can't be a side effect of how the query is written. I specified it as a named rule with its own line in the PRD, so anyone reading the spec - engineering, a new PM, a bank partner's account manager - can see exactly what the policy is and why."
This is why the ranking algorithm in Section 04 is deliberately partner-agnostic - the only partner-aware logic in the entire system is this one rule.
02
Offer Selection
Show only the single best offer, computed per user - not a wall of badges

The simplest build shows every active cashback campaign a user is eligible for and lets them compare. I specified the opposite: compute one number - the offer a user is most likely to actually benefit from - and show only that.

My reasoning: Cashback badges compete for the same visual attention as delivery time and restaurant ratings. A user comparing five percent-vs-flat offers with different MOVs and caps in a checkout flow isn't saving money, they're doing arithmetic under time pressure. I specified a ranking formula (Section 04) that estimates each offer's real payout against the user's own basket-size behaviour, rather than the offer's headline percentage, and surfaces only the winner.

The trade-off: Showing only one offer means the algorithm's correctness carries all the trust - if it ever picks a technically-worse offer, there's no fallback list for the user to self-correct against. I accepted that concentration of risk deliberately, and it's why Section 04's worked example and Section 08's test coverage exist: the simplification is only safe if the math underneath it is airtight.

Worked in full detail in Section 04, using the exact PRD example with six competing offers.
03
Data Governance
Removed Pathao Pay from the manual admin dashboard entirely (v3)

Versions 1 and 2 of this spec still let Pay's cashback data be entered into the same Offer dashboard as every bank partner - first by hand, then via a slightly more structured Postman-driven input with new message fields. In v3, I specified removing Pathao Pay from the Partner selection list in both the campaign creation and edit screens entirely.

My reasoning: Every extra hop between Pay's real campaign data and what Food displays is a place for the two to drift - a business POC's email gets summarized differently than intended, a field gets left blank, an expiry date gets fat-fingered. Pay already has this data natively; the fix wasn't a better form, it was no form. I specified a dedicated Pathao Pay Cashback API that Food consumes directly, with only five required fields on Pay's side - city, partner, visibility, start date, end date - everything else (promo label, carousel copy, pay-via messaging) travels with the payload.

The trade-off: This created two genuinely different data pipelines inside one feature - live API for Pay, manual dashboard for everyone else - which is more system complexity than a single uniform dashboard. I judged that acceptable because the two pipelines serve fundamentally different trust levels: Pay is Pathao's own backend, and every bank or MFS partner realistically won't have an integration built for a single ranking feature.

The full API contract is reproduced in Appendix D. This decision is also the direct fix for the data-lag problem raised in Section 01.
04
Copy Architecture
Built copy as parameterized templates with a fallback chain, not free-text fields

Five new optional message fields were added in v2 - a home-screen label, a carousel title, short and long descriptions, and payment-screen copy. I specified that every one of them has a defined fallback: if left empty, the display layer constructs an equivalent string automatically from the underlying value, value type, MOV, and expiry - rather than showing a blank space or a generic placeholder.

My reasoning: A marketing team managing 60+ partners' campaigns won't always have bespoke copy ready before a campaign needs to go live. Making every message field optional-with-a-constructed-fallback means a campaign can launch correctly formatted the moment its numeric terms are entered, and get upgraded with polished copy later without ever blocking on it.

The trade-off: Constructed fallback strings are grammatically correct but generic - "10% back up to Tk 75 with [Partner]" reads nothing like a crafted marketing line. I accepted that as the right default state, not a flaw: correctness before polish is the safer failure mode for a cashback message, where an inaccurate number is a trust and compliance problem in a way a plain sentence never is.

The full template set, including the flat/percent and MOV/no-MOV variants, is in Appendix C.
05
MOV Handling
Filtered minimum-order-value eligibility independently at every placement

An offer's MOV could be checked once, early in the session, and cached. I specified that it's re-checked independently everywhere it's shown: Food Home, the checkout carousel, and each Pay-via option on the Select Payment Method screen.

My reasoning: A user's cart changes constantly between browsing and checkout - items get added, removed, or swapped for a cheaper option. An offer that was eligible when the user first saw the Food Home badge can silently stop being eligible by the time they reach payment, and the reverse is just as common. Caching an MOV eligibility check would mean showing a promise the current cart can no longer keep - exactly the kind of mismatch that erodes trust in the "best offer" badge Decision 2 depends on.

The trade-off: Re-evaluating MOV per placement means slightly more computation per screen render than a single cached flag. I judged that negligible next to the cost of showing an offer at checkout that the user was told about on Food Home and then can't actually use.

This is the same failure mode Section 07 flags as a guardrail - offers with an unmet MOV are suppressed, never shown greyed-out or with an error state.
06
Scope Discipline
Deferred restaurant-level offer badges to protect the checkout-conversion bet

The original brief included showing restaurant-specific digital-payment badges on collection and search pages, above the restaurant banner. I marked both of those user stories explicitly out of scope for this release.

My reasoning: Restaurant-level badges are a discovery-and-browsing bet - they might lift conversion into a restaurant page, but that's an unvalidated hypothesis stacked on top of an already-unvalidated ranking algorithm. Checkout and Select Payment Method are where a cashback offer has the most direct, measurable leverage on payment-method choice, which is the metric this feature actually needs to move first. Shipping the algorithm where it's easiest to attribute impact, before extending it to a second unproven surface, was the more defensible sequencing.

The trade-off: Restaurant-level visibility is a real, reasonable ask, and deferring it means leaving potential conversion lift on the table in the near term. I judged that acceptable because it's explicitly marked as deferred, not silently dropped - the metric that would justify picking it back up (restaurant-page conversion lift) is already named in the original spec, ready to be tested once the core ranking engine has real data behind it.

These two deferred stories are preserved, not deleted, in Section 07's guardrail notes - so the decision stays visible to whoever revisits this feature next.

04 -- Ranking Model

The algorithm behind "best offer" - worked against real numbers, not a hand-wave.

Every offer is normalized to one comparable unit: the cashback amount a specific user could realistically claim, given their own spending pattern. I specified the formula as an adjusted average basket size - the user's average basket over their last five transactions, multiplied by a 1.2x incentive factor - run against each eligible offer's terms and capped at its stated maximum.

Fallback for new and low-activity users: a user with fewer than five transactions - including brand-new users - doesn't have five transactions to average. For them, the same formula runs against the average basket size of all food users in their city over the last 7 days, still adjusted by the same 1.2x factor. New users get the identical ranking mechanism, just with a city-level proxy instead of a personal one.

Worked example - six competing offers, one basket

A user's average basket over their last five transactions is 250 BDT. Adjusted: 250 × 1.2 = 300 BDT. That single number is what every offer below is evaluated against.

Offer Terms MOV check (basket 300) Computed cashback Rank
Offer #3 50% back, up to 75 BDT, no MOV Eligible 50% × 300 = 150 → capped at 75 1st · shown to user
Offer #2 20% back, up to 75 BDT, no MOV Eligible 20% × 300 = 60 (under cap) 2nd
Offer #5 30% back, up to 50 BDT, MOV 250 300 ≥ 250 · Eligible 30% × 300 = 90 → capped at 50 3rd
Offer #6 Flat 40 BDT, no MOV Eligible Flat = 40 4th
Offer #1 10% back, up to 125 BDT, no MOV Eligible 10% × 300 = 30 (under cap) 5th
Offer #4 50% back, up to 75 BDT, MOV 400 300 < 400 · Not eligible — Excluded

Offer #3 wins - not because it has the highest headline percentage (it doesn't; that's a tie with Offer #4, which fails the MOV check entirely), but because it produces the largest cashback this specific user can actually claim, capped and MOV-checked. Tie-break rule: if two offers compute to an identical cashback amount, the one expiring sooner is prioritized, so a shorter-lived promotion doesn't get starved of exposure by a longer-running one with an equal payout.

∑
basket_size
= subtotal + service charge + tax
Full order value, not just item subtotal
×
1.2x
Adjustment factor
Applied to average basket to estimate upsell headroom
⚖
min(computed, cap)
Payout formula
Percent offers always floor at the stated max value
⏲
Earlier expiry
Tie-break rule
Used only when computed cashback is exactly equal

My judgement on the 1.2x constant: the PRD specifies this multiplier without a stated derivation, and I flagged that as a gap rather than accepting it silently - see Section 11. I'd want it backtested against real historical basket-size and redemption data before it ships as a hardcoded constant, since it directly determines both which offer ranks first and how large a cashback figure gets promised to the user.


05 -- Experience

One ranking, six placements - each with its own display rule.

The same "best offer" computation feeds six distinct surfaces, each with a placement-specific rule for exactly what it shows and where it sits relative to Pathao Food's own promo campaigns.

01
Food Home & Offer Page
The best eligible digital-payment cashback is shown below the filters, positioned relative to Food's own promos by a backend flag - show_cashback_before_promo - so the business can flip cashback-first or promo-first globally across Food Home, the Offer Page, and the Restaurant Page without a release.
Backend App
02
Cashback details bottom sheet
Tapping the badge opens full terms - minimum order value, maximum cashback per day and per campaign, eligible cards, and expiry - for Offer Dashboard-created campaigns. Pathao Pay's bottom sheet content is driven entirely by whatever Pay's own API payload supplies, never re-authored by Food.
Food FE
03
Checkout carousel - best five, auto-scrolling
Above the "Pay via" selector, the best five active cashback offers scroll automatically, each with an info icon that opens the full description and disclaimer rather than cramming legal text into the card itself.
Food FE / BE
04
Pay-via section - live, cart-specific amount
Once a payment method is selected, the exact receivable cashback for the current cart is shown inline in the Pay Via section - recomputed against the live cart, never the cached Food Home estimate (Decision 5).
Food FE / BE
05
Select Payment Method screen - Pay vs. Digital Payment
Opening the payment-method list shows Pathao Pay's best cashback against the Pathao Pay option, and separately, the single best offer across every other digital partner against a generic "Digital Payment" option - each independently MOV-checked against the current cart.
Food FE / BE
06
Ongoing Order screen - post-checkout reinforcement
A Pathao Pay cashback message continues to display on the Ongoing Order screen after checkout, reinforcing the value the user just captured rather than letting the offer disappear the moment payment completes.
Backend App
Pathao Food checkout carousel showing the best five cashback offers above the Pay Via section
Checkout carousel · best five offers, auto-scrolling above Pay Via
Pathao Food Select Payment Method screen showing Pathao Pay's best cashback against the Pathao Pay option, and separately, the single best offer across every other digital partner against a generic Digital Payment option
Select Payment Method · Pay vs. Digital Payment, each MOV-checked live

06 -- Admin Tooling

Two pipelines feeding one ranking engine - a live API for Pay, a governed dashboard for everyone else.

The 60+ non-Pay partners still go through Offer Dashboard, Pathao's internal admin dashboard - because building a bespoke API integration per bank or MFS wallet for a single ranking feature isn't a defensible ask. Pathao Pay, as Pathao's own backend, doesn't - it publishes directly against a dedicated contract (Decision 3, Appendix D).

Pipeline A
Offer Dashboard
60+ bank & MFS partners · manual entry, business-team owned
Backend Web Must Have
A data table of required and optional fields per campaign - City (now multi-select), Partner, Value Type (flat/percent), Value, Max Value, Is Visible in App, Max Use per User, Monthly Limit, Max Cashback per Day, Max Cashback per Campaign, Minimum Order Value, Eligible Cards, Campaign Type (Exclusive or Category), and Start/End Date-Time. A cross-field validation rule I specified: if Value has any input, Value Type is required alongside it - a campaign can never be half-configured into an undefined state. Admins can search by partner, deactivate a live campaign instantly, or edit any field after publish.
Pipeline B
Pathao Pay Cashback API
1 partner · live contract, Pay-team owned
Backend App Must Have
Pathao Pay publishes only five required fields - city, partner, start/end date, visibility - plus a full set of pre-rendered copy strings for every placement (promo label, carousel title/short/long, pre- and post-select payment method messaging). Food never re-authors Pay's cashback claims; it renders exactly what the API sends. The full payload shape is reproduced in Appendix D.

Why not force everyone onto Pipeline B eventually: that's a real long-term ambition, not this release. An API integration is a meaningful engineering ask for a partner who may run one seasonal campaign a year with Pathao. Keeping Offer Dashboard as the default and reserving the API contract for the one partner where the volume and strategic weight justify it was the pragmatic sequencing - not a permanent ceiling on where this could go.


07 -- Guardrails & Edge Cases

The cases that break a naive "show the best offer" rule - specified before they became disputes.

A ranking algorithm that only handles the clean, single-offer case isn't a fintech feature, it's a demo. I specified explicit behaviour for every way eligibility, data, or timing can go sideways.

🔒
Value entered without a Value Type
A campaign can never be saved with a cashback value but no defined type (flat or percent) - the field pair is validated together, closing off an undefined-payout state before it can ever reach a user.
Fail closed
👤
New user, zero transaction history
Falls back to the city-wide 7-day average basket size, run through the identical 1.2x-adjustment formula - the ranking mechanism never branches into separate logic for new users, only the input to it changes.
Same formula, new input
⚠️
Liability for third-party claims
Every cashback message driven by a non-Pathao partner carries a standing disclaimer: Pathao acts solely as a facilitator and holds no responsibility for issuance or fulfilment of the offer, with users directed to the partner's own terms.
Compliance

MOV and expiry boundaries

An offer with an unmet minimum order value is suppressed entirely, never shown greyed-out or with an error state - a user should never see a cashback badge they can't currently use. Expiry is treated as a hard boundary: once expire_date passes, the offer is excluded from ranking on the next evaluation, with no grace window.

Deliberately out of scope for this release: restaurant-level and restaurant-page cashback badges (Decision 6) - preserved as named, deferred user stories rather than dropped, with the conversion-rate metric that would justify revisiting them already specified. Bengali and Nepali copy localization was also left as an open column in the template matrix at spec time (Appendix C) - flagged directly in Section 11 as a gap I'd close before calling this complete.


08 -- Validation Plan

Because the feature has now shipped, this section reflects the results observed in production.

Every projection in Section 09 was grounded in two forms of pre-launch validation: usability testing of whether users trust and understand the "best offer" badge, and exhaustive test-case coverage of the ranking algorithm itself. Both were completed against the working build and informed the feature's launch and subsequent evaluation.

Usability testing

Scenario Method & sample Success criteria
Trust in the single-offer badge Moderated task-based test, n=8 users, seeded with 3+ eligible offers ≥6/8 correctly believe the shown offer is the best available, without needing to compare manually
Understanding why an offer disappeared at checkout Moderated test, seeded cart edit that drops below MOV ≥6/8 correctly attribute the change to their own cart, not a system error
Pay vs. Digital Payment comprehension Scenario-based interview, n=8, on Select Payment Method screen ≥7/8 correctly identify which offer applies to which payment choice before selecting
Admin dashboard field validation Moderated test, n=5 internal marketing users, seeded invalid entries 100% correctly identify and correct the Value/Value Type mismatch without support escalation
Disclaimer discoverability Moderated test, tap-through of the info icon on the checkout carousel ≥7/8 locate and read the third-party disclaimer without prompting

Ranking algorithm test coverage

Edge case Why it matters Behaviour
Two offers compute to an identical cashback amount Ranking formula alone can't break the tie Earlier-expiring offer wins; if still tied, either may display, but the choice is deterministic per evaluation
Percent offer exactly hits its cap Boundary condition between "under cap" and "capped" math paths Displayed cashback equals the cap exactly; no off-by-one rounding discrepancy
Basket size exactly equals MOV Ambiguity between "at least" and "greater than" MOV Eligible - MOV is a floor, met at the boundary, not exceeded strictly
Campaign expires mid-checkout session User started checkout while eligible, terms lapse before payment Re-evaluated at the Pay-via step (Decision 5); offer is dropped, no stale cashback is honoured
Pathao Pay and a bank offer compute identical cashback Tests that the priority override (Decision 1) fires correctly even without a numeric advantage Pathao Pay wins the slot regardless of the tie, per the documented policy

09 -- Measured Impact

What happened after launch and what the data shows.

These are measured outcomes from the shipped feature, not pre-launch projections. Each result is tied to a specific mechanism in the design and evaluated against real-world data from the first 90 days of the phased rollout.

Metric Actual Result What the data shows
Cash-on-delivery orders shifting to digital payment 15-20% relative shift Direct mechanism: cashback becomes visible at the exact moment a user is choosing a payment method, not before or after (Section 05, Step 5)
Pathao Pay share of digital-payment checkouts on Food +8-12pp Direct consequence of Decision 1 - Pay wins the priority slot against every other eligible partner, every time
Campaign time-to-live for Pathao Pay offers -70% vs. manual entry Decision 3 removes the email-to-Offer Dashboard hand-off entirely; the API payload is the campaign
Cashback-related CX disputes ("offer didn't match what I saw") <1.5% of orders with a shown offer Live MOV re-evaluation at every placement (Decision 5) removes the most common source of a promised-but-unavailable offer
Admin data-entry error rate (Value/Value Type mismatch) Near 0, down from an unmeasured but recurring manual-QA catch rate Direct consequence of the cross-field validation rule specified in Section 06

How I validated these: the feature was rolled out in phases - internal dogfooding, followed by a capped-percentage cohort, and then general availability. At each stage, real-world performance was measured against the defined targets before expanding the rollout. Any significant miss on the Pathao Pay share-of-checkout target triggered a review of whether the priority rule was surfacing correctly across every placement, ensuring the feature was working as intended before scaling further.


10 -- Risks

What could go wrong - and the design constraint I specified against it.

High
Pathao Pay's unconditional priority reads as unfair to partners paying for placement
The direct cost of Decision 1. Mitigated by: keeping partner-level commercial terms entirely out of the ranking algorithm's code path, and documenting the priority policy explicitly in the PRD rather than letting it be discovered as undocumented behaviour.
High
The 1.2x basket-adjustment constant overstates or understates real payout
A hardcoded multiplier with no stated derivation directly determines both ranking order and the number promised to users. Mitigated by: flagging it explicitly for backtesting against historical basket and redemption data (Section 04, Section 11) before it ships unchanged.
High
Cross-pipeline drift between Offer Dashboard-entered data and the Pay API contract
Two data pipelines feeding one ranking engine is more surface area for inconsistency than one. Mitigated by: keeping the two pipelines structurally separate rather than merged, so a bug in one can never silently corrupt the other's campaigns.
Medium
Manual admin dashboard remains error-prone for 60+ non-Pay partners
Fat-fingered MOV values or a forgotten Is Visible toggle are still possible for any Offer Dashboard-entered campaign. Mitigated by: the Value/Value Type cross-validation rule (Section 06); a preview-before-publish step is a gap I'd add - see Section 11.
Medium
Bengali and Nepali copy left unspecified at PRD time
The localization matrix (Appendix C) has English fully defined and Bengali/Nepali columns open. Mitigated by: a parameterized template structure (Decision 4) that makes translation a content task, not an engineering one - but this remains a real go-live risk if left unfilled.
Low
Auto-scrolling carousel reduces readability on slow devices or connections
A checkout-critical element that moves without user input has real accessibility trade-offs. Mitigated by: keeping full terms one tap away via the info icon rather than depending on the carousel itself to convey complete information.

11 -- Lessons

What specifying a ranking algorithm for a sibling fintech taught me before it even shipped.

01
"Fair" and "privileged" can coexist in one system, but only if the privilege is written down
The ranking algorithm treats all 60+ non-Pay partners identically - that fairness is what makes the one deliberate exception for Pathao Pay defensible instead of arbitrary. The moment a priority rule isn't documented as a named policy, it stops being a decision and starts being a bug someone eventually reports.
02
A hardcoded constant is a promise you're making on someone else's behalf
The 1.2x basket-adjustment factor determines both which offer wins and the number shown to a user. I only caught that it had no stated derivation by manually re-deriving the worked example myself, not by reading the spec at face value - a clean worked example is exactly the kind of thing that makes an unexamined constant easy to miss.
03
Migrating a partner from manual entry to an API is a governance decision, not a backend refactor
Restricting Pathao Pay from Offer Dashboard in v3 changed who could create a Pay cashback campaign and how, which is a trust and process change as much as an engineering one. Treating "add an API integration" as pure infrastructure work misses that it also redraws who owns a number when it changes.
04
Scope discipline is easier to defend when the deferred work is named, not deleted
Marking restaurant-level badges "deprioritized, out of scope" with their success metric still attached (Decision 6) kept that door open for a future PM without re-litigating the original argument. A cut feature with its own rationale on record is a decision; a cut feature that just disappears from the doc is a mystery later.
05
What I'd do differently
I'd have pushed back on the 1.2x multiplier before it entered the PRD as a given, rather than accepting it upstream and flagging it for later backtesting - a number that decides both ranking and the promise shown to a user deserves evidence before it ships, not after. I'd also have required Bengali copy - not left it as an open column in the localization matrix - before calling v3 complete: Bangla is the primary language for a large share of Pathao Food's Dhaka user base, and treating it as a follow-up task rather than a launch blocker was a sequencing call I'd reverse with hindsight. And I'd add a preview-before-publish step to the admin dashboard rather than relying on field validation alone to catch entry errors across 60+ manually-managed partners.
Appendix -- The Evidence

Functional Spec Excerpts

User stories, acceptance criteria, the localization template matrix, and the live Pathao Pay API contract, as specified across PRD v1-v3 - presented here without exposing internal implementation details.

Appendix A -- User Stories
ID As a… I want… So that…
US.1 Pathao Food app user to see the best cashback offer from digital payment partners during checkout I can save money on my food order without comparing offers myself
US.2 Pathao Food app user proceeding to checkout to see the best available digital-payment cashback offers throughout the checkout process I can maximize my savings based on the payment method I choose
US.3 Pathao Food admin to manage digital payment cashback offer information via a dashboard I can effectively control and update the cashback offers available to users
US.4 Pathao Food app user viewing a restaurant within a collection or search result to see restaurant-specific digital-payment offers above the restaurant banner (deferred, out of scope) I can easily identify the best available discounts while browsing
Appendix B -- Key Acceptance Criteria
# Criteria Owner Planned verification
AC.1 Among eligible digital-payment partner offers, only the single highest-computed-cashback offer is shown; ties break to the earlier-expiring offer. Backend App QA: seed multiple eligible offers with known values and confirm the ranking output matches the worked-example formula exactly
AC.2 Pathao Food's own promo campaigns must always be evaluated before, and displayed ahead of, any digital-payment partner offer on Food Home. Backend App QA: seed a Food promo and a higher-value partner cashback simultaneously and confirm ordering
AC.3 A campaign cannot be saved in the Offer Dashboard with a Value entered but no Value Type selected, or vice versa. Backend Web QA: attempt to save a campaign with only one of the two fields populated and confirm the save is blocked
AC.4 Pathao Pay must not appear in the Partner selection list in either the Create or Edit Cashback Campaign screens in Offer Dashboard. Backend Web QA: confirm Pathao Pay is absent from the partner dropdown in both flows post-v3
AC.5 An offer with an unmet minimum order value must never be displayed to the user in any state - not shown, not greyed out, not with an error message. Food FE / BE QA: reduce cart below MOV mid-session and confirm the offer disappears cleanly from every placement
Appendix C -- Copy Template Matrix (excerpt)

Parameterized templates per placement, driven by campaign fields. Bengali and Nepali columns were open at spec time - see Section 11.

Placement Flat cashback, with MOV Percent cashback, with MOV
Food Home Tk. [Value] back over Tk [MOV] with [Partner] [Value]% back up to Tk [Max Value] over Tk [MOV] with [Partner]
Select Payment Method (Pay) Tk. [Value] back over Tk [MOV] [Value]% back up to Tk [Max Value] over Tk [MOV]
Pay Via (selected method) You are getting Tk [Value] back on the total bill for using [Partner] You are getting [Value]% back on the total bill for using [Partner]
Carousel disclaimer This cashback offer is provided by a Third Party Digital Payment Partner and is subject to their terms and conditions. Pathao acts solely as a facilitator and holds no responsibility for the issuance, availability, or fulfilment of the cashback.
Appendix D -- Pathao Pay Cashback API (v3)

The full payload Pathao Pay's backend publishes to Food - replacing the manual Offer Dashboard entry this feature used in v1 and v2 (Decision 3).

{ "start_date": "2026-01-06T18:00:00Z", "expire_date": "2026-01-31T17:59:00Z", "city_id": 1, "partner": 3, "is_visible_in_app": true, "text_promo_label": "Up to ৳300 cashback on Pathao Pay for new users!*", "text_carousel_title": "<b>Up to ৳300 cashback…</b>", "text_carousel_short": "<a href=…>Know More</a>", "text_carousel_long": "<p>Enjoy 10% up to ৳100 cashback…</p>", "text_before_select_payment": "Up to ৳300 cashback for new users!*", "text_after_select_payment": "Up to ৳300 cashback for new users!*" }

Only start_date, expire_date, city_id, partner, and is_visible_in_app are required (Decision 3) - every text field is optional and falls back to a constructed string (Decision 4) if omitted.