Skip to content

Maintenance refactor - 2nd pass#72

Open
AESternberg wants to merge 30 commits into
jdeath:mainfrom
AESternberg:main
Open

Maintenance refactor - 2nd pass#72
AESternberg wants to merge 30 commits into
jdeath:mainfrom
AESternberg:main

Conversation

@AESternberg

Copy link
Copy Markdown
Contributor

Please note the Pending Questions section at the bottom. These were things that I came across but didn't want to change without your input or decision.

Description

This PR introduces an architectural modernization of the cruise tracker script focused on DRY (Don't Repeat Yourself) principles, rigid type-safety boundaries, clean multi-path request unification, and enhanced runtime resilience against unstable remote API responses.

Key Enhancements & Structural Themes

  • DRY Code Consolidation (_execute_api_request): Centralized duplicate network communication layers, header assembly mechanics (Access-Token, vds-id, AppKey), and validation routines into a single request engine. This eliminates redundant boilerplate code across downstream querying components.
  • Unified Multi-Path Execution (Path A & Path B Alignment): Standardized how automated reservations are processed by leveraging _build_checkout_url to synthesize clean link strings. Funneling this output back into parse_provided_URL unifies both Path A and Path B requests into an identical, predictable stream before hitting the downstream pricing modules.
  • Strict Domain Type Hardening: Implemented comprehensive type annotations across all primary routing hooks and utility managers. Generic tracking dictionaries have been systematically refactored to consume explicit structural interfaces and models (AccountInfo, CruiseURLParams, DiscountProfile, WatchListItem).
  • Defensive Boundary Execution (get_room_price_via_API): Enclosed deep multi-layered JSON payload traversals (such as Next.js RSC string processing and response.json().get("rooms") nodes) inside specialized try/except scopes. This safeguards the master script loop from unexpected root-level dictionary or list structure shifts during server-side modifications or outages.
  • Robust Input Normalization (get_final_payment_date): Standardized dynamic deadline offsets (75/90/120 day rules) by implementing a sanitization interface capable of translating raw text representations, arbitrary dash/slash timestamps, and standard datetime objects interchangeably.
  • Technical Debt & Scope Leak Remediations:
    -- Fixed a casing typo (self.all_Included = False) inside apply_overrides that accidentally mutated an unmapped property instead of updating the actual dataclass tracker flag.
    -- Cleaned up redundant buffering infrastructure (self._buffer) from the PrintRedirector log class.
    -- Standardized local variables, dictionary keys, and function configurations across the entire script to meet uniform PEP 8 snake_case patterns.

Pending Questions (Technical Debt)

  • I noticed two sets of ANSI color codes between this script and Browse*.py. For better maintainability, I recommend picking one for commonality.
  • I noticed a number of the API servers are hard-coded to use "royal" even though a cruise may be with Celebrity (for example, aws-prd.api.rccl.com/en/royal/web/commerce-api/catalog in get_new_beverage_price. Is that required, or should they go to the correct cruise line URL?
  • Speaking of get_new_beverage_price, it seems to me that this function is used for all add-ons, not just beverages. If so, I recommend renaming the function to reflect that, as it's better for understanding and maintenance
  • Is it possible to have a Club Royale ID without having a C&A number? If not, I propose moving that check under the C&A check (again, for streamlining and maintainability). Similarly for Blue Chip and Captain's Club
  • I want to understand your design: is it the intent that all guests would have the same stateroom subtype and category code in calculate_passenger_metrics? I think so, but wanted to confirm
  • Finally, I noticed all the passenger discount qualifiers were hard-coded to 'n' when translating to the various r0* fields in check_if_room_is_available? Is that by design, or should those values use what's read in from the config YAML?

@jdeath

jdeath commented May 31, 2026

Copy link
Copy Markdown
Owner

Still have at least a couple bugs:

XXXX Brilliance of the Seas BALCONY CB (Residency Discount) Not For Sale
Sent ntfy notification to 'https://ntfy.sh'.
        Available Rooms (non-discounted price) for 2 Adult and 0 Child on This Sailing Are:
ERROR: NameError: name 'newKey' is not defined

Fixed that, I immediately found another:

yalcaribbean\CheckRoyalCaribbeanPriceAES.py", line 1672, in get_orders
    reservations=getattr(watchItem, 'reservations', [])
                         ^^^^^^^^^
NameError: name 'watchItem' is not defined

I do not even have watch items. I can't imagine why that would not have been found in testing.

First, if booked cruise, should not get a notification if no longer on sale. I only notify if a watch list cruise is no longer available.

For your other questions:
I noticed two sets of ANSI color codes between this script and Browse*.py. For better maintainability, I recommend picking one for commonality.
Use the Check script ones. though I like the blue in the browse*

I noticed a number of the API servers are hard-coded to use "royal" even though a cruise may be with Celebrity (for example, aws-prd.api.rccl.com/en/royal/web/commerce-api/catalog in get_new_beverage_price. Is that required, or should they go to the correct cruise line URL?

I do not know. I do not have celbrite cruises, so do not know the URL. I bet they both resolve to same place, so no need to alter.

Speaking of get_new_beverage_price, it seems to me that this function is used for all add-ons, not just beverages. If so, I recommend renaming the function to reflect that, as it's better for understanding and maintenance

That is fine. it was only beverage when I first wrote it. it does alot more now

Is it possible to have a Club Royale ID without having a C&A number? If not, I propose moving that check under the C&A check (again, for streamlining and maintainability). Similarly for Blue Chip and Captain's Club

I do not think so. I think you need a C&A before you have a casino. same with celeb

I want to understand your design: is it the intent that all guests would have the same stateroom subtype and category code in calculate_passenger_metrics? I think so, but wanted to confirm
** I think so**, if they are in the same room.

Finally, I noticed all the passenger discount qualifiers were hard-coded to 'n' when translating to the various r0* fields in check_if_room_is_available? Is that by design, or should those values use what's read in from the config YAML?
** They should default to no, unless overridden by the account or the config.yaml . Config.yaml should override what is in the account**

@AESternberg

Copy link
Copy Markdown
Contributor Author

I have a few watch items and they were coming up OK; it's likely that not having them was another execution path I hadn't taken. I'm not surprised: what you found are some camelCase to snake_case conversions that were unfortunately missed (I corrected them manually when refactoring, so it's hard to catch them all, especially sincethe server keys are in camelCase and have to be kept like that). They're being flagged even without watch items because of how the Python interpreter parses the script (which I don't fully understand myself).

Thanks for answering the questions; I'll make those updates and submit an update.

@AESternberg

Copy link
Copy Markdown
Contributor Author

I'm still making changes based on your answers to my questions (thank you again!), but still had questions on the colors.

As background, I was looking to be consistent in the codes:

  • \033[<code>m means "Bright text, default background, normal weight", where code is 91-94
  • \033[1;<code>;40m means "Bold standard text, black background", where code is 31-34.

The currently used definitions are (you see that RED follows a different standard):
RED = '\033[1;31;40m'
GREEN = '\033[1;32m'
YELLOW = '\033[33m'
BLUE = '\033[94m' (from Browse*.py)

Here's how Google describes the differences:
Code Breakdown

  • \033[9m: High-intensity (bright) color text with default background.
  • \033[1;3;40m: Bold standard color text with a black background.

Detailed Parameter Comparison
ANSI escape sequences use numbers separated by semicolons to apply styles. Here is what each specific number inside your codes triggers:

Component \033[9m \033[1;3;40m
1 Not included (Normal weight) Bold or increased intensity
Foreground Color 9 (Bright Color) 3 (Standard Color)
Background Color Not included (Default terminal background) 40 (Black background)

Key Practical Differences

  • Visual Appearance: \033[9m uses the terminal's native "bright " color slot. \033[1;3;40m uses the "standard" color slot but renders it bold, while forcing the background behind that specific text to turn pitch black.
  • Terminal Backgrounds: If a user has a transparent or white terminal theme, \033[9m will blend in cleanly. \033[1;3;40m will draw an abrupt black box behind the text.
  • Compatibility: 9 belongs to the AIXterm high-intensity extension. It is widely supported by modern terminals but skipped by very old legacy systems. 1, 3, and 40 belong to the strict baseline ANSI standard and work everywhere.

So, which do you prefer, [9m style, [1;3;40m style, or mixed as it currently is?

@jdeath

jdeath commented May 31, 2026

Copy link
Copy Markdown
Owner

I am fine with mix, not so big on conforming to standards. Better yet, define a config.yaml OPTIONAL override for the colors.

I run in home assistant and the terminal colors took a lot of trial and error to get them to display correctly on the default white background in home assistant logs.

@AESternberg

Copy link
Copy Markdown
Contributor Author

How do you envision a YAML override for the colors? I would recommend a one-word toggle to pick between the two, with your current definitions as default. Then again, I doubt anyone beyond you and I would care, so it may just be better from a UI perspective to just stick with your defaults.

@jdeath

jdeath commented Jun 5, 2026

Copy link
Copy Markdown
Owner

something like below. then if empty strings, I think will be default color. I made one minor change to script (I messed up the check in API call". should be easy to merge back in or fix after your PR

colorOverride:

  • red: "XXXXX"
  • blue: "YYYYY"
  • yellow: "XXXX"

For the browse script, don't really care or a CLI argument

@AESternberg

AESternberg commented Jun 5, 2026 via email

Copy link
Copy Markdown
Contributor Author

@jdeath

jdeath commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Maybe both. I think fine control is good, with the option of simple providing a theme. So its:
colorOverride: bright or
colorOverride:
-red:YYY

  • yellow: XXX
  • blue: zzz

@AESternberg

Copy link
Copy Markdown
Contributor Author

Hi, jdeath,
It's been several days and this PR is still open. Did you have any more questions or concerns you'd like me to address?

@jdeath

jdeath commented Jun 15, 2026

Copy link
Copy Markdown
Owner

I haven't had a chance to test yet. I will soon

@AESternberg

Copy link
Copy Markdown
Contributor Author

Understood; it's been a pretty busy time for me as well. As always, feel free to let me know if I can help.

I did refactor Browse as well in the meantime, and made one more change to Check to just rearrange the functions to a more logical grouping (based on what they do). I was going to wait to submit until this one was merged, but can do so if you'd prefer. For my two cents, I think keeping them separate is better just from a "complexity of review" standpoint, but it's ultimately your call.

@jdeath

jdeath commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Still errors and have not had a successful run yet.

  1. Still ntfy on booked cruise no longer for sale. Do not want to notify in that case. Only notify if using watchlist and sold out
  2. code crashes due to bad variable.
ERROR: NameError: name 'watch_item' is not defined
Traceback (most recent call last):
  CheckRoyalCaribbeanPriceAES.py", line 1677, in get_orders
    reservations=getattr(watch_item, 'reservations', [])
                         ^^^^^^^^^^
NameError: name 'watch_item' is not defined

If you can, update:
params = {'brand': brand_code, 'includeCheckin': 'false'}
to
params = {'brand': brand_code, 'includeCheckin': 'true'}

that was a bug that should been fixed before.

I'll look into the browse script later.

@AESternberg

Copy link
Copy Markdown
Contributor Author

I surprised there are still errors and deeply apologize; I hate wasting your time and really thought I'd caught them all (there must be some execution paths I'm unable to test).

I've not yet pushed the other changes I was talking about, but will do so with that fix.

@AESternberg

Copy link
Copy Markdown
Contributor Author

Thanks for the update. That is certainly very frustrating, and I hope you're not losing patience with me (I don't have linked reservations, so it's hard to debug). Would you mind sharing the screen output you are seeing (redacting out personal information of course), so I can try to get as close to apples to apples as I can?

@jdeath

jdeath commented Jun 30, 2026

Copy link
Copy Markdown
Owner
XXX (Cabin None) has best price for XXXX of: 100.99 USD (now 119.0 USD)
Can't contact cruise line servers; please try again later
(program exception '404 Client Error: Not Found for url: https://aws-prd.api.rccl.com/en/royal/web/commerce-api/catalog/v2/XX/categories/pt_shoreX/products/XX?reservationId=XX&startDate=XX&currencyIso=USD&passengerId=XX')

above is the error. It did not print line number of anything else

@AESternberg

Copy link
Copy Markdown
Contributor Author

Thanks. Looking into it now...

@jdeath

jdeath commented Jun 30, 2026

Copy link
Copy Markdown
Owner

replace line 2096 with these two lines fixes linked reservations:
guestreservation_ID = guest.get("reservationId")
booking["bookingId"] = guestreservation_ID

Not sure why cabin number is none.

@AESternberg

Copy link
Copy Markdown
Contributor Author

Thanks! That gave me the right place to look, but opened up to potential side-effects, changing what's essentially read-only data from the server. Instead, I updated the WatchItemContext class to add the reservation ID and pass it in safely. The good news is that this fixed also looks to have corrected the Cabin None problem (which ended up being a side effect of the reservation ID bug).

I've also written some linked reservation mocks in the unit tests to shake out what's hopefully the last of these nasty little bugs!

@jdeath

jdeath commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Still does not work. Downloaded latest from your fork. Same issue as before
Cabin still none
Pulling the reservation ID from the order list instead of from the guest field.

Also looks like the loyalty number is not being used in the cruise cabin fare price check.

@jdeath

jdeath commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Yeh. It finally handles linked reservations. Addon price checks work and pulls correct prices per passenger.

A few things not quite the same:
Your Code: Dining: TRADITIONAL 08:30 PM
My Code: Dining: TRADITIONAL 08:30 PM Table Size: 04

You are not pulling in the cabin paid price from the API nor applying discounts correctly. This was a real pain to get working, took a lot of debugging and a touch of AI to parse the next.js code:
You Code: XX/XX/2027 Anthem of the Seas BALCONY CB (Residency Discount): Current Price 4581.48 USD
My code: XX/XX/2027 Anthem of the Seas BALCONY CB (Loyalty, Residency Discount): You have best price of 3989.48 USD (now 4355.48 USD not including 50 OBC)

This I might have added after you started working on it, and sometimes the API is buggy
Your Code: Check In Available and Not Completed
My code: XXX Check in Partially Complete, Boarding Time 12:00, XXX Check in Partially Complete, Boarding Time 12:00

@AESternberg

Copy link
Copy Markdown
Contributor Author

Hopefully everything is fixed, but I can't test table size or check-in with live data (because my cruise is still too far out). I did add quite a few test cases (including for those two changes), so I hope I got it. Finally. 🤞

@jdeath

jdeath commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Closer, but still many issues. You got the table size, but still cabin pricing issues

You are not applying the loyalty discount. You must apply the C&A number, not the email address. There appears to be a $100 discount, so need to ensure applying it correctly.

You are also not comparing the price. Since after final payment you should not alarm if lower, but should still have the same output as me

You:
Cruise Fare - Total 3622.86
XX/XX/2026 Brilliance of the Seas BALCONY CB (Residency Discount): Current Price 6915.2 USD

Me:
Cruise Fare - Total 3622.86
XX/XX/2026 Brilliance of the Seas BALCONY CB (Loyalty, Residency Discount): You have best price of 3622.86 USD (now 6815.2 USD)

Another reservation it does look like loyalty makes a $100 difference and you are not pulling the OBC. You are also not comparing the price

You
Cruise Fare - Total 3989.48 You Still Owe: 3489.48
Remaining Cruise Payment Balance is 3013.16 due XX/XXX/2027
XX/XX/2027 Anthem of the Seas BALCONY CB (Residency Discount): Current Price 4803.48 USD

Me:
Cruise Fare - Total 3989.48 You Still Owe: 3489.48
Remaining Cruise Payment Balance is 3013.16 due XX/XX/2027
XX/XX/2027 Anthem of the Seas BALCONY CB (Loyalty, Residency Discount): You have best price of 3989.48 USD (now 4703.48 USD not including 150 OBC)

Once you figure this out, be sure to double check logic for DP340 code is right. I cannot test that, but it is an important feature. It was tested extensively with others, so I want to not regress.

Check in is also not quite right when not fully checked in. In my case the boarding time should be available, even though partially checked in.

You:
Check In Available! Fetching boarding documentation data...
Passenger Check-In Status: IN_PROGRESS
Assigned Boarding Window: Not Selected

Me:
XXXFirstname Check in Partially Complete, Boarding Time 12:00, XXXFirstName Check in Partially Complete, Boarding Time 12:00

@AESternberg

Copy link
Copy Markdown
Contributor Author

I made sure to add a DP340 test to make sure it's getting passed to the server properly. I can add other test cases if you think of others.

@jdeath

jdeath commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Partial Check-in Fixed.

Loyalty number still not applied for cabin price check
Cabin price check does not compare to current price
Cabin price check does not report the OBC

I won't check again until you confirm everything is working.

@AESternberg

Copy link
Copy Markdown
Contributor Author

I won't check again until you confirm everything is working.
I can understand that, but I'm not working with the same data, and I'm not sure I'm seeing something missing. I hope you understand that I'm NOT half-assing it, and trying to do the best I can with the testing I can do. Would you mind correcting the data I do see, so I know what to expect on my end? Here's my current output:

Logging run to file: output.txt
Report generated 07/04/2026 5:26:40 PM
Only alerting for savings >= 0.00

Using Royal Caribbean for user
Royal Caribbean loyalty number will be used for checking cabin prices
C&A: ######### GOLD - 20 Shared Points (20 Individual Points)
Total Trips on Royal: 4 - Total Nights: 20

Reservation #xxxxxxx ()
05/10/2027 Wonder of the Seas Room xxxx (In this cabin: )
Dining: TRADITIONAL 05:00 PM
[PROMO] 50 EARLY BOOKING BONUS THRILL WATERPARK HIDEAWAY BEACH GREEN TEAL BLUE TEXT (cococay) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 40 EARLY BOOKING BONUS INTERNET GREEN TEAL BLUE TEXT (internet) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 25% OFF DELUXE BEVERAGE PACKAGE - INDEPENDENCE DAY SALE (Valid 2026-07-01 to 2026-07-08)
[PROMO] 30 DINING (dining) (Valid 2026-07-01 to 2026-07-08)
Rebook! 05/10/2027 Wonder of the Seas BALCONY 1D New price of 2502.96 USD, not including 100.00 USD OBC, is lower than 2662.96
(Cabin xxxx) has best price per night for VOOM SURF + STREAM Voyage Package - 4 Devices of: 57.99 USD

[WATCH] (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)
[WATCH] (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)
[WATCH] (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)

Reservation #xxxxxxxxx ()
05/10/2027 Wonder of the Seas Room xxxx (In this cabin: )
Dining: TRADITIONAL 05:00 PM
Onboard Credit of 150.00 USD
[PROMO] 50 EARLY BOOKING BONUS THRILL WATERPARK HIDEAWAY BEACH GREEN TEAL BLUE TEXT (cococay) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 40 EARLY BOOKING BONUS INTERNET GREEN TEAL BLUE TEXT (internet) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 25% OFF DELUXE BEVERAGE PACKAGE - INDEPENDENCE DAY SALE (Valid 2026-07-01 to 2026-07-08)
[PROMO] 30 DINING (dining) (Valid 2026-07-01 to 2026-07-08)
Rebook! 05/10/2027 Wonder of the Seas BALCONY 2D New price of 1757.64 USD, not including 100.00 USD OBC, is lower than 2119.64

[WATCH] (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)
[WATCH] (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)

Processing Prospective Cruise Watchlist...
Consider Booking! 11/04/2027 Freedom of the Seas DELUXE GS (Loyalty Discount) (Travel Protection, Prepaid grats): New price of 6982.20 USD, not including 300.00 OBC, is lower than watchlist price of 7711.20
12/08/2026 Celebrity Infinity INTERIOR I1 (All Included): You have the best price of 1.00 USD (now 4544.66 USD)

@jdeath

jdeath commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Your's looks good. Not sure why not checking cabin price or doing loyalty on my account. I'll try to poke around

@AESternberg

Copy link
Copy Markdown
Contributor Author

That's actually a relief. I was afraid I was still making a colossal blunder somewhere!

While you were doing that, I did some more deep digging into the execution paths and found what I think is why it was bypassing those blocks for you.

  1. The Loyalty / Watchlist Issue: For prospective watchlist items, main() in my refactor passes a cruiseURL string, forcing the code down what's labeled in get_cruise_price() as 'Path A' (for checking prospective cruises by URL). However, all of the Crown & Anchor loyalty resolution logic was trapped inside the active reservation block (Path B). I moved the loyalty asset extraction downstream to the base level of get_cruise_price() (where both paths 're-merge'), so that both paths dynamically apply the loyalty tier before calling apply_overrides(). This still works for my data, but as we've seen, that's not full testing (although I can add some more pytest cases to check it)

  2. The Cabin Price / Missing PaidPrice Issue: If a configuration item doesn't pass a baseline target paidPrice, the refactored script was hitting an early if paid_price is None: condition and executing a short-circuit return before printing the current tracking details. I adjusted that exit criteria so manual checks don't get swallowed. Again, this looks to work for me, but I haven't yet added test cases for it.

  3. Bonus Catch (Possible OBC discrepancy): While verifying the logic as I could, I noticed the script logs the live anonymous promo OBC from the web scrape, but drops actual historical reservation OBC (from get_OBC()) on the floor. For example, in my output:

Reservation #xxxxxxxxx ('friendly name for linked reservation')
05/10/2027 Wonder of the Seas Room xxxx (In this cabin: 'names')
Dining: TRADITIONAL 05:00 PM
Onboard Credit of 150.00 USD
...
Rebook! 05/10/2027 Wonder of the Seas BALCONY 2D New price of 1757.64 USD, not including 100.00 USD OBC, is lower than 2119.64
Is that intentional? I've worked out the code to correct it to match the OBC for the booking ('not including 150.00' in my case): I updated get_OBC() to return its parsed amount and mapped it into paid_price_struct['booked_obc']. That way, we can eventually do a true Net Price comparison (Fare - Booked OBC vs. Scraped 'Live' Fare - Live OBC) so a user doesn't accidentally tank their legacy credits on a rebook alert.

Let me know if you want me to add that with the (hopefully) clean, unified code while I work on the new test cases!

@jdeath

jdeath commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Here is a clue what is happening with loyalty number.
You make the URL correctly with the right URL
I see a CruiseURLParams object that is also filled correctly

then in your code, i go to the else statement:

if hasattr(account_info, 'access') and account_info.access and getattr(account_info.access, 'loyalty_number', None):
            url_params.loyalty_number = account_info.access.loyalty_number
        else:
            print(url_params.loyalty_number)
            url_params.loyalty_number = booking.get("loyaltyNumber") or getattr(account_info, 'loyalty_number', None)

there it is: url_params.loyalty_number already had the loyalty number in it. Then you override it with two things that are both None. I do not think the booking.get("loyaltyNumber") ever has loyalty number. I think your account struct is only if you specify your loyalty number, which is optional

Found another bug below causing OBC not to print. ignore strikethough
removing the overwriting allows price check to work, but OBC from booking is not being extracted or printed (not sure what).

It also is not comparing to the cabin price paid. I am not sure why, because your paid_price_struct is filled correctly with the cruise price

@jdeath

jdeath commented Jul 4, 2026

Copy link
Copy Markdown
Owner

OBC is tricky. You should just print OBCs and not include in price. I probably do not print the current booking OBC, as sometimes it is not loaded. OBC is only ever us US dollars. So you cannot just remove it from the price before price checking.

If you have the current OBC available, definitely print it . Perhaps if have old OBC, print a warning to check OBC differences before rebooking.

@jdeath

jdeath commented Jul 4, 2026

Copy link
Copy Markdown
Owner

You had a bug:
should be:

# Capture target price bounds if they exist
paid_price = paid_price_struct.get("paid_price") if paid_price_struct else None

you had paid_price_struct.get("paidPrice")

Once I fixed that and the other loyalty overwriting, everything appears ok at first glace. including OBC

Please fix those bugs and resubmit

@jdeath

jdeath commented Jul 5, 2026

Copy link
Copy Markdown
Owner

A couple other things:
Final payment date is being calculated wrong. for a 7 day cruise, it should be 90 days . your code is doing 75 days for mine.

If the cruise price is lower, the (now 4544.66 USD) should happen after the color reset so it is in the standard color. Your code does this right for the items, but not the cruise booking

- Removed bad loyalty_number override
- Added booked vs. live OBC extraction for future use (no functional change)
- Corrected paid_price key casing
- Fixed final_payment_date integer casting
- Corrected terminal color reset sequence
@AESternberg

Copy link
Copy Markdown
Contributor Author

Thanks so much for the feedback; it helped pinpoint the problem areas. I've made those changes, enhanced the test scripts, and should be all good now (famous last words, I know...)

As a note, I've left in the code that extracts booked vs. 'live' (scraped from the server for a rebooking) OBC for potential future use, but left it functionally alone (not part of any price calculation, and still printing the live one.

Standby on a check: I think I see one more thing after push... 😢

@AESternberg

Copy link
Copy Markdown
Contributor Author

Caught and corrected. Here's my output for verification:

Report generated 07/05/2026 2:22:10 PM
Only alerting for savings >= 0.00

Using Royal Caribbean for user xxx
Royal Caribbean loyalty number will be used for checking cabin prices
C&A: xxx GOLD - 20 Shared Points (20 Individual Points)
Total Trips on Royal: 4 - Total Nights: 20

Reservation #xxx (friendly1)
05/10/2027 Wonder of the Seas Room 6574 (In this cabin: name1, name2, name3)
Dining: TRADITIONAL 05:00 PM
[PROMO] 50 EARLY BOOKING BONUS THRILL WATERPARK HIDEAWAY BEACH GREEN TEAL BLUE TEXT (cococay) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 40 EARLY BOOKING BONUS INTERNET GREEN TEAL BLUE TEXT (internet) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 25% OFF DELUXE BEVERAGE PACKAGE - INDEPENDENCE DAY SALE (Valid 2026-07-01 to 2026-07-08)
[PROMO] 30 DINING (dining) (Valid 2026-07-01 to 2026-07-08)
05/10/2027 Wonder of the Seas BALCONY 1D (Loyalty Discount): Current Price 2452.96 USD
name1 (Cabin xxxx) has best price per night for VOOM SURF + STREAM Voyage Package - 4 Devices of: 57.99 USD

[WATCH] name2 (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)
[WATCH] name1 (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)
[WATCH] name3 (Cabin xxxx) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)

Reservation #xxx (friendly2)
05/10/2027 Wonder of the Seas Room 6576 (In this cabin: name4, name5)
Dining: TRADITIONAL 05:00 PM
Onboard Credit of 150.00 USD
[PROMO] 50 EARLY BOOKING BONUS THRILL WATERPARK HIDEAWAY BEACH GREEN TEAL BLUE TEXT (cococay) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 40 EARLY BOOKING BONUS INTERNET GREEN TEAL BLUE TEXT (internet) (Valid 2026-07-01 to 2026-08-01)
[PROMO] 25% OFF DELUXE BEVERAGE PACKAGE - INDEPENDENCE DAY SALE (Valid 2026-07-01 to 2026-07-08)
[PROMO] 30 DINING (dining) (Valid 2026-07-01 to 2026-07-08)
05/10/2027 Wonder of the Seas BALCONY 2D (Loyalty Discount): Current Price 1707.64 USD

[WATCH] name4 (Cabin yyyy) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)
[WATCH] name5 (Cabin yyyy) Deluxe Beverage Package price is higher than watch price: 60.00 USD (now 89.99 USD)

Processing Prospective Cruise Watchlist...
Consider Booking! 11/04/2027 Freedom of the Seas DELUXE GS (Loyalty Discount) (Travel Protection, Prepaid grats): New price of 6982.20 USD, not including 300.00 OBC, is lower than watchlist price of 7711.20
12/08/2026 Celebrity Infinity INTERIOR I1 (All Included): You have the best price of 1.00 USD (now 4544.66 USD)

One other question: I noted that the discount for 'fire' (r0s) is always set to 'n' despite what may be set in the config YAML; I'm curious as to why? I've put the hook in for using it dynamically in _build_checkout_url() should you want to activate it one day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants