sammy786 commited on
Commit
b579145
·
1 Parent(s): dba62f5

open coupons priced, UPI asides

Browse files
app/channels.py CHANGED
@@ -18,7 +18,7 @@ from typing import Dict, List, Optional
18
 
19
  from availability import channel_availability
20
  from card_catalogue import CATALOGUE_BY_ID
21
- from offers import active_offers_for, best_open_offer, best_conditional_offer
22
  from scoring_engine import TxnContext, score_transaction, _r2
23
 
24
  # Brands that are themselves a booking platform: when the user's query resolved
@@ -428,6 +428,9 @@ def compare_channels(
428
  open_inr, open_text, open_code, open_prebook = best_open_offer(ctx.offers, price, category, ch["channel"])
429
  # Claimable only on a first booking. Reported, never priced.
430
  cond_inr, cond_text, cond_code, _cond_prebook = best_conditional_offer(ctx.offers, price, category, ch["channel"])
 
 
 
431
  # A bank offer and a public promo code are NOT assumed to stack. Most
432
  # Indian OTAs make them mutually exclusive, and we have no evidence
433
  # either way for any given pair - so take the better of the two rather
@@ -469,6 +472,12 @@ def compare_channels(
469
  "emi_offer_inr": top.get("emi_offer_inr", 0.0),
470
  "emi_offer_text": top.get("emi_offer_text"),
471
  "emi_offer_code": top.get("emi_offer_code"),
 
 
 
 
 
 
472
  "open_discount_inr": _r2(open_inr),
473
  "open_discount_text": open_text,
474
  # Named so the user can actually type it. An offer we price but cannot
 
18
 
19
  from availability import channel_availability
20
  from card_catalogue import CATALOGUE_BY_ID
21
+ from offers import active_offers_for, best_open_offer, best_conditional_offer, best_upi_offer
22
  from scoring_engine import TxnContext, score_transaction, _r2
23
 
24
  # Brands that are themselves a booking platform: when the user's query resolved
 
428
  open_inr, open_text, open_code, open_prebook = best_open_offer(ctx.offers, price, category, ch["channel"])
429
  # Claimable only on a first booking. Reported, never priced.
430
  cond_inr, cond_text, cond_code, _cond_prebook = best_conditional_offer(ctx.offers, price, category, ch["channel"])
431
+ # Claimable only by paying UPI instead of the card. Reported, never
432
+ # priced - like EMI, the user hears the trade and decides.
433
+ upi_inr, upi_text, upi_code = best_upi_offer(ctx.offers, price, category, ch["channel"])
434
  # A bank offer and a public promo code are NOT assumed to stack. Most
435
  # Indian OTAs make them mutually exclusive, and we have no evidence
436
  # either way for any given pair - so take the better of the two rather
 
472
  "emi_offer_inr": top.get("emi_offer_inr", 0.0),
473
  "emi_offer_text": top.get("emi_offer_text"),
474
  "emi_offer_code": top.get("emi_offer_code"),
475
+ # UPI-only coupon at this channel (EMTUPI, UPIPAY): claimable by
476
+ # paying UPI instead of the card, forfeiting its rewards. A
477
+ # labelled aside, never in cash_off / net_cost / savings.
478
+ "upi_offer_inr": _r2(upi_inr),
479
+ "upi_offer_text": upi_text,
480
+ "upi_offer_code": upi_code,
481
  "open_discount_inr": _r2(open_inr),
482
  "open_discount_text": open_text,
483
  # Named so the user can actually type it. An offer we price but cannot
app/offers.py CHANGED
@@ -587,6 +587,37 @@ def best_conditional_offer(offs: List[Dict], amount: float, category: Optional[s
587
  return _best_unlinked(offs, amount, category, channel, conditional=True)
588
 
589
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  def _best_unlinked(offs, amount, category, channel, conditional: bool):
591
  """conditional=False -> discounts anyone can actually take today.
592
  conditional=True -> real, quoted, but gated on something we cannot check."""
 
587
  return _best_unlinked(offs, amount, category, channel, conditional=True)
588
 
589
 
590
+ def best_upi_offer(offs: List[Dict], amount: float, category: Optional[str] = None,
591
+ channel: Optional[str] = None):
592
+ """Best open discount that only a UPI payment can claim (EMTUPI, UPIPAY).
593
+
594
+ A UPI coupon is real, checkable money - the user just has to pay by UPI
595
+ instead of the card we are recommending, forfeiting the card's rewards. So
596
+ like EMI it is a labelled aside, never inside the card answer or the price:
597
+ the user hears the trade in so many words and decides. Only unlinked rows;
598
+ a card-linked offer confined to a payments app stays dropped outright
599
+ (scoring_engine._offer_applies), because no card can claim it as a card.
600
+ Returns (rupees, text|None, code|None)."""
601
+ best, note, code = 0.0, None, None
602
+ for off in (offs or []):
603
+ if not off.get("requires_upi_app"):
604
+ continue
605
+ if is_card_linked(off) or off.get("requires_debit") or off.get("requires_emi"):
606
+ continue
607
+ if off.get("requires_new_user"):
608
+ continue
609
+ if off.get("requires_promo_code") and not (off.get("promo_code") or "").strip():
610
+ continue
611
+ och = off.get("channel", "both")
612
+ if channel and och != "both" and och != channel:
613
+ continue
614
+ val = offer_value(off, amount, category)
615
+ if val > best:
616
+ best, note = val, _display(off.get("text") or off.get("evidence"))
617
+ code = (off.get("promo_code") or "").strip() or None
618
+ return best, note, code
619
+
620
+
621
  def _best_unlinked(offs, amount, category, channel, conditional: bool):
622
  """conditional=False -> discounts anyone can actually take today.
623
  conditional=True -> real, quoted, but gated on something we cannot check."""
app/offers_data/curated_offers.json CHANGED
@@ -7463,5 +7463,208 @@
7463
  "source": "https://www.ixigo.com/offers",
7464
  "last_verified": "2026-07-29",
7465
  "evidence": "Up to ₹2,000 Off on Domestic Flights with HDFC Bank Credit Card + Interest Free EMI"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7466
  }
7467
  ]
 
7463
  "source": "https://www.ixigo.com/offers",
7464
  "last_verified": "2026-07-29",
7465
  "evidence": "Up to ₹2,000 Off on Domestic Flights with HDFC Bank Credit Card + Interest Free EMI"
7466
+ },
7467
+ {
7468
+ "id": "of_easemytrip__pct_a8a240",
7469
+ "merchant_key": "easemytrip",
7470
+ "merchant_name": "EaseMyTrip",
7471
+ "applies_to_issuer": "",
7472
+ "card_id": null,
7473
+ "valid_from": "2026-08-02",
7474
+ "valid_to": "2026-08-31",
7475
+ "last_verified": "2026-08-02",
7476
+ "min_spend": 0,
7477
+ "max_discount": 5000,
7478
+ "network": "any",
7479
+ "geo": "any",
7480
+ "categories": [
7481
+ "travel_flights"
7482
+ ],
7483
+ "channel": "online",
7484
+ "requires_debit": false,
7485
+ "requires_new_user": false,
7486
+ "requires_promo_code": true,
7487
+ "requires_upi_app": false,
7488
+ "requires_prebooking": false,
7489
+ "excluded_card_ids": [],
7490
+ "type": "pct",
7491
+ "value": 5.0,
7492
+ "promo_code": "EMT2026",
7493
+ "source": "https://www.easemytrip.com/offers/coupon.html",
7494
+ "evidence": "5% instant discount on flight bookings Rs. 5,000 EMT2026"
7495
+ },
7496
+ {
7497
+ "id": "of_easemytrip__pct_85872c",
7498
+ "merchant_key": "easemytrip",
7499
+ "merchant_name": "EaseMyTrip",
7500
+ "applies_to_issuer": "",
7501
+ "card_id": null,
7502
+ "valid_from": "2026-08-02",
7503
+ "valid_to": "2026-08-31",
7504
+ "last_verified": "2026-08-02",
7505
+ "min_spend": 0,
7506
+ "max_discount": 750,
7507
+ "network": "any",
7508
+ "geo": "any",
7509
+ "categories": [
7510
+ "travel_flights"
7511
+ ],
7512
+ "channel": "online",
7513
+ "requires_debit": false,
7514
+ "requires_new_user": false,
7515
+ "requires_promo_code": true,
7516
+ "requires_upi_app": true,
7517
+ "requires_prebooking": false,
7518
+ "excluded_card_ids": [],
7519
+ "type": "pct",
7520
+ "value": 5.0,
7521
+ "promo_code": "UPIPAY",
7522
+ "source": "https://www.easemytrip.com/offers/upi-payment.html",
7523
+ "evidence": "5% up to Rs. 750 This offer is valid for payment done via UPI method UPIPAY"
7524
+ },
7525
+ {
7526
+ "id": "of_easemytrip__pct_1e10da",
7527
+ "merchant_key": "easemytrip",
7528
+ "merchant_name": "EaseMyTrip",
7529
+ "applies_to_issuer": "",
7530
+ "card_id": null,
7531
+ "valid_from": "2026-08-02",
7532
+ "valid_to": "2026-08-31",
7533
+ "last_verified": "2026-08-02",
7534
+ "min_spend": 0,
7535
+ "max_discount": 1500,
7536
+ "network": "any",
7537
+ "geo": "any",
7538
+ "categories": [
7539
+ "travel_hotels"
7540
+ ],
7541
+ "channel": "online",
7542
+ "requires_debit": false,
7543
+ "requires_new_user": false,
7544
+ "requires_promo_code": true,
7545
+ "requires_upi_app": true,
7546
+ "requires_prebooking": false,
7547
+ "excluded_card_ids": [],
7548
+ "type": "pct",
7549
+ "value": 15.0,
7550
+ "promo_code": "UPIPAY",
7551
+ "source": "https://www.easemytrip.com/offers/upi-payment.html",
7552
+ "evidence": "15% up to Rs. 1,500 This offer is valid for payment done via UPI method UPIPAY"
7553
+ },
7554
+ {
7555
+ "id": "of_easemytrip__pct_bb7bf3",
7556
+ "merchant_key": "easemytrip",
7557
+ "merchant_name": "EaseMyTrip",
7558
+ "applies_to_issuer": "",
7559
+ "card_id": null,
7560
+ "valid_from": "2026-08-02",
7561
+ "valid_to": "2026-08-31",
7562
+ "last_verified": "2026-08-02",
7563
+ "min_spend": 0,
7564
+ "max_discount": 150,
7565
+ "network": "any",
7566
+ "geo": "any",
7567
+ "categories": [
7568
+ "bus"
7569
+ ],
7570
+ "channel": "online",
7571
+ "requires_debit": false,
7572
+ "requires_new_user": false,
7573
+ "requires_promo_code": true,
7574
+ "requires_upi_app": true,
7575
+ "requires_prebooking": false,
7576
+ "excluded_card_ids": [],
7577
+ "type": "pct",
7578
+ "value": 7.0,
7579
+ "promo_code": "UPIPAY",
7580
+ "source": "https://www.easemytrip.com/offers/upi-payment.html",
7581
+ "evidence": "7% up to Rs. 150 This offer is valid for payment done via UPI method UPIPAY"
7582
+ },
7583
+ {
7584
+ "id": "of_easemytrip__flat_3039f3",
7585
+ "merchant_key": "easemytrip",
7586
+ "merchant_name": "EaseMyTrip",
7587
+ "applies_to_issuer": "",
7588
+ "card_id": null,
7589
+ "valid_from": "2026-08-02",
7590
+ "valid_to": "2026-08-31",
7591
+ "last_verified": "2026-08-02",
7592
+ "min_spend": 0,
7593
+ "max_discount": null,
7594
+ "network": "any",
7595
+ "geo": "any",
7596
+ "categories": [
7597
+ "travel_flights"
7598
+ ],
7599
+ "channel": "online",
7600
+ "requires_debit": false,
7601
+ "requires_new_user": false,
7602
+ "requires_promo_code": true,
7603
+ "requires_upi_app": true,
7604
+ "requires_prebooking": false,
7605
+ "excluded_card_ids": [],
7606
+ "type": "flat",
7607
+ "value": 400.0,
7608
+ "promo_code": "EMTUPI",
7609
+ "source": "https://www.easemytrip.com/",
7610
+ "evidence": "Instant Discount of Rs.400 has been applied successfully. Please note- EMTUPI Coupon is valid only on payments made via UPI"
7611
+ },
7612
+ {
7613
+ "id": "of_easemytrip__flat_7dde75",
7614
+ "merchant_key": "easemytrip",
7615
+ "merchant_name": "EaseMyTrip",
7616
+ "applies_to_issuer": "",
7617
+ "card_id": null,
7618
+ "valid_from": "2026-08-02",
7619
+ "valid_to": "2026-08-31",
7620
+ "last_verified": "2026-08-02",
7621
+ "min_spend": 0,
7622
+ "max_discount": null,
7623
+ "network": "any",
7624
+ "geo": "domestic",
7625
+ "categories": [
7626
+ "travel_flights"
7627
+ ],
7628
+ "channel": "online",
7629
+ "requires_debit": false,
7630
+ "requires_new_user": false,
7631
+ "requires_promo_code": true,
7632
+ "requires_upi_app": false,
7633
+ "requires_prebooking": false,
7634
+ "excluded_card_ids": [],
7635
+ "type": "flat",
7636
+ "value": 400.0,
7637
+ "promo_code": "EMTNCF",
7638
+ "source": "https://www.easemytrip.com/",
7639
+ "evidence": "EMTNCF applied Congratulations! Zero Convenience Coupon is applied. You saved Rs.400 per passenger on domestic flight"
7640
+ },
7641
+ {
7642
+ "id": "of_easemytrip__flat_3dca2f",
7643
+ "merchant_key": "easemytrip",
7644
+ "merchant_name": "EaseMyTrip",
7645
+ "applies_to_issuer": "",
7646
+ "card_id": null,
7647
+ "valid_from": "2026-08-02",
7648
+ "valid_to": "2026-08-31",
7649
+ "last_verified": "2026-08-02",
7650
+ "min_spend": 0,
7651
+ "max_discount": null,
7652
+ "network": "any",
7653
+ "geo": "international",
7654
+ "categories": [
7655
+ "travel_flights"
7656
+ ],
7657
+ "channel": "online",
7658
+ "requires_debit": false,
7659
+ "requires_new_user": false,
7660
+ "requires_promo_code": true,
7661
+ "requires_upi_app": false,
7662
+ "requires_prebooking": false,
7663
+ "excluded_card_ids": [],
7664
+ "type": "flat",
7665
+ "value": 1500.0,
7666
+ "promo_code": "EMTNCF",
7667
+ "source": "https://www.easemytrip.com/",
7668
+ "evidence": "Zero Convenience Coupon is applied. You saved Rs.1500 per passenger on international flight Convenience fees not applicable EMTNCF"
7669
  }
7670
  ]