Do you do astrology
DOB: 19-08-1996 TOB: 11:55 AM POB: ROHTAK Q: Is my career growth will be outside of india or in india ? If its abroad which country it would be ? Q: is health care profession is lead my future career ? Q: when will i get married ? Is my from abroad or india ?
Rohtak India latitude longitude coordinates
pip install --user pyswisseph ephem 2>/dev/null | tail -1; python3 -c "import swisseph; print('swisseph ok')" 2>/dev/null || echo "no swisseph"
python3 << 'EOF' import swisseph as swe from datetime import datetime, timezone import math # Birth data # 19 Aug 1996, 11:55 AM IST = 06:25 UTC # Rohtak: Lat 28.8955, Lon 76.6066 lat = 28.8955 lon = 76.6066 # Julian Day for 1996-08-19 06:25:00 UTC jd = swe.julday(1996, 8, 19, 6 + 25/60.0) print(f"Julian Day: {jd}") # Set sidereal mode (Lahiri ayanamsha - standard for Vedic) swe.set_sid_mode(swe.SIDM_LAHIRI) # Ayanamsha ayan = swe.get_ayanamsa(jd) print(f"Lahiri Ayanamsha: {ayan:.4f} degrees") # Calculate Ascendant (Lagna) using houses houses, ascmc = swe.houses(jd, lat, lon, b'P') asc_trop = ascmc[0] # tropical ascendant asc_sid = (asc_trop - ayan) % 360 print(f"\nTropical Ascendant: {asc_trop:.4f}") print(f"Sidereal Ascendant (Vedic Lagna): {asc_sid:.4f}") # Planet IDs planets = { 'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY, 'Venus': swe.VENUS, 'Mars': swe.MARS, 'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN, 'Rahu': swe.MEAN_NODE, } # Signs signs = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo', 'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] def get_sign(deg): return signs[int(deg/30)] def get_nakshatra(deg): nakshatras = [ 'Ashwini','Bharani','Krittika','Rohini','Mrigashira','Ardra', 'Punarvasu','Pushya','Ashlesha','Magha','Purva Phalguni','Uttara Phalguni', 'Hasta','Chitra','Swati','Vishakha','Anuradha','Jyeshtha', 'Mula','Purva Ashadha','Uttara Ashadha','Shravana','Dhanishtha','Shatabhisha', 'Purva Bhadrapada','Uttara Bhadrapada','Revati' ] nak_lords = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury'] * 3 idx = int(deg * 27 / 360) pada = int((deg * 108 / 360) % 4) + 1 return nakshatras[idx], nak_lords[idx], pada print("\n--- PLANETARY POSITIONS (Vedic/Sidereal) ---") planet_data = {} for name, pid in planets.items(): result, _ = swe.calc_ut(jd, pid, swe.FLG_SIDEREAL) deg = result[0] % 360 sign = get_sign(deg) deg_in_sign = deg % 30 nak, nak_lord, pada = get_nakshatra(deg) planet_data[name] = {'deg': deg, 'sign': sign, 'deg_in_sign': deg_in_sign, 'nak': nak, 'nak_lord': nak_lord, 'pada': pada} print(f"{name:10}: {sign:15} {deg_in_sign:6.2f}Β° | Nakshatra: {nak} (Lord: {nak_lord}) Pada {pada}") # Ketu = Rahu + 180 ketu_deg = (planet_data['Rahu']['deg'] + 180) % 360 ketu_sign = get_sign(ketu_deg) nak, nak_lord, pada = get_nakshatra(ketu_deg) planet_data['Ketu'] = {'deg': ketu_deg, 'sign': ketu_sign, 'deg_in_sign': ketu_deg % 30, 'nak': nak, 'nak_lord': nak_lord, 'pada': pada} print(f"{'Ketu':10}: {ketu_sign:15} {ketu_deg%30:6.2f}Β° | Nakshatra: {nak} (Lord: {nak_lord}) Pada {pada}") # Lagna details lagna_sign = get_sign(asc_sid) lagna_deg_in_sign = asc_sid % 30 nak, nak_lord, pada = get_nakshatra(asc_sid) print(f"\n{'Lagna':10}: {lagna_sign:15} {lagna_deg_in_sign:6.2f}Β° | Nakshatra: {nak} (Lord: {nak_lord}) Pada {pada}") # --- Houses (Bhava) for Whole Sign system (standard Vedic) --- print("\n--- WHOLE SIGN HOUSES ---") lagna_sign_num = int(asc_sid / 30) # 0=Aries, 1=Taurus... for house_num in range(1, 13): sign_idx = (lagna_sign_num + house_num - 1) % 12 planets_in_house = [] # Check lagna if house_num == 1: planets_in_house.append('Lagna') for pname, pdata in planet_data.items(): p_sign_idx = int(pdata['deg'] / 30) if p_sign_idx == sign_idx: planets_in_house.append(pname) print(f"House {house_num:2} ({signs[sign_idx]:15}): {', '.join(planets_in_house) if planets_in_house else '-'}") # --- Vimshottari Dasha --- print("\n--- VIMSHOTTARI DASHA (Moon-based) ---") moon_nak_deg = planet_data['Moon']['deg'] # Balance of first dasha nak_lords_order = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury'] dasha_years = {'Ketu':7,'Venus':20,'Sun':6,'Moon':10,'Mars':7,'Rahu':18,'Jupiter':16,'Saturn':19,'Mercury':17} total = 120 moon_nak_idx = int(moon_nak_deg * 27 / 360) moon_nak_lord_idx = moon_nak_idx % 9 start_lord = nak_lords_order[moon_nak_lord_idx] # Position within nakshatra nak_span = 360/27 pos_in_nak = (moon_nak_deg % nak_span) / nak_span balance = (1 - pos_in_nak) * dasha_years[start_lord] birth_year = 1996 + (8-1 + 19/31)/12 # approx # More precisely: Aug 19.5 1996 birth_jd = jd from datetime import date birth_date = date(1996, 8, 19) print(f"Moon Nakshatra: {planet_data['Moon']['nak']} | Dasha Lord at birth: {start_lord}") print(f"Balance of {start_lord} dasha at birth: {balance:.2f} years") # Build dasha sequence import datetime current_date = datetime.date(2026, 7, 31) birth_date_d = datetime.date(1996, 8, 19) # Start from birth start_idx = nak_lords_order.index(start_lord) dasha_list = [] current_start = birth_date_d # First dasha: only balance first_end_days = balance * 365.25 first_end = birth_date_d + datetime.timedelta(days=first_end_days) dasha_list.append((start_lord, birth_date_d, first_end, balance)) current_start = first_end for i in range(1, 10): lord = nak_lords_order[(start_idx + i) % 9] yrs = dasha_years[lord] end = current_start + datetime.timedelta(days=yrs*365.25) dasha_list.append((lord, current_start, end, yrs)) current_start = end print("\nMahadasha Timeline:") for lord, start, end, yrs in dasha_list: marker = " <-- CURRENT" if start <= current_date <= end else "" print(f" {lord:10}: {start.strftime('%d-%b-%Y')} to {end.strftime('%d-%b-%Y')} ({yrs:.1f} yrs){marker}") # Find current antardasha print("\nCurrent Antardasha:") for lord, start, end, yrs in dasha_list: if start <= current_date <= end: current_maha = lord maha_start = start maha_end = end maha_yrs = yrs print(f"Mahadasha: {lord} ({start.strftime('%d-%b-%Y')} - {end.strftime('%d-%b-%Y')})") # Antardasha maha_total_days = (maha_end - maha_start).days ant_start = maha_start maha_idx = nak_lords_order.index(lord) for j in range(9): ant_lord = nak_lords_order[(maha_idx + j) % 9] ant_days = (dasha_years[ant_lord] / total) * maha_yrs * 365.25 ant_end = ant_start + datetime.timedelta(days=ant_days) ant_marker = " <-- NOW" if ant_start <= current_date <= ant_end else "" print(f" Antardasha {ant_lord:10}: {ant_start.strftime('%d-%b-%Y')} to {ant_end.strftime('%d-%b-%Y')}{ant_marker}") ant_start = ant_end break EOF
| Planet | Sign | Degree | Nakshatra | House |
|---|---|---|---|---|
| Lagna (Asc) | Libra | 20.25Β° | Vishakha (Jupiter) | 1st |
| Sun | Leo | 2.74Β° | Magha (Ketu) | 11th |
| Moon | Virgo | 28.07Β° | Chitra (Mars) | 12th |
| Mercury | Leo | 29.97Β° | Uttara Phalguni (Sun) | 11th |
| Venus | Gemini | 17.00Β° | Ardra (Rahu) | 9th |
| Mars | Gemini | 22.41Β° | Punarvasu (Jupiter) | 9th |
| Jupiter | Sagittarius | 14.38Β° | Purva Ashadha (Venus) | 3rd |
| Saturn | Pisces | 12.78Β° | Uttara Bhadrapada (Saturn) | 6th |
| Rahu | Virgo | 16.38Β° | Hasta (Moon) | 12th |
| Ketu | Pisces | 16.38Β° | Uttara Bhadrapada (Saturn) | 6th |
| Mahadasha | Period | Key Theme |
|---|---|---|
| Jupiter | 2019 - 2035 | Expansion, education, foreign growth |
| β³ Ketu AD | Now - Jan 2027 | Transitions, spiritual shifts, loose ends |
| β³ Venus AD | Jan 2027 - Sep 2029 | Best window for marriage & career leap abroad |
| Saturn | 2035 - 2054 | Long-term stability, deep professional establishment |
| Question | Answer |
|---|---|
| Career - India or Abroad? | Abroad is strongly indicated (Rahu in 12th, Venus in 9th) |
| Which country? | USA, Canada, UK, or Australia most likely |
| Healthcare career? | Yes - strong yoga (Saturn+Ketu in 6th house) |
| Marriage timing? | 2027-2029 (Jupiter-Venus antardasha) |
| Partner - India or abroad? | Abroad or foreign-connected (7th lord Mars in 9th) |
Follow ups Q: i did my mbbs from china , so yoga is out of option. Currently i am preparing for abroad ! Which one is best for me UK or Australia? Q: my future wife will be foreigner or from indian origin ? Will i meet her in india or abroad uk or Australia?
| Planet | Country Association | Your Chart Position |
|---|---|---|
| Venus (Lagna lord) | UK, Europe, luxury/arts countries | 9th house - very powerful |
| Rahu | Western world, foreign lands broadly | 12th house (foreign settlement) |
| Mars (7th lord, co-occupies 9th) | Australia, military/active nations, USA | 9th house |
| Jupiter (current Mahadasha lord) | UK, USA, spiritual/educational nations | 3rd house in Sagittarius |
| Saturn (6th lord - career/service) | UK, cold/northern countries, service nations | 6th house |
| Factor | UK | Australia |
|---|---|---|
| Venus (Lagna lord) country | β Strong | Moderate |
| Moon (10th lord) = Cancer/UK | β Direct link | β |
| Saturn in 6th (island/northern) | β | Partial |
| Mars in 9th (southern hemisphere) | Partial | β |
| Jupiter Mahadasha alignment | β Strong | Moderate |
| Overall | Stronger | Good backup |
| Question | Answer |
|---|---|
| UK or Australia? | UK is better (Moon-Cancer link, Venus, Saturn, Jupiter alignment) |
| Australia? | Strong backup - Mars in 9th makes it viable if UK doesn't work |
| Wife - foreigner or Indian? | Indian-origin NRI (most likely), foreigner also possible |
| Where will you meet her? | Abroad - in the country you settle in (UK most likely) |
| When? | Jan 2027 - Sep 2029 (Jupiter-Venus antardasha) |
When is my best window to move to UK or Australia?
python3 << 'EOF' import swisseph as swe import datetime # Birth data jd_birth = swe.julday(1996, 8, 19, 6 + 25/60.0) swe.set_sid_mode(swe.SIDM_LAHIRI) # Known chart data (from previous calculation) # Lagna: Libra 20.25Β° (Vishakha, Jupiter) # 12th house: Virgo (Moon + Rahu) - foreign lands # 9th house: Gemini (Venus + Mars) - long distance travel # Current: Jupiter MD, Ketu AD (now - Jan 2027) # Next: Jupiter MD, Venus AD (Jan 2027 - Sep 2029) # Dasha lords and dates dasha_periods = [ ('Jupiter', 'Ketu', datetime.date(2026, 1, 27), datetime.date(2027, 1, 2)), ('Jupiter', 'Venus', datetime.date(2027, 1, 2), datetime.date(2029, 9, 2)), ('Jupiter', 'Sun', datetime.date(2029, 9, 2), datetime.date(2030, 6, 21)), ('Jupiter', 'Moon', datetime.date(2030, 6, 21), datetime.date(2031, 10, 21)), ('Jupiter', 'Mars', datetime.date(2031, 10, 21),datetime.date(2032, 9, 25)), ('Jupiter', 'Rahu', datetime.date(2032, 9, 25), datetime.date(2035, 2, 18)), ] # Pratyantardasha within Jupiter-Venus (Jan 2027 - Sep 2029) # Total Venus AD = ~32.9 months # Pratyantardasha order starting from Venus prat_lords = ['Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury','Ketu'] prat_years = {'Ketu':7,'Venus':20,'Sun':6,'Moon':10,'Mars':7,'Rahu':18,'Jupiter':16,'Saturn':19,'Mercury':17} venus_ad_start = datetime.date(2027, 1, 2) venus_ad_end = datetime.date(2029, 9, 2) venus_ad_days = (venus_ad_end - venus_ad_start).days # ~972 days jupiter_md_years = 16 print("=== PRATYANTARDASHA within Jupiter-Venus AD ===") print(f"Venus AD: {venus_ad_start} to {venus_ad_end} ({venus_ad_days} days)") prat_start = venus_ad_start for lord in prat_lords: # Pratyantardasha days = (prat_lord_years / 120) * venus_ad_days_in_years * 365.25 venus_ad_years = venus_ad_days / 365.25 prat_days = (prat_years[lord] / 120) * venus_ad_years * 365.25 prat_end = prat_start + datetime.timedelta(days=prat_days) print(f" {lord:10}: {prat_start.strftime('%d-%b-%Y')} to {prat_end.strftime('%d-%b-%Y')}") prat_start = prat_end # Now check Saturn transits over key houses print("\n=== SATURN TRANSIT (2026-2032) ===") print("Saturn transits are most important for foreign relocation") # Saturn currently in Pisces sidereal (as of birth chart era) # Let's check Saturn's current position today_jd = swe.julday(2026, 7, 31, 0) signs = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] for year in range(2026, 2032): for month in [1, 4, 7, 10]: jd = swe.julday(year, month, 1, 0) result, _ = swe.calc_ut(jd, swe.SATURN, swe.FLG_SIDEREAL) deg = result[0] % 360 sign_idx = int(deg / 30) print(f" {year}-{month:02d}: Saturn at {deg:.1f}Β° = {signs[sign_idx]} {deg%30:.1f}Β°") print("\n=== JUPITER TRANSIT (2026-2031) ===") for year in range(2026, 2031): for month in [1, 6]: jd = swe.julday(year, month, 1, 0) result, _ = swe.calc_ut(jd, swe.JUPITER, swe.FLG_SIDEREAL) deg = result[0] % 360 sign_idx = int(deg / 30) print(f" {year}-{month:02d}: Jupiter at {deg:.1f}Β° = {signs[sign_idx]} {deg%30:.1f}Β°") print("\n=== RAHU/KETU TRANSIT (2026-2030) ===") for year in range(2026, 2030): for month in [1, 7]: jd = swe.julday(year, month, 1, 0) result, _ = swe.calc_ut(jd, swe.MEAN_NODE, swe.FLG_SIDEREAL) deg = result[0] % 360 sign_idx = int(deg / 30) ketu_deg = (deg + 180) % 360 ketu_sign_idx = int(ketu_deg / 30) print(f" {year}-{month:02d}: Rahu={signs[sign_idx]} {deg%30:.1f}Β° | Ketu={signs[ketu_sign_idx]} {ketu_deg%30:.1f}Β°") # Key natal positions for reference print("\n=== KEY NATAL POSITIONS FOR TRANSIT REFERENCE ===") print("Lagna (Ascendant): Libra 20.25Β°") print("12th house cusp: Virgo (Moon+Rahu natal)") print("9th house: Gemini (Venus+Mars natal)") print("10th house: Cancer") print("Natal Venus: Gemini 17Β°") print("Natal Moon: Virgo 28Β°") print("Natal Rahu: Virgo 16Β°") print("7th house: Aries") EOF
python3 -c "import sys; sys.path.insert(0, '/root/.local/lib/python3.11/site-packages'); import swisseph; print('ok')" 2>&1
pip install --user pyswisseph 2>&1 | tail -3 && python3 -c "import sys; sys.path.insert(0, '/root/.local/lib/python3.11/site-packages'); import swisseph as swe; print('ok', swe.__version__)"
python3 << 'EOF' import sys sys.path.insert(0, '/root/.local/lib/python3.11/site-packages') import swisseph as swe import datetime jd_birth = swe.julday(1996, 8, 19, 6 + 25/60.0) swe.set_sid_mode(swe.SIDM_LAHIRI) signs = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] # Pratyantardasha within Jupiter-Venus AD prat_lords_order = ['Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury','Ketu'] prat_years = {'Ketu':7,'Venus':20,'Sun':6,'Moon':10,'Mars':7,'Rahu':18,'Jupiter':16,'Saturn':19,'Mercury':17} venus_ad_start = datetime.date(2027, 1, 2) venus_ad_end = datetime.date(2029, 9, 2) venus_ad_days = (venus_ad_end - venus_ad_start).days print("=== PRATYANTARDASHA within Jupiter-Venus AD ===") venus_ad_years = venus_ad_days / 365.25 prat_start = venus_ad_start for lord in prat_lords_order: prat_days = (prat_years[lord] / 120) * venus_ad_years * 365.25 prat_end = prat_start + datetime.timedelta(days=prat_days) print(f" {lord:10}: {prat_start.strftime('%d-%b-%Y')} to {prat_end.strftime('%d-%b-%Y')}") prat_start = prat_end print("\n=== SATURN TRANSIT (2026-2031) ===") for year in range(2026, 2032): for month in [1, 4, 7, 10]: jd = swe.julday(year, month, 1, 0) result, _ = swe.calc_ut(jd, swe.SATURN, swe.FLG_SIDEREAL) deg = result[0] % 360 sign_idx = int(deg / 30) print(f" {year}-{month:02d}: Saturn at {signs[sign_idx]:15} {deg%30:.1f}Β°") print("\n=== JUPITER TRANSIT (2026-2031) ===") for year in range(2026, 2031): for month in [1, 5, 9]: jd = swe.julday(year, month, 1, 0) result, _ = swe.calc_ut(jd, swe.JUPITER, swe.FLG_SIDEREAL) deg = result[0] % 360 sign_idx = int(deg / 30) print(f" {year}-{month:02d}: Jupiter at {signs[sign_idx]:15} {deg%30:.1f}Β°") print("\n=== RAHU/KETU TRANSIT (2026-2030) ===") for year in range(2026, 2030): for month in [1, 7]: jd = swe.julday(year, month, 1, 0) result, _ = swe.calc_ut(jd, swe.MEAN_NODE, swe.FLG_SIDEREAL) deg = result[0] % 360 sign_idx = int(deg / 30) ketu_deg = (deg + 180) % 360 ketu_sign_idx = int(ketu_deg / 30) print(f" {year}-{month:02d}: Rahu={signs[sign_idx]:12} {deg%30:.1f}Β° | Ketu={signs[ketu_sign_idx]:12} {ketu_deg%30:.1f}Β°") EOF
| Period | Dates | Score for Relocation |
|---|---|---|
| Jupiter - Ketu AD | Now - 02 Jan 2027 | β οΈ Unstable, transitional |
| Jupiter - Venus AD | 02 Jan 2027 - 02 Sep 2029 | β β Prime window |
| β³ Venus-Venus Pratyantar | 02 Jan 2027 - 13 Jun 2027 | β β β Best sub-period |
| β³ Venus-Sun Pratyantar | 13 Jun 2027 - 31 Jul 2027 | β β Strong |
| β³ Venus-Moon Pratyantar | 31 Jul 2027 - 20 Oct 2027 | β β Good |
| β³ Venus-Rahu Pratyantar | 15 Dec 2027 - 09 May 2028 | β β Foreign push |
| Jupiter - Sun AD | 02 Sep 2029 - 21 Jun 2030 | β Good, secondary |
| Period | Saturn Position | Impact on Your Chart |
|---|---|---|
| Now - mid 2027 | Pisces (6th house) | Saturn transiting your 6th (service/work) - building foundation, exams, preparation phase |
| Mid 2027 onwards | Aries (7th house) | Saturn enters your 7th house - activates partnership, foreign settlement, and serious commitments |
| Period | Jupiter Position | Impact |
|---|---|---|
| Now - Sep 2026 | Gemini (9th house) | β Jupiter over natal Venus+Mars - activating your 9th house right now |
| Sep 2026 - 2027 | Cancer (10th house) | β β Jupiter over your career house - professional expansion, new opportunities |
| 2027-2028 | Leo/Virgo (11/12th house) | β β Jupiter over 11th (gains/network) then 12th (foreign lands) |
| 2028-2029 | Virgo (12th house) | β β β Jupiter directly over natal Moon+Rahu in 12th - maximum foreign activation |
| Period | Rahu Position | Impact |
|---|---|---|
| Now - late 2026 | Aquarius (5th house) | Neutral |
| Late 2026 - mid 2028 | Capricorn (4th house) | Mixed - disrupts home/roots (push to leave India) |
| Mid 2028 - 2029 | Sagittarius (3rd house) | Ketu enters Gemini (your 9th house) |
| Date | Milestone (Astrological Timing) |
|---|---|
| Now - Jan 2027 | Exam preparation, applications, paperwork (Ketu AD - focused grind) |
| Jan 2027 | Venus AD opens - visa application, registration submission |
| Jan - Jun 2027 | Ideal departure window - move to UK/Australia |
| Dec 2027 - May 2028 | Secondary window if delays occur |
| 2028 | Deep establishment abroad, career foothold |
| Jan 2027 - Sep 2029 | Full Venus AD - career + marriage both get activated |
Find recent PLAB/AMC exam success rates and visa timelines
PLAB exam pass rates 2024 2025 international medical graduates statistics
AMC exam pass rate 2024 2025 international medical graduates Australia
UK Skilled Worker visa doctor timeline 2025 processing time NHS
| Exam | Pass Rate | Notes |
|---|---|---|
| PLAB 1 | ~65-75% | 180 MCQs in 3 hrs, Angoff pass mark (~110-118/180) |
| PLAB 2 | ~65.8% (2024 data) | OSCE format, max 4 attempts |
| Item | Cost |
|---|---|
| PLAB 1 | |
| PLAB 2 | |
| IELTS/OET (if needed) | Additional |
| Travel + accommodation for PLAB 2 | Budget Β£1,500-2,500 extra |
| Stage | Typical Timeline |
|---|---|
| Pass PLAB 1 + 2 | 6-12 months of prep |
| GMC Registration after PLAB 2 | 2-4 weeks |
| Job hunting (NHS Trust offer) | 1-6 months |
| Certificate of Sponsorship (CoS) issued by employer | 1-4 weeks |
| Health & Care Visa application (from India) | ~3 weeks standard |
| Priority service visa | 5 working days |
| Super-priority visa | Next working day |
| Total from starting PLAB prep to landing in UK | ~12-18 months |
| Exam | Pass Rate | Notes |
|---|---|---|
| AMC Part 1 (MCQ) | ~47-52% | 150 questions, 3.5 hrs, Pearson VUE centres worldwide |
| AMC Part 2 (Clinical) | ~21-24% historically; may rise slightly to ~25-30% post 2024 rule change | OSCE, Melbourne only, 14 scored stations, need 9/14 to pass (changed from 10/14 in 2024) |
| Item | Cost |
|---|---|
| AMC Part 1 (MCQ) | |
| AMC Part 2 (Clinical) | |
| MyIntealth verification | ~AUD $400 |
| Travel + accommodation for Clinical (Melbourne) | AUD $2,000-4,000 |
| Stage | Typical Timeline |
|---|---|
| AMC Part 1 + Part 2 | 12-24 months of prep (harder exams) |
| AHPRA Registration after AMC certificate | 4-8 weeks |
| Job offer (Area of Need or standard) | 1-6 months |
| Skilled Worker (subclass 482) visa from India | 2-4 months standard |
| Total from starting AMC prep to landing in Australia | ~18-30 months |
| Factor | UK (PLAB) | Australia (AMC) |
|---|---|---|
| Part 1 pass rate | ~70% | ~50% |
| Part 2 pass rate | ~66% | ~22-30% |
| Part 1 can be taken in India | β Yes | β Yes (Pearson VUE) |
| Part 2 location | UK only | Melbourne only |
| Total exam cost | ~Β£1,300 (βΉ1.4L) | ~AUD $6,000+ (βΉ3.2L+) |
| Visa processing | ~3 weeks (Health & Care) | 2-4 months |
| Time to land | ~12-18 months | ~18-30 months |
| Job market (2026) | Competitive but large | Competitive, geographically spread |
| Post-qualification pathway | NHS Trust Grade β training | Supervised practice β general registration |
| Month | Action |
|---|---|
| Aug-Sep 2026 | Register with GMC, complete IELTS/OET (need 7.5 overall for PLAB), start PLAB 1 prep |
| Oct-Nov 2026 | Book and sit PLAB 1 (book immediately - seats go fast) |
| Nov 2026 - Jan 2027 | Book PLAB 2 in UK (requires a UK trip) |
| Jan-Feb 2027 | Sit PLAB 2 in UK during Venus Antardasha opening |
| Feb-Apr 2027 | GMC registration + job hunt for NHS post |
| Apr-Jun 2027 | Receive CoS from NHS employer, apply for Health & Care Visa (~3 weeks processing) |
| Mid 2027 | Land in UK - right in the heart of your best astrological window |
Create a detailed month-by-month prep schedule for PLAB exams
mkdir -p /tmp/workspace/plab-schedule && ls /tmp/workspace/plab-schedule/
~/plab-schedule/build_schedule.py
from docx import Document
from docx.shared import Pt, Inches, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
import copy
# ββ helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def set_cell_bg(cell, hex_color):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:val'), 'clear')
shd.set(qn('w:color'), 'auto')
shd.set(qn('w:fill'), hex_color)
tcPr.append(shd)
def set_cell_borders(cell, color="CCCCCC"):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcBorders = OxmlElement('w:tcBorders')
for side in ['top', 'left', 'bottom', 'right']:
border = OxmlElement(f'w:{side}')
border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), '4')
border.set(qn('w:space'), '0')
border.set(qn('w:color'), color)
tcBorders.append(border)
tcPr.append(tcBorders)
def add_cell_text(cell, text, bold=False, size=10, color=None, align=WD_ALIGN_PARAGRAPH.LEFT):
cell.paragraphs[0].clear()
run = cell.paragraphs[0].add_run(text)
run.bold = bold
run.font.size = Pt(size)
if color:
run.font.color.rgb = RGBColor.from_string(color)
cell.paragraphs[0].alignment = align
def add_heading(doc, text, level=1, color="1B3A6B"):
p = doc.add_heading(text, level=level)
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
for run in p.runs:
run.font.color.rgb = RGBColor.from_string(color)
return p
def add_colored_heading(doc, text, bg_color, text_color="FFFFFF", size=13):
"""Full-width colored heading paragraph"""
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
pPr = p._p.get_or_add_pPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:val'), 'clear')
shd.set(qn('w:color'), 'auto')
shd.set(qn('w:fill'), bg_color)
pPr.append(shd)
run = p.add_run(f' {text} ')
run.bold = True
run.font.size = Pt(size)
run.font.color.rgb = RGBColor.from_string(text_color)
pPr2 = p._p.get_or_add_pPr()
spacing = OxmlElement('w:spacing')
spacing.set(qn('w:before'), '120')
spacing.set(qn('w:after'), '60')
pPr2.append(spacing)
return p
def add_info_box(doc, lines, bg="EBF5FB", border_color="2E86C1"):
"""Add a shaded info box as a 1-cell table"""
tbl = doc.add_table(rows=1, cols=1)
tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
cell = tbl.rows[0].cells[0]
set_cell_bg(cell, bg)
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcBorders = OxmlElement('w:tcBorders')
for side in ['top', 'left', 'bottom', 'right']:
border = OxmlElement(f'w:{side}')
border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), '12')
border.set(qn('w:space'), '0')
border.set(qn('w:color'), border_color)
tcBorders.append(border)
tcPr.append(tcBorders)
cell.paragraphs[0].clear()
for line in lines:
p = cell.add_paragraph(line)
p.runs[0].font.size = Pt(10) if p.runs else None
if p.runs:
p.runs[0].font.size = Pt(10)
cell.paragraphs[0]._element.getparent().remove(cell.paragraphs[0]._element)
doc.add_paragraph()
# ββ document setup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc = Document()
# Page margins
from docx.oxml import OxmlElement
section = doc.sections[0]
section.top_margin = Cm(2)
section.bottom_margin = Cm(2)
section.left_margin = Cm(2.2)
section.right_margin = Cm(2.2)
# Default style
normal = doc.styles['Normal']
normal.font.name = 'Calibri'
normal.font.size = Pt(10.5)
# ββ TITLE PAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_paragraph()
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = title.add_run('PLAB EXAM PREPARATION SCHEDULE')
r.bold = True
r.font.size = Pt(22)
r.font.color.rgb = RGBColor.from_string('1B3A6B')
sub = doc.add_paragraph()
sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
r2 = sub.add_run('Complete Month-by-Month Roadmap: PLAB 1 + PLAB 2')
r2.bold = True
r2.font.size = Pt(13)
r2.font.color.rgb = RGBColor.from_string('2E86C1')
doc.add_paragraph()
meta = doc.add_paragraph()
meta.alignment = WD_ALIGN_PARAGRAPH.CENTER
r3 = meta.add_run('Personalised for: MBBS (China) | Target: UK Medical Practice (NHS)\nPrepared: August 2026 | Target PLAB 1: November 2026 | Target PLAB 2: February 2027')
r3.font.size = Pt(10)
r3.font.color.rgb = RGBColor.from_string('555555')
doc.add_paragraph()
# Overview box
tbl = doc.add_table(rows=3, cols=4)
tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
headers = ['Phase', 'Period', 'Exam', 'Goal']
bg_hdr = '1B3A6B'
for i, h in enumerate(headers):
cell = tbl.rows[0].cells[i]
set_cell_bg(cell, bg_hdr)
add_cell_text(cell, h, bold=True, size=10, color='FFFFFF', align=WD_ALIGN_PARAGRAPH.CENTER)
rows_data = [
['Phase 1', 'Aug - Oct 2026', 'PLAB 1 Prep', '3 months focused MCQ prep'],
['Phase 2', 'Nov 2026', 'PLAB 1 EXAM', 'Sit & Pass PLAB 1 in India'],
['Phase 3', 'Nov 26 - Jan 27','PLAB 2 Prep', 'OSCE skills + UK trip planning'],
['Phase 4', 'Feb 2027', 'PLAB 2 EXAM', 'Sit & Pass PLAB 2 in UK'],
['Phase 5', 'Mar - Jun 2027', 'GMC + Jobs', 'Registration, NHS job, visa'],
]
row_colors = ['EBF5FB', 'D5F5E3', 'FEF9E7', 'FDEDEC', 'F4ECF7']
for i, (phase, period, exam, goal) in enumerate(rows_data):
row = tbl.add_row()
data = [phase, period, exam, goal]
for j, d in enumerate(data):
cell = row.cells[j]
set_cell_bg(cell, row_colors[i % len(row_colors)])
set_cell_borders(cell)
add_cell_text(cell, d, bold=(j == 0), size=10)
doc.add_paragraph()
# ββ SECTION: BEFORE YOU START ββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PRE-REQUISITES & SETUP (Do This First)', '1B3A6B', 'FFFFFF', 14)
doc.add_paragraph()
prereqs = [
('GMC Eligibility Check',
'Go to gmc-uk.org and check your China MBBS is on the World Directory of Medical Schools (WDOMS). '
'Most Chinese medical universities are listed. If yours is not, you must contact GMC directly.'),
('English Language Test',
'PLAB requires IELTS Academic 7.5 overall (min 7.0 in each band) OR OET Grade B in all 4 sections. '
'Book immediately if you do not have a valid score (scores valid 2 years). IELTS is more common and '
'widely available in India. Book for September 2026 sitting.'),
('GMC Identity Verification',
'Register on MyGMC portal. Upload your MBBS certificate, transcripts, internship completion certificate, '
'and ID proof. GMC takes 4-8 weeks to verify. Start this in August 2026.'),
('PLAB 1 Booking',
'PLAB 1 is available in India (Delhi, Mumbai, Chennai). Seats open ~3 months in advance and fill '
'within MINUTES. Set a calendar alert for when the November 2026 booking window opens (typically August). '
'Book the moment slots go live.'),
('Study Materials - PLAB 1',
'Primary: PLABABLE (question bank, most popular) or Passmedicine UK. '
'Books: Oxford Handbook of Clinical Medicine (OHCM), BNF (British National Formulary - free online). '
'NICE guidelines app (free). Focus on UK-specific management, not MBBS-style theory.'),
('Study Materials - PLAB 2',
'PLAB 2 Ninja or Dr Najeeb OSCE videos. Join a PLAB 2 course in India first (Marrow/DAMS PLAB batch), '
'then a UK-based course before the exam (highly recommended). PLAB 2 is about communication style, '
'not just clinical knowledge.'),
('Budget Planning',
'PLAB 1 fee: Β£268. PLAB 2 fee: Β£998. UK trip (PLAB 2): ~Β£1,500-2,500 for flights + accommodation + course. '
'IELTS: ~βΉ16,000. GMC registration: Β£405. Total estimate: ~βΉ3.5-4.5 lakhs all-in.'),
]
for title_text, body_text in prereqs:
p = doc.add_paragraph()
r_title = p.add_run(f'{title_text}: ')
r_title.bold = True
r_title.font.color.rgb = RGBColor.from_string('1B3A6B')
r_title.font.size = Pt(10.5)
r_body = p.add_run(body_text)
r_body.font.size = Pt(10.5)
# ββ PHASE 1: AUGUST 2026 βββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PHASE 1 - PLAB 1 PREPARATION (Aug - Oct 2026)', '154360', 'FFFFFF', 14)
months_p1 = [
{
'month': 'MONTH 1 - AUGUST 2026',
'subtitle': 'Foundation & Orientation',
'color': 'D5F5E3',
'hdr_color': '1E8449',
'daily': '6-8 hours/day',
'target': 'Complete 600 practice MCQs | Finish Cardiology + Respiratory',
'topics': [
('Week 1', 'Orientation + Cardiology',
'Download PLABABLE or Passmedicine. Do NOT start random questions yet. '
'Read OHCM Cardiology chapter. Learn UK-specific: ACS management (NICE NG185), '
'Heart Failure (NICE NG106), AF management. Do 150 cardiology MCQs.'),
('Week 2', 'Respiratory + GI',
'Asthma (BTS/SIGN stepwise ladder), COPD (NICE NG115 - know the inhalers), '
'pneumonia (CURB-65 scoring), PE (Wells score + CTPA vs V/Q). '
'GI: GORD, peptic ulcers (H.pylori eradication - triple therapy), IBD basics. '
'200 MCQs this week.'),
('Week 3', 'Neurology + Psychiatry',
'Stroke (FAST, thrombolysis window 4.5 hrs, thrombectomy 6 hrs), TIA (ABCD2 score), '
'seizures (first-line: sodium valproate/levetiracetam), headaches (red flags). '
'Psychiatry: Mental Health Act basics, depression (NICE - SSRIs first line), '
'schizophrenia (antipsychotics). 200 MCQs.'),
('Week 4', 'Revision + Mock Test 1',
'Revise all topics covered. Do your FIRST full 180-question timed mock. '
'Score it, identify weak areas. Do not be discouraged by the score - '
'this is a baseline. Review every wrong answer with explanation. '
'Register on GMC portal this week.'),
],
'resources': 'PLABABLE / Passmedicine | OHCM Chapters 1-4 | NICE Guidelines App | BNF online',
'milestones': ['GMC portal registration started', 'IELTS booked (Sep sitting)', 'PLAB 1 booking alert set', '600+ MCQs completed'],
},
{
'month': 'MONTH 2 - SEPTEMBER 2026',
'subtitle': 'Core Systems Deep Dive',
'color': 'EBF5FB',
'hdr_color': '2E86C1',
'daily': '6-8 hours/day',
'target': 'Complete 1,000 more MCQs | Cover Endocrine, Renal, Haematology, Rheumatology',
'topics': [
('Week 1', 'Endocrinology',
'Diabetes (NICE NG28 - HbA1c targets, metformin first line, SGLT2 inhibitors, insulin types), '
'thyroid (hypo: levothyroxine, hyper: carbimazole, Graves), adrenal (Addisons vs Cushings - '
'know the tests: short Synacthen, 24hr urine cortisol, dexamethasone suppression), '
'pituitary. 200 MCQs.'),
('Week 2', 'Renal + Urology',
'AKI (KDIGO staging, causes - pre/intra/post, management), CKD (eGFR staging), '
'nephrotic vs nephritic syndrome, UTI management (UK: nitrofurantoin first line), '
'renal stones (ESWL vs ureteroscopy), prostate (PSA, BPH - alpha blockers). 200 MCQs.'),
('Week 3', 'Haematology + Oncology basics',
'Anaemia workup (iron deficiency vs B12/folate vs haemolytic), anticoagulation '
'(heparin, warfarin, DOACs - know reversals), blood transfusion reactions, '
'common cancers screening (breast, cervical, bowel - UK NHS screening programmes), '
'palliative care basics. 200 MCQs.'),
('Week 4', 'Rheumatology + Dermatology + Mock Test 2',
'RA (DMARDS - methotrexate first line), SLE (ANA, anti-dsDNA), gout (allopurinol for prevention, '
'colchicine/NSAIDs acute), osteoporosis (DEXA, bisphosphonates). '
'Dermatology: common rashes, skin cancer (BCC vs SCC vs melanoma - ABCDE). '
'MOCK TEST 2 - aim for >55% (passing threshold ~61-65%). IELTS exam this week.'),
],
'resources': 'PLABABLE | OHCM Chapters 5-11 | NICE guidelines NG28, NG185, NG106 | BNF sections 6,9',
'milestones': ['IELTS sat (need 7.5 overall)', 'GMC documents submitted', '1,600+ total MCQs done', 'Mock score improving'],
},
{
'month': 'MONTH 3 - OCTOBER 2026',
'subtitle': 'High-Yield Specialties + Final Revision',
'color': 'FEF9E7',
'hdr_color': 'B7950B',
'daily': '8-10 hours/day (exam is in November - push hard)',
'target': '2,500+ total MCQs | All topics covered | 3 full mocks | Score >65% consistently',
'topics': [
('Week 1', 'Obstetrics & Gynaecology',
'Antenatal care (UK schedule), pre-eclampsia (labetalol/nifedipine, MgSO4 seizure prophylaxis), '
'PPH management, ectopic pregnancy (methotrexate vs surgery), contraception (UK-specific: COCP '
'contraindications, copper IUD, Mirena), cervical cancer (HPV, colposcopy). 200 MCQs.'),
('Week 2', 'Paediatrics + Neonatology',
'Development milestones (know all key ages), febrile convulsions, meningitis (benzylpenicillin '
'IM before hospital if suspected), croup vs epiglottitis, bronchiolitis (RSV, supportive), '
'safeguarding red flags, vaccination schedule (UK routine immunisation programme). 200 MCQs.'),
('Week 3', 'Surgery + Emergency Medicine + Ethics',
'Surgical emergencies: appendicitis, bowel obstruction, AAA rupture. Pre-op assessment. '
'Emergency: ABCDE approach, anaphylaxis (adrenaline 0.5mg IM), sepsis (NEWS2 scoring, '
'Sepsis 6). Ethics: Gillick competence, capacity assessment (Mental Capacity Act 2005), '
'consent, confidentiality, duty of candour. These are high-yield PLAB topics. 200 MCQs.'),
('Week 4', 'Final Revision Sprint',
'STOP new topics. Only revision. '
'Day 1-2: Cardiology + Respiratory. Day 3-4: Neuro + Psych. Day 5-6: Endo + Renal. '
'Day 7: Rest. Day 8-9: O&G + Paeds. Day 10-11: Surgery + Emergency + Ethics. '
'Day 12-14: 2 FULL mock exams (180 Qs each, timed). Review every wrong answer. '
'Target score: >65%. Know your weak spots and do targeted MCQs only.'),
],
'resources': 'PLABABLE timed mocks | Passmedicine subject tests | OHCM | NICE Guidance summaries',
'milestones': ['All 12 systems covered', '2,500+ MCQs completed', '3+ full mocks done', 'Consistent score >65%', 'PLAB 1 slot confirmed'],
},
]
for m in months_p1:
add_colored_heading(doc, f" {m['month']}: {m['subtitle']}", m['hdr_color'], 'FFFFFF', 12)
# Daily hours + target row
info_tbl = doc.add_table(rows=1, cols=2)
info_tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
c1 = info_tbl.rows[0].cells[0]
c2 = info_tbl.rows[0].cells[1]
set_cell_bg(c1, 'F2F3F4')
set_cell_bg(c2, 'F2F3F4')
add_cell_text(c1, f'Daily Study: {m["daily"]}', bold=True, size=10)
add_cell_text(c2, f'Monthly Target: {m["target"]}', bold=False, size=10)
doc.add_paragraph()
# Weekly breakdown table
tbl = doc.add_table(rows=1, cols=3)
tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
# Header
hdr_row = tbl.rows[0]
for ci, htxt in enumerate(['Week', 'Topic Focus', 'What to Study & Do']):
cell = hdr_row.cells[ci]
set_cell_bg(cell, m['hdr_color'])
add_cell_text(cell, htxt, bold=True, size=10, color='FFFFFF')
col_widths = [Cm(2.2), Cm(4.0), Cm(12.0)]
for w_label, w_topic, w_detail in m['topics']:
row = tbl.add_row()
data = [w_label, w_topic, w_detail]
for ci, (cell, txt) in enumerate(zip(row.cells, data)):
set_cell_bg(cell, m['color'])
set_cell_borders(cell)
add_cell_text(cell, txt, bold=(ci < 2), size=10)
for ci, w in enumerate(col_widths):
row.cells[ci].width = w
doc.add_paragraph()
# Resources + Milestones
rm_tbl = doc.add_table(rows=1, cols=2)
r_cell = rm_tbl.rows[0].cells[0]
m_cell = rm_tbl.rows[0].cells[1]
set_cell_bg(r_cell, 'EAF2FF')
set_cell_bg(m_cell, 'EAFAF1')
set_cell_borders(r_cell, '2E86C1')
set_cell_borders(m_cell, '1E8449')
r_cell.paragraphs[0].clear()
rp = r_cell.add_paragraph()
rp.add_run('Resources').bold = True
for res in m['resources'].split(' | '):
r_cell.add_paragraph(f'β’ {res}').runs[0].font.size = Pt(9.5)
r_cell.paragraphs[0]._element.getparent().remove(r_cell.paragraphs[0]._element)
m_cell.paragraphs[0].clear()
mp2 = m_cell.add_paragraph()
mp2.add_run('Monthly Milestones').bold = True
for ms in m['milestones']:
m_cell.add_paragraph(f'β {ms}').runs[0].font.size = Pt(9.5)
m_cell.paragraphs[0]._element.getparent().remove(m_cell.paragraphs[0]._element)
doc.add_paragraph()
doc.add_paragraph()
# ββ PHASE 2: NOVEMBER 2026 - EXAM ββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PHASE 2 - NOVEMBER 2026: PLAB 1 EXAM MONTH', 'C0392B', 'FFFFFF', 14)
doc.add_paragraph()
exam_weeks = [
('Week 1-2', 'Active Revision Only',
'No new topics. Rotate through all systems, 100 MCQs/day. Focus on your weakest areas identified '
'in mocks. Review the GMC PLAB 1 question paper blueprint (available on gmc-uk.org). '
'Ethics and Law questions: re-read the GMC Good Medical Practice guidance - several questions '
'come from this document. Know the Mental Capacity Act 2005, Gillick/Fraser competence, '
'notifiable diseases list.'),
('Week 3', 'Final 3 Days Before Exam',
'Day 1: Light revision, 50 MCQs only. Day 2: Rest, walk, light reading only - do NOT cram. '
'Day 3 (exam eve): Organize documents (passport, GMC email confirmation). Early sleep. '
'Exam day: Arrive 30 min early. Read each question carefully - UK context, not textbook. '
'180 questions in 180 minutes = 1 min per question. Flag uncertain ones, come back.'),
('Week 4', 'Post-Exam + PLAB 2 Start',
'If passed: Immediately begin PLAB 2 preparation. Book PLAB 2 date (first available in UK, '
'ideally February 2027). Start PLAB 2 Ninja or join a PLAB 2 prep group online. '
'If failed: Do not panic. Analyse your weak areas. Re-book PLAB 1 for next sitting. '
'Most candidates pass on 2nd attempt. Adjust study plan accordingly.'),
]
tbl = doc.add_table(rows=1, cols=3)
for ci, htxt in enumerate(['Period', 'Focus', 'Detail']):
cell = tbl.rows[0].cells[ci]
set_cell_bg(cell, 'C0392B')
add_cell_text(cell, htxt, bold=True, size=10, color='FFFFFF')
for w, f, d in exam_weeks:
row = tbl.add_row()
for ci, txt in enumerate([w, f, d]):
cell = row.cells[ci]
set_cell_bg(cell, 'FDEDEC')
set_cell_borders(cell)
add_cell_text(cell, txt, bold=(ci < 2), size=10)
doc.add_paragraph()
# PLAB 1 Key Facts box
kf = doc.add_table(rows=1, cols=1)
kf.alignment = WD_TABLE_ALIGNMENT.LEFT
kf_cell = kf.rows[0].cells[0]
set_cell_bg(kf_cell, 'FDF2F8')
set_cell_borders(kf_cell, 'A93226')
kf_cell.paragraphs[0].clear()
kf_hdr = kf_cell.add_paragraph()
kf_hdr.add_run('PLAB 1 EXAM - KEY FACTS').bold = True
facts = [
'β’ 180 single-best-answer MCQs in 3 hours (exactly 1 min per question)',
'β’ Pass mark: Angoff method, typically 110-118 correct out of 180 (~61-66%)',
'β’ No negative marking - ALWAYS answer every question',
'β’ Taken at Pearson VUE centres in India (Delhi, Mumbai, Chennai, Hyderabad)',
'β’ Results: Usually within 4 weeks. Certificate issued digitally via GMC portal',
'β’ Valid for: No expiry on PLAB 1 pass (you can sit PLAB 2 at any time after)',
'β’ UK-specific knowledge is key: NICE guidelines, BNF drug choices, NHS pathways',
'β’ Ethics/Law questions: ~10-15% of paper - know Mental Capacity Act, GMC duties',
]
for f in facts:
p = kf_cell.add_paragraph(f)
p.runs[0].font.size = Pt(10)
kf_cell.paragraphs[0]._element.getparent().remove(kf_cell.paragraphs[0]._element)
doc.add_paragraph()
# ββ PHASE 3: PLAB 2 PREP βββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PHASE 3 - PLAB 2 PREPARATION (Nov 2026 - Jan 2027)', '6C3483', 'FFFFFF', 14)
doc.add_paragraph()
p2_intro = doc.add_paragraph()
r_intro = p2_intro.add_run(
'PLAB 2 is fundamentally different from PLAB 1. It is an OSCE (Objective Structured Clinical Examination) '
'with 18 stations (16 scored, 2 rest). Each station is 8 minutes. You play the role of an FY2 doctor. '
'The exam is ONLY held in Manchester, UK. You CANNOT pass by reading alone - you must practise '
'with partners out loud, every single day.')
r_intro.font.size = Pt(10.5)
r_intro.font.color.rgb = RGBColor.from_string('333333')
doc.add_paragraph()
p2_months = [
{
'month': 'NOVEMBER 2026 (post-PLAB 1)',
'color': 'F4ECF7',
'hdr_color': '6C3483',
'weeks': [
('Week 1-2', 'PLAB 2 Orientation',
'Watch PLAB 2 Ninja introduction videos or Dr Najeeb PLAB 2 course. '
'Understand the 6 station types: History taking, Examination, Explanation/counselling, '
'Communication/ethics, Practical procedures, Data interpretation. '
'Read the PLAB 2 mark sheet format - understand what examiners are scoring. '
'Start practising opening lines for each station type.'),
('Week 3-4', 'History Taking Stations',
'The most common station type. Practice SOCRATES for pain, ICE (Ideas, Concerns, '
'Expectations) framework, systems review, summarising. '
'Key conditions: chest pain, SOB, headache, abdominal pain, joint pain, collapse. '
'CRITICAL: The examiner is watching how you talk to the patient, not just what you ask. '
'Practice with a friend or record yourself. UK communication style = empathetic, '
'non-directive, patient-centred. Avoid being mechanical.'),
],
'milestones': ['PLAB 2 date booked (Feb 2027)', 'UK trip flights + accommodation booked', 'Study partner found for OSCE practice'],
},
{
'month': 'DECEMBER 2026',
'color': 'EBF5FB',
'hdr_color': '2E86C1',
'weeks': [
('Week 1-2', 'Examination Stations + Explanation Stations',
'Physical examination: Cardiovascular, Respiratory, Abdominal, Neurological (cranial nerves, '
'peripheral), MSK (hip, knee, shoulder). Learn the exact UK format sequences. '
'Explanation/counselling: Practice explaining diagnoses (e.g., new diabetes, cancer), '
'procedures (colonoscopy, bronchoscopy), and medications. Use teach-back method. '
'Record yourself and watch it back - this is very effective.'),
('Week 3-4', 'Communication & Ethics Stations',
'These are often the hardest for IMGs from India/China due to cultural differences. '
'Scenarios: breaking bad news (use SPIKES protocol), angry patient, '
'capacity assessment, confidentiality breach, colleague impairment, consent for surgery. '
'Key principle: In the UK, patient autonomy is paramount. Do not be paternalistic. '
'Practice every ethics scenario at least 3 times with a partner. '
'Christmas week: 2 days off maximum, then continue practice.'),
],
'milestones': ['All 6 station types practiced', 'UK trip itinerary finalised', 'PLAB 2 prep course in India completed (if enrolled)'],
},
{
'month': 'JANUARY 2027',
'color': 'D5F5E3',
'hdr_color': '1E8449',
'weeks': [
('Week 1-2', 'Full Mock OSCEs (India)',
'Do MINIMUM 2 full 18-station mock OSCE circuits every week. '
'Find a study group - at least 2 other PLAB candidates. '
'Rotate roles: doctor, patient, examiner. Use PLAB 2 Ninja mock cases or similar. '
'After each mock: debrief every station. What did you miss? Was your language empathetic? '
'Did you address the patient\'s concerns? Did you safety-net? '
'This month should feel like living and breathing OSCE practice.'),
('Week 3', 'Travel to UK + UK Prep Course',
'Fly to Manchester/London. Arrive at least 5-6 days before your exam. '
'Enrol in a UK-based PLAB 2 course (Options: PLAB 2 Ninja UK, SWOT courses, '
'or a local tutor group). These courses are valuable because you practice with '
'other IMGs and get examiner-style feedback. '
'Visit the exam centre (Manchester Medical Examination Centre) to familiarise yourself. '
'Acclimatise - jet lag, new environment, cold weather. Look after your health.'),
('Week 4', 'Final Polish Week (UK)',
'Targeted practice on your weakest station types only. '
'Do NOT try to learn new material. Consolidate. '
'Focus on: opening each station correctly, time management (8 min is short), '
'closing each station professionally with safety-netting. '
'Rest 2 days before exam. Explore Manchester - keep calm and confident.'),
],
'milestones': ['10+ full mock OSCE circuits completed', 'UK-based prep course done', 'Exam venue visited', 'Well-rested before exam'],
},
]
for m in p2_months:
add_colored_heading(doc, f" {m['month']}", m['hdr_color'], 'FFFFFF', 11)
tbl = doc.add_table(rows=1, cols=3)
for ci, htxt in enumerate(['Week', 'Focus', 'Detail']):
cell = tbl.rows[0].cells[ci]
set_cell_bg(cell, m['hdr_color'])
add_cell_text(cell, htxt, bold=True, size=10, color='FFFFFF')
for w, f, d in m['weeks']:
row = tbl.add_row()
for ci, txt in enumerate([w, f, d]):
cell = row.cells[ci]
set_cell_bg(cell, m['color'])
set_cell_borders(cell)
add_cell_text(cell, txt, bold=(ci < 2), size=10)
doc.add_paragraph()
ms_tbl = doc.add_table(rows=1, cols=1)
ms_cell = ms_tbl.rows[0].cells[0]
set_cell_bg(ms_cell, 'FDFEFE')
set_cell_borders(ms_cell, m['hdr_color'])
ms_cell.paragraphs[0].clear()
mhdr = ms_cell.add_paragraph()
mhdr.add_run('Month Milestones:').bold = True
for ms in m['milestones']:
p = ms_cell.add_paragraph(f' β {ms}')
p.runs[0].font.size = Pt(10)
ms_cell.paragraphs[0]._element.getparent().remove(ms_cell.paragraphs[0]._element)
doc.add_paragraph()
doc.add_paragraph()
# ββ PHASE 4: PLAB 2 EXAM βββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PHASE 4 - FEBRUARY 2027: PLAB 2 EXAM MONTH (UK)', '922B21', 'FFFFFF', 14)
doc.add_paragraph()
p2_facts = [
'β’ 18 stations (16 scored, 2 rest), 8 minutes each station + 2 min reading time between',
'β’ Location: Manchester Medical Examination Centre (ONLY location in UK)',
'β’ Pass mark: Must pass sufficient stations; cumulative score assessed',
'β’ Pass rate: ~65-70% overall (higher for well-prepared candidates)',
'β’ Maximum 4 attempts allowed',
'β’ EXAMINER FOCUS: Communication skills, empathy, patient safety, clinical knowledge',
'β’ Common failures: Poor communication, running out of time, not addressing patient concerns (ICE)',
'β’ After passing: GMC issues Certificate of Completion - apply for full GMC registration',
]
pf_tbl = doc.add_table(rows=1, cols=1)
pf_cell = pf_tbl.rows[0].cells[0]
set_cell_bg(pf_cell, 'FDEDEC')
set_cell_borders(pf_cell, '922B21')
pf_cell.paragraphs[0].clear()
ph = pf_cell.add_paragraph()
ph.add_run('PLAB 2 EXAM - KEY FACTS').bold = True
for f in p2_facts:
p = pf_cell.add_paragraph(f)
p.runs[0].font.size = Pt(10)
pf_cell.paragraphs[0]._element.getparent().remove(pf_cell.paragraphs[0]._element)
doc.add_paragraph()
# Station type quick ref
add_colored_heading(doc, ' PLAB 2 Station Types - Quick Reference', '717D7E', 'FFFFFF', 11)
st_data = [
('History Taking', '~5 stations', 'Open questions, ICE, systems review, summarise, safety-net'),
('Examination', '~3 stations', 'State what you are doing, expose properly, thank patient, report findings'),
('Explanation/Counselling', '~3 stations', 'Simple language, check understanding, leaflets, follow-up plan'),
('Communication/Ethics', '~2 stations', 'SPIKES for bad news, CARPA for anger, capacity = MCA 2005'),
('Practical Procedures', '~2 stations', 'Consent, sterile technique, talk through steps, safety'),
('Data Interpretation', '~1 station', 'ECG, ABG, X-ray, bloods - systematic approach, key findings'),
]
st_tbl = doc.add_table(rows=1, cols=3)
for ci, htxt in enumerate(['Station Type', 'Frequency', 'Key Tips']):
cell = st_tbl.rows[0].cells[ci]
set_cell_bg(cell, '717D7E')
add_cell_text(cell, htxt, bold=True, size=10, color='FFFFFF')
for stype, freq, tips in st_data:
row = st_tbl.add_row()
for ci, txt in enumerate([stype, freq, tips]):
set_cell_bg(row.cells[ci], 'F2F3F4')
set_cell_borders(row.cells[ci])
add_cell_text(row.cells[ci], txt, bold=(ci == 0), size=10)
doc.add_paragraph()
# ββ PHASE 5: POST-PLAB βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PHASE 5 - MARCH TO JUNE 2027: GMC Registration + NHS Job + Visa', '1A5276', 'FFFFFF', 14)
doc.add_paragraph()
post_steps = [
('March 2027', 'GMC Full Registration',
'After passing PLAB 2, apply for GMC Full Registration with a licence to practise. '
'Fee: Β£405. Submit: PLAB 2 pass certificate, MBBS, internship certificate, English test, '
'identity documents. Processing: 2-4 weeks if all documents in order. '
'You CANNOT work as a doctor in the UK without this. Priority: get this done immediately.'),
('March-April 2027', 'Job Search - NHS Posts',
'Register on NHS Jobs (jobs.nhs.uk) and Oriel (for specialty training). '
'Start with Trust Grade / Clinical Fellow posts - these are easier to get than training posts. '
'Also look at: Locum posts, GP practices, private hospitals, out-of-hours services. '
'Target hospitals that are known IMG-friendly. '
'WARNING (2026 reality): The NHS job market is competitive. Apply widely (50+ applications '
'is not unusual). Consider Area of Need posts in rural or less popular regions - these hire faster. '
'Network: LinkedIn, BAPIO (British Association of Physicians of Indian Origin), junior doctor forums.'),
('April-May 2027', 'Certificate of Sponsorship (CoS)',
'Once you have a job offer, your NHS employer (as a licensed sponsor) will issue a CoS electronically. '
'This is your permission to apply for the Health and Care Visa. '
'The CoS has a reference number - you use it in your visa application. '
'Apply for your visa WITHIN 3 MONTHS of CoS date. Do not miss this window.'),
('May 2027', 'Health and Care Visa Application',
'Apply online at gov.uk/health-care-visa. Pay: Immigration Health Surcharge (Β£776/year) + '
'visa fee (Β£284 for Health & Care route, much cheaper than standard Skilled Worker). '
'Book biometric appointment at VFS Global centre in India. '
'Processing: ~3 weeks standard (from India). Priority: 5 working days (extra fee). '
'Documents needed: CoS reference, passport, English test, MBBS, GMC certificate, TB test result.'),
('June 2027', 'Arrive in UK',
'Once visa granted: book flights, find accommodation near your hospital '
'(NHS often helps with relocation support for new doctors). '
'Set up: Bank account (Monzo or Starling are easiest as new arrival), '
'NI number application, BRP collection from local post office. '
'Join your hospital induction. Your UK medical career begins here. '
'Astrologically: This lands perfectly in your Jupiter-Venus Antardasha peak window.'),
]
for period, step, detail in post_steps:
tbl_post = doc.add_table(rows=1, cols=3)
for ci, (w, txt) in enumerate([(Cm(3.5), period), (Cm(4.5), step), (Cm(11.5), detail)]):
cell = tbl_post.rows[0].cells[ci]
set_cell_bg(cell, 'EBF5FB' if ci < 2 else 'FDFEFE')
set_cell_borders(cell, '2E86C1')
add_cell_text(cell, txt, bold=(ci < 2), size=10)
cell.width = w
doc.add_paragraph()
# ββ QUICK REFERENCE: PLAB 1 HIGH-YIELD TOPICS ββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' PLAB 1 HIGH-YIELD TOPICS QUICK REFERENCE', '1B3A6B', 'FFFFFF', 14)
doc.add_paragraph()
hy_topics = [
('Cardiology', 'ACS (STEMI: PCI within 90 min; NSTEMI: ticagrelor + aspirin), AF (CHADS2-VASc for anticoag), Heart Failure (ACEi + beta-blocker + spironolactone), Hypertension (NICE ladder: ACEI β CCB β thiazide)'),
('Respiratory', 'Asthma (BTS stepwise: SABA β ICS β LABA β specialist), COPD (NICE: SABA + SAMA β LABA+LAMA β add ICS), PE (Wells score, CTPA, LMWH then DOAC), Pneumonia (CURB-65 scoring for admission)'),
('Neurology', 'Stroke (thrombolysis <4.5h, thrombectomy <6h), Meningitis (IV ceftriaxone, LP after CT if safe), Epilepsy (sodium valproate or levetiracetam 1st line), Parkinson\'s (levodopa + carbidopa)'),
('Endocrine', 'DM2 (metformin 1st β add SGLT2i/GLP1 β sulfonylurea β insulin), Hypothyroid (levothyroxine), Hyperthyroid (carbimazole), Addison\'s (short Synacthen test, hydrocortisone replacement)'),
('Renal', 'AKI stages (KDIGO: creatinine x1.5/x2/x3 baseline), CKD management (ACEi, BP control, diet), UTI (nitrofurantoin 1st line, trimethoprim if CI), Nephrotic (protein >3.5g/day, oedema, hypoalbuminaemia)'),
('Psychiatry', 'Depression (SSRI 1st line, venlafaxine 2nd), Schizophrenia (atypical antipsychotics, clozapine treatment-resistant), Bipolar (lithium, valproate), Section 2 (28 days assessment), Section 3 (6 months treatment)'),
('O&G', 'Pre-eclampsia (labetalol/nifedipine, MgSO4 for seizures), Ectopic (methotrexate if stable + criteria met), Contraception (COCP CIs: migraine with aura, DVT), Cervical screening (25-64 yrs, smear every 3-5 yrs)'),
('Paediatrics', 'Milestones (smiles 6w, sits 6m, walks 12m, 50 words 18m), Febrile seizure (simple: <15 min, generalised), Meningitis (IM benzylpenicillin before hospital), Croup (dexamethasone, nebulised adrenaline if severe)'),
('Ethics/Law', 'Consent: valid if informed, voluntary, capacity. Capacity = MCA 2005 (understand, retain, weigh, communicate). Gillick: under-16 can consent if competent. Notifiable diseases: TB, measles, meningitis, COVID. Duty of candour.'),
('Emergency', 'Anaphylaxis (IM adrenaline 0.5mg, then chlorphenamine + hydrocortisone), Sepsis 6 (bloods, cultures, lactate, O2, IV fluids, IV antibiotics within 1 hr), NEWS2 scoring for deterioration trigger'),
]
hy_tbl = doc.add_table(rows=1, cols=2)
for ci, htxt in enumerate(['System', 'High-Yield Knowledge Points']):
cell = hy_tbl.rows[0].cells[ci]
set_cell_bg(cell, '1B3A6B')
add_cell_text(cell, htxt, bold=True, size=10, color='FFFFFF')
alt = ['F2F3F4', 'EBF5FB']
for i, (sys, points) in enumerate(hy_topics):
row = hy_tbl.add_row()
set_cell_bg(row.cells[0], alt[i % 2])
set_cell_bg(row.cells[1], alt[i % 2])
set_cell_borders(row.cells[0])
set_cell_borders(row.cells[1])
add_cell_text(row.cells[0], sys, bold=True, size=10)
add_cell_text(row.cells[1], points, bold=False, size=9.5)
doc.add_paragraph()
# ββ RECOMMENDED RESOURCES βββββββββββββββββββββββββββββββββββββββββββββββββββββ
add_colored_heading(doc, ' RECOMMENDED RESOURCES & LINKS', '1B3A6B', 'FFFFFF', 13)
doc.add_paragraph()
resources = [
('PLAB 1 Question Banks', [
'PLABABLE - plabable.com (most popular, UK-specific, subscription)',
'Passmedicine - passmedicine.com (UK primary care + hospital, excellent)',
'PasTest PLAB - pastest.co.uk',
]),
('PLAB 2 Resources', [
'PLAB 2 Ninja - plab2ninja.com (courses + mock cases)',
'SWOT Medical - swotmedical.com (UK-based prep courses)',
'Dr Najeeb PLAB 2 videos (YouTube)',
]),
('Essential UK Guidelines (FREE)', [
'NICE Guidelines - nice.org.uk (search by condition)',
'BNF online - bnf.nice.org.uk (free, UK drug reference)',
'GMC Good Medical Practice - gmc-uk.org',
'NHS Clinical Knowledge Summaries - cks.nice.org.uk',
]),
('Books', [
'Oxford Handbook of Clinical Medicine (OHCM) - 10th Edition',
'Oxford Handbook for the PLAB Exam',
'Get Through PLAB: 1000 EMQs (Achar)',
]),
('Community & Support', [
'r/PLAB subreddit - reddit.com/r/PLAB',
'BAPIO - bapio.co.uk (British Association of Physicians of Indian Origin)',
'IMG Connect Facebook groups',
'PLAB WhatsApp groups (search in above communities)',
]),
('Official GMC Links', [
'PLAB overview: gmc-uk.org/registration-and-licensing/join-the-register/plab',
'PLAB 1 booking: gmc-uk.org/plab1',
'PLAB 2 booking: gmc-uk.org/plab2',
'GMC fees: gmc-uk.org/fees',
]),
]
for section_title, items in resources:
p = doc.add_paragraph()
r = p.add_run(f'{section_title}:')
r.bold = True
r.font.color.rgb = RGBColor.from_string('1B3A6B')
r.font.size = Pt(11)
for item in items:
p2 = doc.add_paragraph(f' β’ {item}', style='List Bullet')
p2.runs[0].font.size = Pt(10)
doc.add_paragraph()
# ββ FOOTER NOTE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_colored_heading(doc, ' FINAL NOTES & MOTIVATION', '1B3A6B', 'FFFFFF', 13)
doc.add_paragraph()
final = [
'This schedule is designed to get you from August 2026 to landing in the UK by mid-2027 - '
'a 10-12 month journey from today. It is ambitious but completely realistic for a motivated, '
'disciplined candidate.',
'',
'The single most important thing about PLAB 1: consistency over intensity. 6-8 focused hours '
'daily beats 12-hour cramming sessions followed by burnout. Use spaced repetition - revisit '
'topics at increasing intervals rather than re-reading the same thing repeatedly.',
'',
'The single most important thing about PLAB 2: you cannot prepare alone. Find study partners, '
'practise out loud every single day, and get used to the UK communication style. Empathy, '
'patient-centredness, and safety-netting are what examiners are looking for.',
'',
'The NHS job market in 2026 is competitive. Having PLAB done is not enough - you also need '
'a good application, networking, and persistence. Join IMG communities early, build connections, '
'and apply widely. Do not limit yourself to London or big cities - regional hospitals and '
'Area of Need posts are often faster routes to your first NHS post.',
'',
'You have a strong chart with Venus in the 9th house and Rahu in the 12th - the universe is '
'pointing you abroad. Jupiter-Venus Antardasha from January 2027 is your activation window. '
'Use this preparation period (now to January 2027) to be exam-ready and paperwork-ready so '
'that when that window opens, you can walk through it.',
'',
'Best of luck. The UK NHS needs good doctors, and your MBBS from China + this preparation '
'will get you there.',
]
for line in final:
p = doc.add_paragraph(line)
p.runs[0].font.size = Pt(10.5) if p.runs else None
if not line:
continue
doc.add_paragraph()
footer_p = doc.add_paragraph()
footer_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
fr = footer_p.add_run('Prepared by Orris AI | Based on PLAB 2026 guidelines | Always verify current fees and dates at gmc-uk.org')
fr.font.size = Pt(9)
fr.font.color.rgb = RGBColor.from_string('888888')
# ββ SAVE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
output_path = '/tmp/workspace/plab-schedule/PLAB_Preparation_Schedule_2026_2027.docx'
doc.save(output_path)
print(f'Saved: {output_path}')
cd /tmp/workspace/plab-schedule && python3 build_schedule.py 2>&1
~/plab-schedule/build_schedule.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/plab-schedule/build_schedule.py'
cd /tmp/workspace/plab-schedule && python3 build_schedule.py 2>&1
ls -lh /tmp/workspace/plab-schedule/PLAB_Preparation_Schedule_2026_2027.docx
PLAB Preparation Schedule 2026 2027
Word Document Β· DOCX
| Phase | Period | Content |
|---|---|---|
| Phase 1 | Aug - Oct 2026 | PLAB 1 prep - 3 months, week-by-week breakdown |
| Phase 2 | Nov 2026 | PLAB 1 exam month - final revision + exam day strategy |
| Phase 3 | Nov 2026 - Jan 2027 | PLAB 2 prep - OSCE skills, communication, UK course |
| Phase 4 | Feb 2027 | PLAB 2 exam in UK - station guide, exam day plan |
| Phase 5 | Mar - Jun 2027 | GMC registration, NHS job hunt, Health & Care Visa |
Generate a budget tracking sheet for all exam + visa costs
ls /tmp/workspace/plab-schedule/ && python3 -c "import openpyxl; print('openpyxl', openpyxl.__version__)"
~/plab-schedule/build_budget.py
from openpyxl import Workbook
from openpyxl.styles import (
Font, PatternFill, Alignment, Border, Side, numbers
)
from openpyxl.styles.numbers import FORMAT_NUMBER_COMMA_SEPARATED1
from openpyxl.utils import get_column_letter
from openpyxl.chart import BarChart, Reference, PieChart
from openpyxl.chart.series import DataPoint
from openpyxl.formatting.rule import DataBarRule, ColorScaleRule
from openpyxl.worksheet.datavalidation import DataValidation
import openpyxl
wb = Workbook()
# ββ COLOUR PALETTE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
C_NAVY = "1B3A6B"
C_BLUE = "2E86C1"
C_LIGHT_BLU = "D6EAF8"
C_GREEN = "1E8449"
C_LIGHT_GRN = "D5F5E3"
C_AMBER = "D4AC0D"
C_LIGHT_AMB = "FEF9E7"
C_RED = "C0392B"
C_LIGHT_RED = "FDEDEC"
C_PURPLE = "6C3483"
C_LIGHT_PUR = "F4ECF7"
C_GREY_HDR = "2C3E50"
C_GREY_LITE = "F2F3F4"
C_WHITE = "FFFFFF"
C_INPUT_BG = "EBF5FB"
C_PAID_BG = "EAFAF1"
# ββ STYLE HELPERS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def fill(hex_color):
return PatternFill("solid", fgColor=hex_color)
def font(bold=False, size=10, color="000000", italic=False, name="Arial"):
return Font(bold=bold, size=size, color=color, italic=italic, name=name)
def border(style="thin", color="CCCCCC"):
s = Side(style=style, color=color)
return Border(left=s, right=s, top=s, bottom=s)
def thick_border(color="999999"):
t = Side(style="medium", color=color)
n = Side(style="thin", color="CCCCCC")
return Border(left=t, right=t, top=t, bottom=t)
def align(h="left", v="center", wrap=False):
return Alignment(horizontal=h, vertical=v, wrap_text=wrap)
INR_FMT = 'βΉ#,##0;(βΉ#,##0);"-"'
GBP_FMT = 'Β£#,##0.00;(Β£#,##0.00);"-"'
PCT_FMT = '0.0%;-0.0%;"-"'
DATE_FMT = 'MMM YYYY'
def set_col_width(ws, col, width):
ws.column_dimensions[get_column_letter(col)].width = width
def merge_header(ws, row, col_start, col_end, text,
bg=C_NAVY, fg=C_WHITE, size=11, bold=True):
ws.merge_cells(start_row=row, start_column=col_start,
end_row=row, end_column=col_end)
cell = ws.cell(row=row, column=col_start, value=text)
cell.fill = fill(bg)
cell.font = font(bold=bold, size=size, color=fg)
cell.alignment = align("center")
cell.border = border("thin", "AAAAAA")
return cell
def col_header(ws, row, col, text, bg=C_GREY_HDR, fg=C_WHITE, size=10, wrap=True):
c = ws.cell(row=row, column=col, value=text)
c.fill = fill(bg)
c.font = font(bold=True, size=size, color=fg)
c.alignment = align("center", "center", wrap)
c.border = border("thin", "888888")
return c
def data_cell(ws, row, col, value=None, fmt=None,
bg=C_WHITE, fg="000000", bold=False,
h_align="left", border_color="CCCCCC"):
c = ws.cell(row=row, column=col, value=value)
c.fill = fill(bg)
c.font = font(bold=bold, color=fg, size=10)
c.alignment = align(h_align, "center", wrap_text=(h_align == "left"))
c.border = border("thin", border_color)
if fmt:
c.number_format = fmt
return c
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 1: OVERVIEW DASHBOARD
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws1 = wb.active
ws1.title = "π Dashboard"
ws1.sheet_view.showGridLines = False
ws1.row_dimensions[1].height = 8
# Column widths
widths = [2, 28, 16, 16, 16, 16, 20, 3]
for i, w in enumerate(widths, 1):
set_col_width(ws1, i, w)
# ββ Title block βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws1.row_dimensions[2].height = 36
merge_header(ws1, 2, 2, 7,
"π¬π§ PLAB EXAM & UK VISA β COMPLETE BUDGET TRACKER",
bg=C_NAVY, fg=C_WHITE, size=16)
ws1.row_dimensions[3].height = 18
merge_header(ws1, 3, 2, 7,
"MBBS (China) β UK NHS Doctor | August 2026 β June 2027 | All amounts in INR (βΉ) & GBP (Β£)",
bg=C_BLUE, fg=C_WHITE, size=10, bold=False)
ws1.row_dimensions[4].height = 8
# ββ Exchange rate assumption (user input) βββββββββββββββββββββββββββββββββββββ
ws1.row_dimensions[5].height = 22
merge_header(ws1, 5, 2, 4, "β KEY ASSUMPTIONS (blue cells = change these)",
bg=C_GREY_HDR, fg=C_WHITE, size=10)
labels = ["GBP β INR rate (today)", "Contingency buffer %", "Today's Date"]
vals = [108, 15, "01-Aug-2026"]
fmts = ["#,##0", "0%", "@"]
for i, (lbl, val, fmt) in enumerate(zip(labels, vals, fmts)):
r = 6 + i
ws1.row_dimensions[r].height = 20
c1 = ws1.cell(row=r, column=2, value=lbl)
c1.fill = fill(C_GREY_LITE)
c1.font = font(bold=True, size=10)
c1.alignment = align()
c1.border = border()
c2 = ws1.cell(row=r, column=3, value=val)
c2.fill = fill(C_INPUT_BG)
c2.font = font(bold=True, size=11, color="0000FF") # blue = input
c2.alignment = align("center")
c2.border = border("medium", C_BLUE)
c2.number_format = fmt
# Named references for formula use
# GBP rate = C6, contingency = C7
ws1.row_dimensions[9].height = 8
# ββ Summary KPI cards βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
kpi_row = 10
ws1.row_dimensions[kpi_row].height = 18
merge_header(ws1, kpi_row, 2, 7, "π° COST SUMMARY AT A GLANCE",
bg=C_GREY_HDR, fg=C_WHITE, size=10)
# Headers
ws1.row_dimensions[kpi_row+1].height = 40
kpi_headers = ["Category", "GBP (Β£)", "INR (βΉ)", "% of Total", "Status", "Notes"]
kpi_bg = [C_GREY_HDR]*6
for j, (h, bg) in enumerate(zip(kpi_headers, kpi_bg)):
col_header(ws1, kpi_row+1, j+2, h, bg=bg)
# KPI rows - (category, gbp_value, status, note)
kpi_data = [
("PLAB 1 Exam Fee", 268, "Pending", "GMC fee (as of 2025). Verify at gmc-uk.org"),
("PLAB 2 Exam Fee", 998, "Pending", "GMC fee per attempt. Manchester only"),
("IELTS / OET Exam", 0, "Pending", "IELTS ~βΉ16,500 | OET ~βΉ18,000. Enter in INR"),
("GMC Registration (Full)", 405, "Pending", "After passing PLAB 2"),
("English Language Test (INT)", 0, "Pending", "If retake needed"),
("PLAB 1 Prep Materials", 0, "Pending", "PLABABLE ~Β£90/mo | Passmedicine ~Β£70/mo"),
("PLAB 2 Prep Course (India)", 0, "Pending", "Marrow/DAMS PLAB batch ~βΉ25,000-50,000"),
("PLAB 2 Prep Course (UK)", 0, "Pending", "PLAB2Ninja UK course ~Β£300-500"),
("UK Trip: Flights", 0, "Pending", "Return flight India β Manchester ~Β£600-900"),
("UK Trip: Accommodation", 0, "Pending", "~Β£500-800 for 3-4 week stay"),
("UK Trip: Food & Transport", 0, "Pending", "~Β£400-600 for trip duration"),
("TB Test (mandatory for visa)", 0, "Pending", "~βΉ4,000 at UKVI-approved clinic"),
("Biometric Appointment (VFS)", 0, "Pending", "~βΉ2,000-3,500 at VFS Global India"),
("Health & Care Visa Fee", 284, "Pending", "Cheaper than standard Skilled Worker"),
("Immigration Health Surcharge","", "Pending", "Β£776/yr Γ visa years (e.g., 3yr = Β£2,328)"),
("Document Translation/Notary", 0, "Pending", "MBBS, internship cert etc. ~βΉ5,000-10,000"),
("Relocation (India β UK)", 0, "Pending", "Baggage, shipping, misc. ~βΉ20,000-50,000"),
]
row_bg_alt = [C_WHITE, C_GREY_LITE]
phase_colors = {
"PLAB 1": C_LIGHT_BLU,
"PLAB 2": C_LIGHT_PUR,
"Visa" : C_LIGHT_GRN,
"Travel": C_LIGHT_AMB,
"Other" : C_WHITE,
}
status_options = '"Pending,Paid,Budgeted,N/A"'
for i, (cat, gbp, status, note) in enumerate(kpi_data):
r = kpi_row + 2 + i
ws1.row_dimensions[r].height = 18
bg = row_bg_alt[i % 2]
# Category
data_cell(ws1, r, 2, cat, bg=bg, bold=False, border_color="BBBBBB")
# GBP
c_gbp = ws1.cell(row=r, column=3, value=gbp if isinstance(gbp, (int, float)) else None)
c_gbp.fill = fill(C_INPUT_BG)
c_gbp.font = font(color="0000FF", size=10)
c_gbp.alignment = align("right", "center")
c_gbp.border = border("thin", "BBBBBB")
if isinstance(gbp, (int, float)):
c_gbp.number_format = GBP_FMT
# INR (formula: GBP * rate, or direct input if GBP=0)
c_inr = ws1.cell(row=r, column=4)
if isinstance(gbp, (int, float)) and gbp > 0:
c_inr.value = f"=C{r}*$C$6"
else:
c_inr.value = 0 # user fills in INR directly
c_inr.fill = fill(C_INPUT_BG)
c_inr.font = font(color="0000FF", size=10)
c_inr.alignment = align("right", "center")
c_inr.border = border("thin", "BBBBBB")
c_inr.number_format = INR_FMT
# % of total (will be filled after total row is known)
ws1.cell(row=r, column=5).value = f"=IF(D{kpi_row+2+len(kpi_data)+1}=0,\"-\",D{r}/D{kpi_key_total_row if False else 'PLACEHOLDER'})"
# Status dropdown
c_status = ws1.cell(row=r, column=6, value=status)
c_status.fill = fill(C_LIGHT_GRN if status == "Paid" else C_LIGHT_AMB)
c_status.font = font(size=10, bold=(status == "Paid"))
c_status.alignment = align("center", "center")
c_status.border = border("thin", "BBBBBB")
# Notes
data_cell(ws1, r, 7, note, bg=bg, border_color="BBBBBB")
# Total row
total_r = kpi_row + 2 + len(kpi_data)
ws1.row_dimensions[total_r].height = 22
merge_header(ws1, total_r, 2, 2, "TOTAL BASE COST", bg=C_NAVY, fg=C_WHITE, size=10)
c_total_gbp = ws1.cell(row=total_r, column=3, value=f"=SUM(C{kpi_row+2}:C{total_r-1})")
c_total_gbp.fill = fill(C_NAVY)
c_total_gbp.font = font(bold=True, size=11, color=C_WHITE)
c_total_gbp.alignment = align("right", "center")
c_total_gbp.border = thick_border(C_NAVY)
c_total_gbp.number_format = GBP_FMT
c_total_inr = ws1.cell(row=total_r, column=4, value=f"=SUM(D{kpi_row+2}:D{total_r-1})")
c_total_inr.fill = fill(C_NAVY)
c_total_inr.font = font(bold=True, size=11, color=C_WHITE)
c_total_inr.alignment = align("right", "center")
c_total_inr.border = thick_border(C_NAVY)
c_total_inr.number_format = INR_FMT
ws1.cell(row=total_r, column=5, value="-").fill = fill(C_NAVY)
ws1.cell(row=total_r, column=5).font = font(color=C_WHITE)
ws1.cell(row=total_r, column=6, value="β").fill = fill(C_NAVY)
ws1.cell(row=total_r, column=6).font = font(color=C_WHITE)
ws1.cell(row=total_r, column=7, value="Excl. contingency").fill = fill(C_NAVY)
ws1.cell(row=total_r, column=7).font = font(color=C_WHITE)
# Now fix % of total formulas
for i in range(len(kpi_data)):
r = kpi_row + 2 + i
c_pct = ws1.cell(row=r, column=5)
c_pct.value = f"=IF(D{total_r}=0,0,D{r}/D{total_r})"
c_pct.number_format = PCT_FMT
c_pct.alignment = align("center", "center")
c_pct.border = border("thin", "BBBBBB")
c_pct.font = font(size=10, color="000000")
c_pct.fill = fill(C_GREY_LITE if i % 2 else C_WHITE)
# Contingency row
cont_r = total_r + 1
ws1.row_dimensions[cont_r].height = 20
merge_header(ws1, cont_r, 2, 2, "Contingency (15%)", bg=C_AMBER, fg=C_WHITE, size=10)
c_cont_gbp = ws1.cell(row=cont_r, column=3, value=f"=C{total_r}*$C$7")
c_cont_gbp.fill = fill(C_LIGHT_AMB)
c_cont_gbp.font = font(bold=True, size=10, color="000000")
c_cont_gbp.alignment = align("right")
c_cont_gbp.number_format = GBP_FMT
c_cont_gbp.border = border("medium", C_AMBER)
c_cont_inr = ws1.cell(row=cont_r, column=4, value=f"=D{total_r}*$C$7")
c_cont_inr.fill = fill(C_LIGHT_AMB)
c_cont_inr.font = font(bold=True, size=10, color="000000")
c_cont_inr.alignment = align("right")
c_cont_inr.number_format = INR_FMT
c_cont_inr.border = border("medium", C_AMBER)
# Grand total
gt_r = cont_r + 1
ws1.row_dimensions[gt_r].height = 26
merge_header(ws1, gt_r, 2, 2, "β
GRAND TOTAL (with contingency)", bg=C_GREEN, fg=C_WHITE, size=11)
c_gt_gbp = ws1.cell(row=gt_r, column=3, value=f"=C{total_r}+C{cont_r}")
c_gt_gbp.fill = fill(C_GREEN)
c_gt_gbp.font = font(bold=True, size=12, color=C_WHITE)
c_gt_gbp.alignment = align("right", "center")
c_gt_gbp.number_format = GBP_FMT
c_gt_gbp.border = thick_border(C_GREEN)
c_gt_inr = ws1.cell(row=gt_r, column=4, value=f"=D{total_r}+D{cont_r}")
c_gt_inr.fill = fill(C_GREEN)
c_gt_inr.font = font(bold=True, size=12, color=C_WHITE)
c_gt_inr.alignment = align("right", "center")
c_gt_inr.number_format = INR_FMT
c_gt_inr.border = thick_border(C_GREEN)
ws1.cell(row=gt_r, column=5).fill = fill(C_GREEN)
ws1.cell(row=gt_r, column=6).fill = fill(C_GREEN)
ws1.cell(row=gt_r, column=7, value="β Your total budget needed").fill = fill(C_GREEN)
ws1.cell(row=gt_r, column=7).font = font(bold=True, color=C_WHITE, size=10, italic=True)
# Add dropdown validation for status column
dv = DataValidation(type="list", formula1=status_options, allow_blank=True)
dv.sqref = f"F{kpi_row+2}:F{total_r-1}"
ws1.add_data_validation(dv)
# ββ Paid so far tracker βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
paid_r = gt_r + 2
ws1.row_dimensions[paid_r].height = 18
merge_header(ws1, paid_r, 2, 7, "π PAYMENT PROGRESS TRACKER",
bg=C_GREY_HDR, fg=C_WHITE, size=10)
ws1.row_dimensions[paid_r+1].height = 20
for j, h in enumerate(["Metric", "GBP (Β£)", "INR (βΉ)", "% Complete", "", ""], 1):
col_header(ws1, paid_r+1, j+1, h, bg=C_BLUE)
tracker_rows = [
("Total Budget (incl. contingency)", f"=C{gt_r}", f"=D{gt_r}"),
("Amount Paid to Date", 0, 0),
("Amount Remaining", f"=C{paid_r+2}-C{paid_r+3}", f"=D{paid_r+2}-D{paid_r+3}"),
]
for i, (lbl, gbp_val, inr_val) in enumerate(tracker_rows):
r = paid_r + 2 + i
ws1.row_dimensions[r].height = 20
bg = [C_GREY_LITE, C_LIGHT_BLU, C_LIGHT_GRN][i]
c_lbl = ws1.cell(row=r, column=2, value=lbl)
c_lbl.fill = fill(bg)
c_lbl.font = font(bold=True, size=10)
c_lbl.alignment = align()
c_lbl.border = border()
c_g = ws1.cell(row=r, column=3, value=gbp_val)
c_g.fill = fill(C_INPUT_BG if i == 1 else bg)
c_g.font = font(bold=(i != 1), color="0000FF" if i == 1 else "000000", size=10)
c_g.alignment = align("right")
c_g.number_format = GBP_FMT
c_g.border = border()
c_i = ws1.cell(row=r, column=4, value=inr_val)
c_i.fill = fill(C_INPUT_BG if i == 1 else bg)
c_i.font = font(bold=(i != 1), color="0000FF" if i == 1 else "000000", size=10)
c_i.alignment = align("right")
c_i.number_format = INR_FMT
c_i.border = border()
c_pct = ws1.cell(row=r, column=5)
if i == 0:
c_pct.value = "100.0%"
c_pct.number_format = PCT_FMT
elif i == 1:
c_pct.value = f"=IF(C{paid_r+2}=0,0,C{r}/C{paid_r+2})"
c_pct.number_format = PCT_FMT
c_pct.fill = fill(bg)
c_pct.alignment = align("center")
c_pct.border = border()
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 2: MONTHLY CASHFLOW
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws2 = wb.create_sheet("π
Monthly Cashflow")
ws2.sheet_view.showGridLines = False
# Column setup
months = ["Aug 2026", "Sep 2026", "Oct 2026", "Nov 2026",
"Dec 2026", "Jan 2027", "Feb 2027", "Mar 2027",
"Apr 2027", "May 2027", "Jun 2027"]
col_widths2 = [2, 30] + [13]*len(months) + [13, 3]
for i, w in enumerate(col_widths2, 1):
set_col_width(ws2, i, w)
ws2.row_dimensions[1].height = 8
ws2.row_dimensions[2].height = 36
merge_header(ws2, 2, 2, 2+len(months)+1,
"π
MONTHLY CASHFLOW PLAN | When to Spend What",
bg=C_NAVY, fg=C_WHITE, size=14)
ws2.row_dimensions[3].height = 18
merge_header(ws2, 3, 2, 2+len(months)+1,
"Enter amounts in INR (βΉ). GBP costs auto-converted using rate on Dashboard sheet.",
bg=C_BLUE, fg=C_WHITE, size=10, bold=False)
ws2.row_dimensions[4].height = 8
# Month headers
ws2.row_dimensions[5].height = 30
col_header(ws2, 5, 2, "Expense Item", bg=C_GREY_HDR)
for j, m in enumerate(months):
col_header(ws2, 5, 3+j, m, bg=C_GREY_HDR)
col_header(ws2, 5, 3+len(months), "TOTAL", bg=C_NAVY)
# Cashflow rows: (category, label, amounts_by_month_dict)
# months index: 0=Aug26, 1=Sep26, 2=Oct26, 3=Nov26, 4=Dec26, 5=Jan27, 6=Feb27, 7=Mar27, 8=Apr27, 9=May27, 10=Jun27
cashflow_sections = [
{
"section": "PLAB 1 COSTS",
"bg_hdr": C_BLUE,
"items": [
("IELTS/OET Exam Fee", {1: 16500}),
("IELTS Prep Materials", {0: 3000, 1: 3000}),
("PLAB 1 Exam Fee (Β£268)", {2: 28944}), # 268 Γ 108
("PLABABLE / Passmedicine Sub.", {0: 9720, 1: 9720, 2: 9720}),
("PLAB 1 Study Books", {0: 4000}),
("Mock Test Fees (if any)", {2: 2000}),
]
},
{
"section": "PLAB 2 COSTS",
"bg_hdr": C_PURPLE,
"items": [
("PLAB 2 Exam Fee (Β£998)", {6: 107784}), # 998 Γ 108
("PLAB 2 Prep Course India", {4: 35000}),
("PLAB 2 Ninja / Online Course", {4: 5000, 5: 5000}),
("PLAB 2 Prep Books", {4: 2000}),
]
},
{
"section": "UK TRIP (PLAB 2 Exam)",
"bg_hdr": C_AMBER,
"items": [
("Return Flights India-Manchester", {5: 75000}),
("UK Accommodation (3-4 weeks)", {5: 54000}), # Β£500 Γ 108
("UK Prep Course (in-person)", {5: 43200}), # Β£400 Γ 108
("Food & Local Transport (UK)", {5: 45000}), # Β£417 Γ 108
("Travel Insurance", {5: 4000}),
("SIM Card / Mobile (UK)", {5: 1500}),
]
},
{
"section": "GMC REGISTRATION",
"bg_hdr": C_GREEN,
"items": [
("GMC Full Registration (Β£405)", {7: 43740}), # 405 Γ 108
("Document Translation/Notarisation",{0: 5000, 7: 5000}),
("Certificate Verification (MyIntealth)",{0: 3000}),
]
},
{
"section": "VISA COSTS",
"bg_hdr": C_RED,
"items": [
("TB Test (UKVI approved clinic)", {9: 4000}),
("Biometric Appt (VFS Global)", {9: 3000}),
("Health & Care Visa Fee (Β£284)", {9: 30672}), # 284 Γ 108
("Immigration Health Surcharge (3yr)", {9: 251856}), # 776Γ3Γ108
("Visa Photo / Misc docs", {9: 1000}),
]
},
{
"section": "RELOCATION",
"bg_hdr": C_GREY_HDR,
"items": [
("One-way Flight to UK (final move)", {10: 55000}),
("Initial Accommodation (UK)", {10: 32400}), # Β£300 Γ 108
("Household Setup / Misc", {10: 20000}),
("Emergency Fund", {10: 50000}),
]
},
]
current_row = 6
section_total_rows = []
all_data_rows = []
for section_info in cashflow_sections:
ws2.row_dimensions[current_row].height = 20
merge_header(ws2, current_row, 2, 2+len(months)+1,
section_info["section"],
bg=section_info["bg_hdr"], fg=C_WHITE, size=10)
current_row += 1
section_start = current_row
for item_label, amounts in section_info["items"]:
ws2.row_dimensions[current_row].height = 18
bg_alt = C_WHITE if (current_row % 2 == 0) else C_GREY_LITE
c_lbl = ws2.cell(row=current_row, column=2, value=item_label)
c_lbl.fill = fill(bg_alt)
c_lbl.font = font(size=10)
c_lbl.alignment = align("left", "center")
c_lbl.border = border()
for j in range(len(months)):
val = amounts.get(j, None)
c = ws2.cell(row=current_row, column=3+j, value=val)
c.fill = fill(C_INPUT_BG if val is not None else bg_alt)
c.font = font(color="0000FF" if val is not None else "000000", size=10)
c.alignment = align("right", "center")
c.number_format = INR_FMT
c.border = border()
# Row total
first_col = get_column_letter(3)
last_col = get_column_letter(3 + len(months) - 1)
c_tot = ws2.cell(row=current_row, column=3+len(months),
value=f"=SUM({first_col}{current_row}:{last_col}{current_row})")
c_tot.fill = fill(C_LIGHT_BLU)
c_tot.font = font(bold=True, size=10, color="000000")
c_tot.alignment = align("right")
c_tot.number_format = INR_FMT
c_tot.border = border("medium", C_BLUE)
all_data_rows.append(current_row)
current_row += 1
# Section subtotal
ws2.row_dimensions[current_row].height = 20
c_sub_lbl = ws2.cell(row=current_row, column=2,
value=f" Subtotal β {section_info['section']}")
c_sub_lbl.fill = fill(section_info["bg_hdr"] + "30" if len(section_info["bg_hdr"]) == 6 else C_GREY_LITE)
c_sub_lbl.fill = fill("E8E8E8")
c_sub_lbl.font = font(bold=True, size=10)
c_sub_lbl.alignment = align()
c_sub_lbl.border = border("medium", "888888")
for j in range(len(months)):
col_letter = get_column_letter(3+j)
c_sub = ws2.cell(row=current_row, column=3+j,
value=f"=SUM({col_letter}{section_start}:{col_letter}{current_row-1})")
c_sub.fill = fill("E8E8E8")
c_sub.font = font(bold=True, size=10)
c_sub.alignment = align("right")
c_sub.number_format = INR_FMT
c_sub.border = border("medium", "888888")
# Subtotal total column
fc = get_column_letter(3)
lc = get_column_letter(3+len(months)-1)
c_sub_t = ws2.cell(row=current_row, column=3+len(months),
value=f"=SUM({fc}{current_row}:{lc}{current_row})")
c_sub_t.fill = fill("CCCCCC")
c_sub_t.font = font(bold=True, size=10)
c_sub_t.alignment = align("right")
c_sub_t.number_format = INR_FMT
c_sub_t.border = border("medium", "555555")
section_total_rows.append(current_row)
current_row += 2 # blank spacer
# GRAND TOTAL ROW
ws2.row_dimensions[current_row].height = 28
merge_header(ws2, current_row, 2, 2, "GRAND TOTAL (INR βΉ)", bg=C_NAVY, fg=C_WHITE, size=11)
for j in range(len(months)):
col_letter = get_column_letter(3+j)
col_refs = "+".join([f"{col_letter}{r}" for r in section_total_rows])
c_gt = ws2.cell(row=current_row, column=3+j, value=f"={col_refs}")
c_gt.fill = fill(C_NAVY)
c_gt.font = font(bold=True, size=11, color=C_WHITE)
c_gt.alignment = align("right")
c_gt.number_format = INR_FMT
c_gt.border = thick_border(C_NAVY)
fc = get_column_letter(3)
lc = get_column_letter(3+len(months)-1)
c_gt_total = ws2.cell(row=current_row, column=3+len(months),
value=f"=SUM({fc}{current_row}:{lc}{current_row})")
c_gt_total.fill = fill(C_GREEN)
c_gt_total.font = font(bold=True, size=13, color=C_WHITE)
c_gt_total.alignment = align("right")
c_gt_total.number_format = INR_FMT
c_gt_total.border = thick_border(C_GREEN)
# RUNNING TOTAL ROW
cr2 = current_row + 1
ws2.row_dimensions[cr2].height = 20
merge_header(ws2, cr2, 2, 2, "Running Cumulative (βΉ)", bg=C_BLUE, fg=C_WHITE, size=10)
for j in range(len(months)):
col_letter = get_column_letter(3+j)
if j == 0:
formula = f"={col_letter}{current_row}"
else:
prev_col = get_column_letter(3+j-1)
formula = f"={prev_col}{cr2}+{col_letter}{current_row}"
c_run = ws2.cell(row=cr2, column=3+j, value=formula)
c_run.fill = fill(C_LIGHT_BLU)
c_run.font = font(bold=True, size=10, color="000000")
c_run.alignment = align("right")
c_run.number_format = INR_FMT
c_run.border = border("medium", C_BLUE)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 3: EXPENSE LOG (actual spending tracker)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws3 = wb.create_sheet("π§Ύ Expense Log")
ws3.sheet_view.showGridLines = False
log_cols = [2, 16, 16, 16, 16, 20, 28, 3]
lc_widths = [2, 14, 20, 13, 13, 18, 14, 32, 3]
for i, w in enumerate(lc_widths, 1):
set_col_width(ws3, i, w)
ws3.row_dimensions[1].height = 8
ws3.row_dimensions[2].height = 36
merge_header(ws3, 2, 2, 8, "π§Ύ ACTUAL EXPENSE LOG | Record Every Payment Here",
bg=C_NAVY, fg=C_WHITE, size=14)
ws3.row_dimensions[3].height = 18
merge_header(ws3, 3, 2, 8,
"Log every actual payment as you make it. Mark status as PAID. Keep receipts.",
bg=C_BLUE, fg=C_WHITE, size=10, bold=False)
ws3.row_dimensions[4].height = 8
# Headers
ws3.row_dimensions[5].height = 30
log_headers = ["#", "Date Paid", "Expense Item", "Currency", "Amount (orig.)", "Amount (INR βΉ)", "Category", "Notes / Receipt #"]
log_bg = C_GREY_HDR
for j, h in enumerate(log_headers, 1):
col_header(ws3, 5, j+1, h, bg=log_bg)
# Pre-fill known future payments as reminders
prefill = [
("", "Aug 2026", "IELTS Exam Fee", "INR", 16500, "PLAB 1", "Book at ieltsindia.org"),
("", "Aug 2026", "PLABABLE Subscription (Month 1)", "INR", 9720, "PLAB 1", "plabable.com"),
("", "Aug 2026", "PLAB 1 Study Books", "INR", 4000, "PLAB 1", "OHCM + Oxford PLAB Handbook"),
("", "Sep 2026", "PLABABLE Subscription (Month 2)", "INR", 9720, "PLAB 1", ""),
("", "Oct 2026", "PLAB 1 Exam Fee", "GBP", 268, "PLAB 1", "gmc-uk.org/plab1 - book slot fast!"),
("", "Oct 2026", "PLABABLE Subscription (Month 3)", "INR", 9720, "PLAB 1", ""),
("", "Dec 2026", "PLAB 2 Prep Course (India)", "INR", 35000, "PLAB 2", "Marrow/DAMS PLAB batch"),
("", "Jan 2027", "Return Flights (India-Manchester)","INR", 75000, "UK Trip", "Book early for better price"),
("", "Jan 2027", "UK Accommodation (pre-booked)", "INR", 54000, "UK Trip", "Airbnb or guesthouse near Manchester"),
("", "Feb 2027", "PLAB 2 Exam Fee", "GBP", 998, "PLAB 2", "gmc-uk.org/plab2"),
("", "Mar 2027", "GMC Full Registration", "GBP", 405, "GMC", "After passing PLAB 2"),
("", "May 2027", "TB Test (UKVI clinic)", "INR", 4000, "Visa", "UKVI-approved clinic only"),
("", "May 2027", "Biometric Appointment (VFS)", "INR", 3000, "Visa", "VFS Global India"),
("", "May 2027", "Health & Care Visa Fee", "GBP", 284, "Visa", "gov.uk/health-care-visa"),
("", "May 2027", "Immigration Health Surcharge 3yr", "GBP", 2328, "Visa", "Β£776/yr Γ 3 = Β£2,328"),
("", "Jun 2027", "One-way Flight to UK", "INR", 55000, "Relocation","Final move to UK"),
]
for i, (num, date, item, curr, amt, cat, notes) in enumerate(prefill):
r = 6 + i
ws3.row_dimensions[r].height = 18
bg = C_WHITE if i % 2 == 0 else C_GREY_LITE
ws3.cell(row=r, column=2, value=i+1).alignment = align("center")
ws3.cell(row=r, column=2).border = border()
ws3.cell(row=r, column=2).fill = fill(bg)
ws3.cell(row=r, column=2).font = font(size=9, color="888888")
c_date = ws3.cell(row=r, column=3, value=date)
c_date.fill = fill(C_INPUT_BG)
c_date.font = font(color="0000FF", size=10)
c_date.alignment = align("center")
c_date.border = border()
c_item = ws3.cell(row=r, column=4, value=item)
c_item.fill = fill(bg)
c_item.font = font(size=10)
c_item.alignment = align()
c_item.border = border()
c_curr = ws3.cell(row=r, column=5, value=curr)
c_curr.fill = fill(bg)
c_curr.font = font(size=10, color="555555")
c_curr.alignment = align("center")
c_curr.border = border()
c_amt = ws3.cell(row=r, column=6, value=amt)
c_amt.fill = fill(C_INPUT_BG)
c_amt.font = font(color="0000FF", size=10)
c_amt.alignment = align("right")
c_amt.number_format = '#,##0.00'
c_amt.border = border()
# INR equivalent
c_inr = ws3.cell(row=r, column=7)
if curr == "GBP":
c_inr.value = f"=F{r}*'π Dashboard'!C6"
c_inr.font = font(size=10, color="000000")
else:
c_inr.value = f"=F{r}"
c_inr.font = font(size=10, color="000000")
c_inr.fill = fill(bg)
c_inr.alignment = align("right")
c_inr.number_format = INR_FMT
c_inr.border = border()
c_cat = ws3.cell(row=r, column=8, value=cat)
c_cat_colors = {"PLAB 1": C_LIGHT_BLU, "PLAB 2": C_LIGHT_PUR,
"UK Trip": C_LIGHT_AMB, "GMC": C_LIGHT_GRN,
"Visa": C_LIGHT_RED, "Relocation": C_GREY_LITE}
c_cat.fill = fill(c_cat_colors.get(cat, C_WHITE))
c_cat.font = font(size=10)
c_cat.alignment = align("center")
c_cat.border = border()
c_notes = ws3.cell(row=r, column=9, value=notes)
c_notes.fill = fill(bg)
c_notes.font = font(size=9, color="555555", italic=True)
c_notes.alignment = align("left", "center", wrap=True)
c_notes.border = border()
# Add 20 blank rows for user entries
for i in range(20):
r = 6 + len(prefill) + i
ws3.row_dimensions[r].height = 18
bg = C_WHITE if i % 2 == 0 else C_GREY_LITE
for col in range(2, 10):
c = ws3.cell(row=r, column=col)
c.fill = fill(C_INPUT_BG if col in [3, 6] else bg)
c.border = border()
if col == 7:
last_r = 6 + len(prefill) + i
c.number_format = INR_FMT
# Running total at bottom
tot_r3 = 6 + len(prefill) + 20 + 1
ws3.row_dimensions[tot_r3].height = 24
merge_header(ws3, tot_r3, 2, 6, "TOTAL SPENT TO DATE (INR βΉ)", bg=C_GREEN, fg=C_WHITE, size=11)
c_total3 = ws3.cell(row=tot_r3, column=7,
value=f"=SUM(G6:G{tot_r3-1})")
c_total3.fill = fill(C_GREEN)
c_total3.font = font(bold=True, size=13, color=C_WHITE)
c_total3.alignment = align("right")
c_total3.number_format = INR_FMT
c_total3.border = thick_border(C_GREEN)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 4: NOTES & TIPS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws4 = wb.create_sheet("π Notes & Tips")
ws4.sheet_view.showGridLines = False
set_col_width(ws4, 1, 2)
set_col_width(ws4, 2, 30)
set_col_width(ws4, 3, 65)
set_col_width(ws4, 4, 3)
ws4.row_dimensions[1].height = 8
ws4.row_dimensions[2].height = 36
merge_header(ws4, 2, 2, 3, "π MONEY-SAVING TIPS & IMPORTANT NOTES",
bg=C_NAVY, fg=C_WHITE, size=14)
ws4.row_dimensions[3].height = 8
tips = [
("PLAB 1 EXAM FEE", C_BLUE, C_LIGHT_BLU, [
"Verify current fee at gmc-uk.org before booking. Fees updated every April.",
"PLAB 1 can be sat in India - no need to fly to UK for Part 1.",
"Book the MOMENT slots open - they sell out in minutes. Set phone alerts.",
"Use the GMC's free PLAB 1 blueprint document to focus your revision.",
]),
("PLAB 2 EXAM FEE", C_PURPLE, C_LIGHT_PUR, [
"PLAB 2 is ONLY in Manchester. You must travel to UK.",
"Book your UK trip and exam slot simultaneously. Accommodation near the exam centre fills fast.",
"Maximum 4 attempts. Each attempt costs Β£998 - make sure you are ready before booking.",
"Factor in Β£1,500-2,500 total for the UK trip (flights + accommodation + food + course).",
]),
("IELTS / ENGLISH TEST", C_GREEN, C_LIGHT_GRN, [
"IELTS Academic (not General Training) is required. Score valid for 2 years.",
"Minimum: 7.5 overall, 7.0 in EACH band. All four bands must meet minimum.",
"OET is an alternative - Grade B in all 4 sections. More expensive than IELTS.",
"Book your test for September 2026 to have results before applying for PLAB 1.",
]),
("GMC REGISTRATION", C_AMBER, C_LIGHT_AMB, [
"Start GMC identity verification NOW - it takes 4-8 weeks.",
"Ensure your China MBBS institution is listed in the World Directory of Medical Schools (WDOMS).",
"Keep certified copies of: MBBS certificate, transcripts, internship completion, ID, English test.",
"GMC Full Registration fee (Β£405) is paid AFTER passing PLAB 2.",
]),
("HEALTH & CARE VISA", C_RED, C_LIGHT_RED, [
"Doctors in the NHS qualify for the Health & Care Visa - cheaper and faster than Skilled Worker.",
"TB test is mandatory for Indian applicants - must use a UKVI-approved clinic. Book early.",
"Immigration Health Surcharge (IHS): Β£776/year. For 3 years = Β£2,328. Paid upfront with visa.",
"Visa fee from India: Β£284 (Health & Care route). Processing: ~3 weeks standard.",
"NEW from Jan 2026: B2 English evidence required with application. Factor extra 2-3 weeks.",
"Priority service: 5 working days (+fee). Super-priority: next working day (+higher fee).",
"You must apply within 3 months of your Certificate of Sponsorship (CoS) date.",
]),
("MONEY-SAVING HACKS", C_GREEN, C_LIGHT_GRN, [
"Share UK accommodation with other PLAB 2 candidates - split costs to halve the expense.",
"Book flights 3-4 months in advance for PLAB 2 UK trip (Jan 2027 target = book Sep 2026).",
"Student/IMG discounts: Ask prep course providers - many offer group or early-bird rates.",
"PLABABLE + Passmedicine: Consider doing one free trial first to see which you prefer.",
"NHS relocation support: Some NHS trusts offer relocation packages (Β£1,000-5,000). Ask HR.",
"Monzo/Wise card: Use for UK spending to avoid international transaction fees.",
]),
("EXCHANGE RATE RISK", C_GREY_HDR, C_GREY_LITE, [
"GBP/INR rate fluctuates. This tracker uses a fixed rate (enter your rate on Dashboard).",
"As of mid-2026: approx Β£1 = βΉ107-110. Budget conservatively at βΉ112-115.",
"Pay GBP fees via Wise or HDFC Forex card for best rates.",
"Avoid airport/hotel money exchange - rates are 5-8% worse.",
]),
]
row = 4
for tip_title, hdr_color, bg_color, tip_list in tips:
ws4.row_dimensions[row].height = 22
merge_header(ws4, row, 2, 3, tip_title, bg=hdr_color, fg=C_WHITE, size=10)
row += 1
for tip in tip_list:
ws4.row_dimensions[row].height = 18
c1 = ws4.cell(row=row, column=2, value="β’")
c1.fill = fill(bg_color)
c1.font = font(bold=True, size=12, color=hdr_color)
c1.alignment = align("center", "top")
c1.border = border()
c2 = ws4.cell(row=row, column=3, value=tip)
c2.fill = fill(bg_color)
c2.font = font(size=10)
c2.alignment = align("left", "top", wrap=True)
c2.border = border()
row += 1
row += 1 # spacer
# ββ SAVE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
out = "/tmp/workspace/plab-schedule/PLAB_Budget_Tracker_2026_2027.xlsx"
wb.save(out)
print(f"Saved: {out}")
cd /tmp/workspace/plab-schedule && python3 build_budget.py 2>&1
~/plab-schedule/build_budget.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/plab-schedule/build_budget.py'
cd /tmp/workspace/plab-schedule && python3 build_budget.py 2>&1
ls -lh /tmp/workspace/plab-schedule/PLAB_Budget_Tracker_2026_2027.xlsx
PLAB Budget Tracker 2026 2027
Spreadsheet Β· XLSX
| Category | Est. Cost (INR) |
|---|---|
| PLAB 1 (exam + prep) | ~βΉ70,000 |
| PLAB 2 (exam + prep) | ~βΉ1,60,000 |
| UK Trip (flights + stay + course) | ~βΉ2,20,000 |
| GMC Registration | ~βΉ55,000 |
| Visa (fee + IHS 3yr + TB + biometric) | ~βΉ3,00,000 |
| Relocation to UK | ~βΉ1,60,000 |
| Base Total | ~βΉ9,65,000 |
| + 15% Contingency | ~βΉ1,45,000 |
| Grand Total |
I took ielts exam and got 6 band overall, now i am preparing for OET how should I prepare and ace the exam ? I am thinking to taking September oet exam
OET exam 2026 format scoring grade B requirements doctors preparation tips
OET September 2026 exam dates registration deadlines
| Sub-test | Duration | Format | Target Score |
|---|---|---|---|
| Listening | 45 min | 3 parts - consultations, monologues, dialogues | 350 (Grade B) |
| Reading | 60 min | 3 parts - medical texts, skim/scan/MCQ | 350 (Grade B) |
| Writing | 45 min | 1 referral/discharge letter from case notes | 350 (Grade B) |
| Speaking | 20 min | 2 clinical roleplays (doctor-patient) | 350 (Grade B) |
| Sub-test | Your Likely Gap | Priority | Time Needed |
|---|---|---|---|
| Writing | Format & register | π΄ Highest | 2 hrs/day in Week 2 |
| Speaking | Empathy style | π High | 1 hr/day from Week 3 |
| Listening | Medical vocab in audio | π‘ Medium | 1 hr/day from Week 4 |
| Reading | Part A timing | π’ Lower | 45 min/day from Week 5 |
| Resource | What For | Cost |
|---|---|---|
| oet.com - Official Sample Tests | Free diagnostic + format familiarity | Free |
| E2Language OET | All 4 sub-tests, writing correction | ~βΉ6,000-8,000/month |
| OETpractice.net | Full mock tests, Writing feedback | ~βΉ3,500/month |
| OET official YouTube channel | Free video explanations | Free |
| "OET Medicine" Facebook groups | Writing feedback, speaking partners | Free |
| Telegram: OET India 2026 groups | Speaking practice partners | Free |
| Swoosh English | Writing correction service | Per letter |
| Aspect | IELTS | OET |
|---|---|---|
| Content | General English | 100% medical/clinical |
| Writing task | Essay or letter | Clinical referral letter only |
| Speaking | General conversation topics | Clinical roleplays |
| Listening | News, education, general | Clinical consultations |
| Reading | Academic/general articles | Medical journal texts |
| Your advantage | General knowledge needed | Your MBBS = massive edge |
My ielts band was W: 6.5 L: 6.5 R: 5.5 S: 6 Overall: 6 How to improve for OET ?
| Sub-test | Your Score | IELTS 7.5 Target | Gap | OET Equivalent |
|---|---|---|---|---|
| Writing | 6.5 | 7.0 | -0.5 | Moderate gap |
| Listening | 6.5 | 7.0 | -0.5 | Moderate gap |
| Reading | 5.5 | 7.0 | -1.5 | Biggest gap |
| Speaking | 6.0 | 7.0 | -1.0 | Clear gap |
PRIORITY 1: Reading (5.5) β Technique overhaul needed
PRIORITY 2: Speaking (6.0) β Clinical empathy + fluency
PRIORITY 3: Writing (6.5) β Format + register only
PRIORITY 4: Listening (6.5) β Format familiarity only
| Day | Activity | Time |
|---|---|---|
| Mon | OET Part A under 15 min strict | 20 min |
| Tue | Read 1 medical journal abstract β answer 5 questions you write yourself | 30 min |
| Wed | OET Part B practice test | 20 min |
| Thu | OET Part A again - beat your Monday time | 20 min |
| Fri | Full OET Reading test timed (60 min) | 65 min |
| Sat | Review wrong answers in detail | 30 min |
| Sun | Light: read 2 medical news articles (BBC Health, BMJ news) | 20 min |
[Hospital/Clinic Name]
[Date]
Dear Dr [Name] / To Whom It May Concern,
Re: [Full patient name], [Age], [DOB]
I am writing to refer/inform/request regarding the above-named
patient who [one-line presenting situation].
[Paragraph 1 - Background & History]
[Relevant history only - no waffle. Past medical history,
medications, allergies if relevant]
[Paragraph 2 - Current Presentation & Findings]
[What brought them in now. Examination findings. Test results.]
[Paragraph 3 - Management to Date]
[What you have done so far. Medications started. Investigations
requested.]
[Paragraph 4 - Request / Reason for Referral]
[Specific, clear request. What you need from the recipient.]
[Any urgency flag if needed.]
Yours sincerely,
Dr [Your Name]
[Designation]
| Week | Main Focus | Daily Time | Key Task |
|---|---|---|---|
| Week 1 | Diagnostic + Format | 2 hrs | Take 1 full OET mock test (untimed), identify exact weak spots |
| Week 2 | Writing only | 2.5 hrs | Write 1 letter/day, get 3 corrected externally |
| Week 3 | Speaking + Reading Part A | 2.5 hrs | 1 roleplay/day recorded + 15-min Part A timed daily |
| Week 4 | Full timed mocks | 3 hrs | Full mock every 2 days, track scores |
| Week 5 | Weak spot targeting | 2 hrs | Final polish on your 1-2 lowest sub-tests, 3 full mocks |
| Exam week | Light revision | 1 hr | Speaking warmup, 1 letter, rest |
| Sub-test | IELTS Equivalent | OET Projection (with prep) | Grade B (350) |
|---|---|---|---|
| Listening | 6.5 | 370-400 | β Likely |
| Reading | 5.5 | 330-370 | β οΈ Risk area |
| Writing | 6.5 | 350-390 | β Achievable |
| Speaking | 6.0 | 350-380 | β Achievable |
I find problem with listening: mcq Reading: mcq Writing: sometime can able to assemble the idea for writing medical history Speaking: i find ok but need polishing
| Day | Activity | Time |
|---|---|---|
| Daily | Practice Question First Method on 3 MCQ questions | 15 min |
| Every 2 days | Full Part C passage with all questions, timed | 25 min |
| Weekly | Review every wrong answer - label it: Distractor? Absolute word? Inference? | 20 min |
Pt: James White, 54M
CC: chest pain x 3 days
PMH: T2DM (metformin 500mg BD), HTN (amlodipine 5mg), ex-smoker 10yrs
Presenting: central crushing CP radiating L arm, diaphoresis,
SOB. No fever. No cough.
Ix: ECG - ST elevation V1-V4. Troponin - elevated (x3 ULN)
Mx: Aspirin 300mg, GTN SL. Referred cardiology.
Pt anxious re: diagnosis.
Rx: needs urgent PCI assessment
| Bucket | What Goes Here |
|---|---|
| A - Who | Patient name, age, DOB |
| B - Why referred | Reason for referral / what you need from recipient |
| C - History | PMH, medications, allergies, social history |
| D - Now | Current presentation, examination findings, test results |
| E - Done | Management already given, investigations pending |
Dear Dr [Name / Colleague],
Re: Mr James White, 54 years, DOB: [if given]
I am writing to urgently refer the above-named patient, who
presented today with a 3-day history of chest pain, for urgent
assessment and consideration of percutaneous coronary intervention.
[Background]
Mr White has a background of type 2 diabetes mellitus, currently
managed with metformin 500mg twice daily, and hypertension, for
which he takes amlodipine 5mg daily. He is an ex-smoker, having
ceased 10 years ago.
[Current Presentation]
He presented with a 3-day history of central crushing chest pain
radiating to the left arm, associated with diaphoresis and
shortness of breath. There was no fever or cough.
Investigations revealed ST elevation in leads V1-V4 on
electrocardiography, with troponin elevated at three times the
upper limit of normal, consistent with an anterior ST-elevation
myocardial infarction.
[Management to Date]
He has been administered aspirin 300mg and sublingual
glyceryl trinitrate. The patient is understandably anxious
regarding his diagnosis.
[Request]
I would be grateful for your urgent assessment and
consideration of primary PCI. Please do not hesitate to
contact me should you require any further information.
Yours sincerely,
Dr [Name]
| Priority | Problem | Time per day | When |
|---|---|---|---|
| π΄ #1 | Listening MCQ - Distractor avoidance | 30 min | Daily from today |
| π΄ #2 | Reading MCQ - Question First Method | 30 min | Daily from today |
| π #3 | Writing - Sort/Filter/Write method | 45 min | Daily from Week 2 |
| π‘ #4 | Speaking - Opening + ICE + Close polish | 20 min | Daily from Week 3 |
Can you create this into document of the last prompt
~/plab-schedule/build_oet_guide.py
from docx import Document
from docx.shared import Pt, Inches, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
# ββ helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def set_cell_bg(cell, hex_color):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:val'), 'clear')
shd.set(qn('w:color'), 'auto')
shd.set(qn('w:fill'), hex_color)
tcPr.append(shd)
def set_cell_borders(cell, color="CCCCCC", style="single", sz="4"):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcBorders = OxmlElement('w:tcBorders')
for side in ['top','left','bottom','right']:
b = OxmlElement(f'w:{side}')
b.set(qn('w:val'), style)
b.set(qn('w:sz'), sz)
b.set(qn('w:space'), '0')
b.set(qn('w:color'), color)
tcBorders.append(b)
tcPr.append(tcBorders)
def cell_text(cell, text, bold=False, size=10, color="000000",
align=WD_ALIGN_PARAGRAPH.LEFT, italic=False, wrap=True):
p = cell.paragraphs[0]
p.clear()
run = p.add_run(text)
run.bold = bold
run.italic = italic
run.font.size = Pt(size)
run.font.color.rgb = RGBColor.from_string(color)
p.alignment = align
p.paragraph_format.wrap = wrap
def colored_row_header(ws, row, col_s, col_e, text,
bg="1B3A6B", fg="FFFFFF", size=12, bold=True):
ws.merge_cells(start_row=row, start_column=col_s,
end_row=row, end_column=col_e)
c = ws.cell(row=row, column=col_s, value=text)
# this is docx β we use a different approach below
def para_spacing(p, before=60, after=60):
pf = p.paragraph_format
pf.space_before = Pt(before / 20)
pf.space_after = Pt(after / 20)
def add_section_heading(doc, text, bg="1B3A6B", fg="FFFFFF", size=13):
p = doc.add_paragraph()
para_spacing(p, 120, 60)
pPr = p._p.get_or_add_pPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:val'), 'clear')
shd.set(qn('w:color'), 'auto')
shd.set(qn('w:fill'), bg)
pPr.append(shd)
run = p.add_run(f' {text} ')
run.bold = True
run.font.size = Pt(size)
run.font.color.rgb = RGBColor.from_string(fg)
return p
def add_sub_heading(doc, text, color="1B3A6B", size=11):
p = doc.add_paragraph()
para_spacing(p, 80, 30)
run = p.add_run(text)
run.bold = True
run.font.size = Pt(size)
run.font.color.rgb = RGBColor.from_string(color)
return p
def add_body(doc, text, size=10.5, color="222222", italic=False):
p = doc.add_paragraph()
para_spacing(p, 30, 30)
run = p.add_run(text)
run.font.size = Pt(size)
run.font.color.rgb = RGBColor.from_string(color)
run.italic = italic
return p
def add_bullet(doc, text, size=10.5, color="222222", bold=False, indent=False):
p = doc.add_paragraph(style='List Bullet')
para_spacing(p, 20, 20)
if indent:
p.paragraph_format.left_indent = Inches(0.4)
run = p.add_run(text)
run.font.size = Pt(size)
run.font.color.rgb = RGBColor.from_string(color)
run.bold = bold
return p
def add_callout(doc, lines, bg="EBF5FB", border="2E86C1", title=None):
tbl = doc.add_table(rows=1, cols=1)
tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
cell = tbl.rows[0].cells[0]
set_cell_bg(cell, bg)
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcBorders = OxmlElement('w:tcBorders')
for side in ['top','left','bottom','right']:
b = OxmlElement(f'w:{side}')
b.set(qn('w:val'), 'single')
b.set(qn('w:sz'), '16')
b.set(qn('w:space'), '0')
b.set(qn('w:color'), border)
tcBorders.append(b)
tcPr.append(tcBorders)
cell.paragraphs[0].clear()
if title:
tp = cell.add_paragraph()
tr = tp.add_run(title)
tr.bold = True
tr.font.size = Pt(10.5)
tr.font.color.rgb = RGBColor.from_string(border)
for line in lines:
lp = cell.add_paragraph(line)
if lp.runs:
lp.runs[0].font.size = Pt(10)
cell.paragraphs[0]._element.getparent().remove(cell.paragraphs[0]._element)
doc.add_paragraph()
def add_table(doc, headers, rows_data, header_bg="1B3A6B",
row_colors=None, col_widths=None):
tbl = doc.add_table(rows=1, cols=len(headers))
tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
# header row
for j, h in enumerate(headers):
c = tbl.rows[0].cells[j]
set_cell_bg(c, header_bg)
set_cell_borders(c, "888888")
cell_text(c, h, bold=True, size=10, color="FFFFFF",
align=WD_ALIGN_PARAGRAPH.CENTER)
alt = ["FFFFFF", "F2F3F4"]
if row_colors is None:
row_colors = [alt[i % 2] for i in range(len(rows_data))]
for i, row_data in enumerate(rows_data):
row = tbl.add_row()
for j, val in enumerate(row_data):
c = row.cells[j]
bg = row_colors[i] if i < len(row_colors) else alt[i % 2]
set_cell_bg(c, bg)
set_cell_borders(c, "CCCCCC")
bold = (j == 0)
cell_text(c, str(val), bold=bold, size=10)
if col_widths:
for i, row in enumerate(tbl.rows):
for j, w in enumerate(col_widths):
row.cells[j].width = w
doc.add_paragraph()
return tbl
def add_example_box(doc, wrong_text, right_text):
"""Two-column wrong/right comparison box"""
tbl = doc.add_table(rows=1, cols=2)
tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
c_wrong = tbl.rows[0].cells[0]
c_right = tbl.rows[0].cells[1]
set_cell_bg(c_wrong, "FDEDEC")
set_cell_bg(c_right, "D5F5E3")
set_cell_borders(c_wrong, "C0392B", sz="8")
set_cell_borders(c_right, "1E8449", sz="8")
c_wrong.paragraphs[0].clear()
lbl_w = c_wrong.add_paragraph()
lbl_w.add_run("β Wrong").bold = True
lbl_w.runs[0].font.color.rgb = RGBColor.from_string("C0392B")
lbl_w.runs[0].font.size = Pt(9)
body_w = c_wrong.add_paragraph(wrong_text)
body_w.runs[0].font.size = Pt(10)
body_w.runs[0].italic = True
c_wrong.paragraphs[0]._element.getparent().remove(c_wrong.paragraphs[0]._element)
c_right.paragraphs[0].clear()
lbl_r = c_right.add_paragraph()
lbl_r.add_run("β
Right").bold = True
lbl_r.runs[0].font.color.rgb = RGBColor.from_string("1E8449")
lbl_r.runs[0].font.size = Pt(9)
body_r = c_right.add_paragraph(right_text)
body_r.runs[0].font.size = Pt(10)
body_r.runs[0].italic = True
c_right.paragraphs[0]._element.getparent().remove(c_right.paragraphs[0]._element)
doc.add_paragraph()
def add_code_block(doc, text, bg="1C2833", fg="F0F3F4"):
tbl = doc.add_table(rows=1, cols=1)
cell = tbl.rows[0].cells[0]
set_cell_bg(cell, bg)
set_cell_borders(cell, "000000", sz="6")
cell.paragraphs[0].clear()
for line in text.strip().split('\n'):
lp = cell.add_paragraph(line)
if lp.runs:
lp.runs[0].font.name = "Courier New"
lp.runs[0].font.size = Pt(9.5)
lp.runs[0].font.color.rgb = RGBColor.from_string(fg)
else:
run = lp.add_run(line)
run.font.name = "Courier New"
run.font.size = Pt(9.5)
run.font.color.rgb = RGBColor.from_string(fg)
cell.paragraphs[0]._element.getparent().remove(cell.paragraphs[0]._element)
doc.add_paragraph()
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# BUILD DOCUMENT
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc = Document()
section = doc.sections[0]
section.top_margin = Cm(2.2)
section.bottom_margin = Cm(2.2)
section.left_margin = Cm(2.5)
section.right_margin = Cm(2.5)
normal = doc.styles['Normal']
normal.font.name = 'Calibri'
normal.font.size = Pt(10.5)
# ββ TITLE PAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_paragraph()
t = doc.add_paragraph()
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = t.add_run('OET EXAM PREPARATION GUIDE')
r.bold = True; r.font.size = Pt(24)
r.font.color.rgb = RGBColor.from_string("1B3A6B")
s = doc.add_paragraph()
s.alignment = WD_ALIGN_PARAGRAPH.CENTER
r2 = s.add_run('Targeted Fix for Your Specific Problem Areas')
r2.bold = True; r2.font.size = Pt(13)
r2.font.color.rgb = RGBColor.from_string("2E86C1")
doc.add_paragraph()
m = doc.add_paragraph()
m.alignment = WD_ALIGN_PARAGRAPH.CENTER
r3 = m.add_run(
'Your Profile: IELTS 6.0 | Target: OET Grade B (350) in all 4 sub-tests\n'
'Problem Areas: Listening MCQ β’ Reading MCQ β’ Writing Assembly β’ Speaking Polish\n'
'Target Exam: September 2026 | Prepared: August 2026')
r3.font.size = Pt(10)
r3.font.color.rgb = RGBColor.from_string("555555")
doc.add_paragraph()
# Summary table
add_table(doc,
headers=["Your Problem", "Root Cause", "Fix", "Priority"],
rows_data=[
["Listening MCQ", "Distractor traps + missing conclusion language", "Wait for final language before answering", "π΄ #1"],
["Reading MCQ", "Reading text before question β too slow", "Question First Method + absolute word spotting", "π΄ #2"],
["Writing Assembly", "Trying to organise + write simultaneously", "Sort β Filter β Write (5 buckets method)", "π #3"],
["Speaking Polish", "Opening, ICE check, and close need structure", "Drill 3-part consultation template", "π‘ #4"],
],
header_bg="1B3A6B",
row_colors=["FDEDEC","FDEDEC","FEF9E7","D5F5E3"],
col_widths=[Cm(4), Cm(6.5), Cm(6.5), Cm(2)]
)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 1 β LISTENING MCQ
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_section_heading(doc, ' PROBLEM 1: LISTENING MCQ (Parts B & C)', bg="2E86C1")
add_body(doc,
"MCQ in OET Listening appears in Part B (6 short workplace recordings) and Part C "
"(2 longer medical talks). These questions are deliberately designed to trap you β "
"every wrong option is mentioned in the recording. You need specific techniques, "
"not just better listening.")
# --- Reason 1
add_sub_heading(doc, "Reason 1 β The Distractor Trap", color="C0392B")
add_body(doc,
"OET deliberately mentions all 3 answer options in the recording. Wrong answers come "
"first; the correct answer appears last or is implied by the conclusion. Most people "
"mark the first option they hear β this is the trap.")
add_callout(doc,
lines=[
"Recording: \"Initially we considered prescribing metformin, and lisinopril was also "
"discussed, but given her renal function, we ultimately decided on amlodipine.\"",
"",
"Question: What medication was prescribed?",
" A) Metformin β mentioned, WRONG",
" B) Lisinopril β mentioned, WRONG",
" C) Amlodipine β CORRECT (conclusion word: 'ultimately decided')",
],
bg="EBF5FB", border="2E86C1", title="Example β Distractor Trap")
add_sub_heading(doc, "β
Fix: Wait for Conclusion Language", color="1E8449")
add_body(doc,
"Never mark an answer the moment you hear it. Hold your pencil. Only commit when "
"the speaker uses final/conclusion language:")
for phrase in [
'"ultimately" / "in the end" / "finally"',
'"we decided" / "the plan was" / "we agreed"',
'"what we actually found" / "however" / "but in fact"',
'"the conclusion is" / "to summarise"',
]:
add_bullet(doc, phrase, color="1B3A6B", bold=True)
doc.add_paragraph()
# --- Reason 2
add_sub_heading(doc, "Reason 2 β Missing the Main Point (Part C)", color="C0392B")
add_body(doc,
"Part C tests the main argument or conclusion of a medical talk, not individual details. "
"You may catch all the facts but still choose the wrong option because you missed the "
"speaker's overall point.")
add_sub_heading(doc, "β
Fix: Listen for Signpost Language", color="1E8449")
add_body(doc, "Train yourself to react immediately when you hear:")
signposts = [
("\"The key issue here is...\"", "This is the main point coming"),
("\"What this means for practice is...\"","Clinical implication β likely the answer"),
("\"The most important takeaway...\"", "Definition of the answer"),
("\"In conclusion / To summarise...\"", "Final answer is about to be stated"),
("\"What concerns me most is...\"", "Speaker's opinion β often tested in MCQ"),
("\"However\" / \"But\" / \"Despite\"", "Contrast β the correct answer often follows"),
]
add_table(doc,
headers=["Signpost Phrase", "What It Signals"],
rows_data=signposts,
header_bg="2E86C1",
col_widths=[Cm(8), Cm(9)])
# --- Reason 3
add_sub_heading(doc, "Reason 3 β Paraphrase Mismatch", color="C0392B")
add_body(doc,
"The question uses different words from the recording. You think it's a different topic "
"but it's the same answer paraphrased. Before each recording, quickly rephrase each "
"MCQ option in your head:")
add_table(doc,
headers=["Option Word", "Possible Paraphrases in Recording"],
rows_data=[
["compliance", "adherence / following the treatment plan / taking medications as prescribed"],
["deterioration", "getting worse / decline / worsening / progression"],
["prognosis", "outlook / likely outcome / what will happen / long-term picture"],
["contraindicated","not suitable / should not be used / avoid in / risk outweighs benefit"],
["aetiology", "cause / underlying reason / what triggered / risk factor"],
["commenced", "started / initiated / begun / put on / prescribed"],
],
header_bg="2E86C1",
col_widths=[Cm(4), Cm(13)])
# --- Daily drill
add_sub_heading(doc, "Daily 15-Minute Listening MCQ Drill", color="1B3A6B")
add_table(doc,
headers=["Step", "Action", "Time"],
rows_data=[
["1", "Read question + all 3 options. Underline KEY WORD in each. Write one paraphrase.", "2 min"],
["2", "Play audio. Do NOT write answer until speaker uses conclusion language.", "During"],
["3", "Mark answer in pencil only after final language heard.", "During"],
["4", "Check answer. If wrong, replay that section and find the exact distractor word.", "3 min"],
["5", "Weekly: label each wrong answer β Distractor? Paraphrase? Missed conclusion?", "5 min"],
],
header_bg="1B3A6B",
col_widths=[Cm(1.5), Cm(12), Cm(2.5)])
add_callout(doc,
lines=[
"Free resources:",
"β’ Official OET sample tests (Part B & C): oet.com/test-takers/test-information",
"β’ E2Language OET Listening Part C walkthroughs β search YouTube: 'OET Listening Part C MCQ strategy'",
"β’ OETpractice.net β full mock tests with answer explanations",
], bg="D5F5E3", border="1E8449")
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 2 β READING MCQ
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_section_heading(doc, ' PROBLEM 2: READING MCQ (Parts B & C)', bg="6C3483")
add_body(doc,
"OET Reading MCQ appears in Part B (6 short workplace texts) and Part C (2 long academic "
"medical articles). Your IELTS 5.5 Reading tells us the core problem is speed and technique, "
"not English ability. Good news: OET texts are 100% medical β your MBBS knowledge is a "
"massive advantage here.")
# --- Problem 2A
add_sub_heading(doc, "Problem 2A β Reading Whole Text Before the Question (Too Slow)", color="C0392B")
add_body(doc,
"Most people read the entire passage first, then go to the question. This wastes "
"50% of your time and causes confusion as you try to remember everything.")
add_sub_heading(doc, "β
Fix: The Question First Method (5 steps)", color="1E8449")
add_table(doc,
headers=["Step", "Action"],
rows_data=[
["1", "Read the QUESTION STEM only (not the options yet)"],
["2", "Identify 2-3 KEY WORDS in the question"],
["3", "SCAN the text for those key words or their synonyms"],
["4", "Read ONLY that sentence + 1 sentence before and after it"],
["5", "NOW look at the options and eliminate wrong ones"],
],
header_bg="6C3483",
col_widths=[Cm(1.5), Cm(15.5)])
add_body(doc,
"This single method cuts your reading time per question by 40-50%. "
"Practise it on every single question until it is automatic.", color="444444", italic=True)
doc.add_paragraph()
# --- Problem 2B
add_sub_heading(doc, "Problem 2B β Absolute Word Traps", color="C0392B")
add_body(doc,
"OET MCQ options often include one that looks correct but uses a stronger claim "
"than the text actually makes. This is the absolute word trap.")
add_example_box(doc,
wrong_text="\"Early mobilisation DEFINITELY reduces recovery time.\"",
right_text="\"Some evidence SUGGESTS early mobilisation MAY reduce recovery time.\"")
add_sub_heading(doc, "β
Fix: Spot Absolute Words β They Are Almost Always Wrong", color="1E8449")
add_body(doc, "When you see these words in an option, treat it as suspicious:")
abs_words = [
("WRONG signal words", "always / never / all / none / definitely / completely / proven / guarantees"),
("CORRECT signal words", "may / might / some / in certain cases / suggests / evidence indicates / appears to"),
]
add_table(doc,
headers=["Type", "Words to Watch"],
rows_data=abs_words,
header_bg="6C3483",
row_colors=["FDEDEC", "D5F5E3"],
col_widths=[Cm(4.5), Cm(12.5)])
# --- Problem 2C
add_sub_heading(doc, "Problem 2C β Inference Questions (Part C, Hardest)", color="C0392B")
add_body(doc,
"These questions ask what the author implies or what can be inferred. "
"The answer is NOT stated directly β you must reason from tone and context.")
add_callout(doc,
lines=[
"Text: \"Despite extensive funding and three decades of research, a reliable biomarker "
"for early Alzheimer's detection remains elusive.\"",
"",
"Question: What does the author suggest about Alzheimer's research?",
"Answer: Research has not achieved its goal despite significant investment "
"β i.e., progress has been disappointing.",
"",
"(This is nowhere in the text word-for-word. You must infer from tone.)",
],
bg="F4ECF7", border="6C3483", title="Example β Inference Question")
add_sub_heading(doc, "β
Fix: The 3-Step Inference Method", color="1E8449")
add_table(doc,
headers=["Step", "Action"],
rows_data=[
["1", "Find the relevant section β scan for key words from the question"],
["2", "Identify the author's ATTITUDE: positive / negative / cautious / critical / neutral"],
["3", "Match that attitude to the correct option β it will have matching tone and be supported by the text without going further than what is implied"],
],
header_bg="6C3483",
col_widths=[Cm(1.5), Cm(15.5)])
add_body(doc,
"The correct inference option will always: (1) match the author's tone, "
"(2) be supported by evidence in the passage, and (3) not overstate what the text implies.",
italic=True, color="444444")
# --- Reading practice plan
doc.add_paragraph()
add_sub_heading(doc, "Daily Reading Practice Schedule", color="1B3A6B")
add_table(doc,
headers=["Day", "Activity", "Time"],
rows_data=[
["Mon", "OET Part A under strict 15-min timer", "20 min"],
["Tue", "Read 1 medical journal abstract β 5 inference questions (write your own)", "30 min"],
["Wed", "OET Part B practice β Question First Method on every question", "20 min"],
["Thu", "OET Part A again β try to beat Monday's speed", "20 min"],
["Fri", "Full OET Reading test timed (60 min)", "65 min"],
["Sat", "Review all wrong answers β label each: Absolute word? Inference? Speed?", "30 min"],
["Sun", "Light: 2 medical news articles (BBC Health / BMJ news)", "20 min"],
],
header_bg="6C3483",
col_widths=[Cm(2), Cm(10), Cm(3)])
add_callout(doc,
lines=[
"Free medical reading materials to build speed:",
"β’ NHS Clinical Knowledge Summaries: cks.nice.org.uk",
"β’ BMJ news section (free): bmj.com/news",
"β’ Patient information leaflets: patient.info",
"β’ Cochrane plain language summaries: cochrane.org/evidence",
], bg="EBF5FB", border="6C3483")
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 3 β WRITING
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_section_heading(doc, ' PROBLEM 3: WRITING β Can\'t Assemble the Medical History', bg="D4AC0D", fg="000000")
add_body(doc,
"This is the most common problem for IMGs. The cause is almost never weak English β "
"it is trying to organise AND write at the same time. The fix is to separate these "
"into two distinct steps before you write a single word.")
add_sub_heading(doc, "Why This Happens β The Case Notes Are Messy", color="C0392B")
add_body(doc, "OET gives you unorganised raw case notes that look like this:")
add_code_block(doc, """Pt: James White, 54M
CC: chest pain x 3 days
PMH: T2DM (metformin 500mg BD), HTN (amlodipine 5mg), ex-smoker 10yrs
Presenting: central crushing CP radiating L arm, diaphoresis, SOB. No fever. No cough.
Ix: ECG - ST elevation V1-V4. Troponin - elevated (x3 ULN)
Mx: Aspirin 300mg, GTN SL. Referred cardiology.
Rx: needs urgent PCI assessment""")
add_body(doc,
"You must turn these messy notes into a professional, flowing referral letter in 40 minutes. "
"The mental block happens when you stare at the notes and try to compose paragraphs "
"directly. The fix is the Sort-Filter-Write framework.")
# --- The Framework
add_sub_heading(doc, "The Sort β Filter β Write Framework (Use Every Time)", color="1E8449")
add_sub_heading(doc, "STEP 1: SORT the Case Notes (3 minutes)", color="1B3A6B", size=11)
add_body(doc,
"During your reading time (5 min), physically sort every piece of information "
"into one of 5 buckets. Write the bucket letter beside each note item.")
add_table(doc,
headers=["Bucket", "Label", "What Goes Here"],
rows_data=[
["A", "WHO", "Patient name, age, DOB"],
["B", "WHY REFERRED", "Purpose of letter β what you need from the recipient"],
["C", "HISTORY", "Past medical history, current medications, allergies, social history"],
["D", "NOW", "Current presentation, examination findings, test results, investigations"],
["E", "DONE", "Management already given, medications started, investigations pending"],
],
header_bg="D4AC0D",
row_colors=["EBF5FB","D5F5E3","EBF5FB","D5F5E3","EBF5FB"],
col_widths=[Cm(1.5), Cm(4), Cm(11.5)])
add_body(doc, "Applied to the James White example above:", italic=True, color="444444")
add_code_block(doc, """A (WHO): James White, 54M
B (WHY): Urgent PCI assessment needed β referral to cardiology
C (HISTORY):T2DM on metformin 500mg BD | HTN on amlodipine 5mg | ex-smoker 10yrs
D (NOW): Central crushing CP 3 days β L arm radiation, diaphoresis, SOB
ECG: ST elevation V1-V4 | Troponin: x3 ULN
E (DONE): Aspirin 300mg given | GTN SL given | Patient anxious re diagnosis""", bg="1A252F")
add_sub_heading(doc, "STEP 2: FILTER (1 minute)", color="1B3A6B", size=11)
add_body(doc,
"Cross out anything NOT relevant to the referral purpose. OET assessors specifically "
"mark you DOWN for including irrelevant information β it shows poor clinical judgment. "
"Ask yourself: 'Does the receiving doctor need this to do their job?'")
add_sub_heading(doc, "STEP 3: WRITE using the Fixed Letter Template (36 minutes)", color="1B3A6B", size=11)
add_code_block(doc, """Dear Dr [Name] / Dear Colleague,
Re: Mr James White, 54 years
I am writing to urgently refer the above-named patient, who presented today
with a 3-day history of chest pain, for urgent assessment and consideration
of percutaneous coronary intervention.
[BACKGROUND β use Bucket C]
Mr White has a background of type 2 diabetes mellitus, currently managed
with metformin 500mg twice daily, and hypertension, for which he takes
amlodipine 5mg daily. He is an ex-smoker, having ceased 10 years ago.
[CURRENT PRESENTATION β use Bucket D]
He presented with a 3-day history of central crushing chest pain radiating
to the left arm, associated with diaphoresis and shortness of breath.
There was no fever or cough.
Investigations revealed ST elevation in leads V1-V4 on electrocardiography,
with troponin elevated at three times the upper limit of normal, consistent
with an anterior STEMI.
[MANAGEMENT TO DATE β use Bucket E]
He has been administered aspirin 300mg and sublingual glyceryl trinitrate.
The patient is understandably anxious regarding his diagnosis.
[REQUEST β use Bucket B]
I would be grateful for your urgent assessment and consideration of
primary PCI. Please do not hesitate to contact me if you require
any further information.
Yours sincerely,
Dr [Your Name]
[Designation]""")
# --- Language patterns
add_sub_heading(doc, "Essential Language Patterns to Memorise", color="1E8449")
add_body(doc, "Use these as your skeleton β they handle the expression so you can focus on content:")
add_table(doc,
headers=["Purpose", "Phrase"],
rows_data=[
["Opening", "I am writing to refer / inform / request regarding [Patient], who presented with..."],
["History", "Mr/Ms X has a background of... | His past medical history is notable for..."],
["Medications", "He has been managed with [drug] for [condition] since [time]."],
["Presentation", "He presented with a [duration] history of [complaint]."],
["Examination", "On examination, [finding] was noted. | Examination revealed..."],
["Investigations","Investigations revealed... | Blood tests demonstrated... | Imaging showed..."],
["Management", "He was commenced on / administered [drug]. | A [test] has been requested."],
["Patient state", "The patient is understandably anxious / agreeable / aware of the diagnosis."],
["Request", "I would be grateful for your assessment and further management."],
["Close", "Please do not hesitate to contact me should you require further information."],
],
header_bg="D4AC0D",
col_widths=[Cm(4), Cm(13)])
# --- The #1 mistake
add_callout(doc,
lines=[
"THE #1 WRITING MISTAKE THAT DROPS YOU BELOW 350:",
"",
"Copying sentences directly from the case notes.",
"",
"The assessor marks you down heavily for direct transcription.",
"You must PARAPHRASE and ORGANISE β not copy.",
"",
"Case note says: 'CP radiating L arm x 3/7, diaphoresis'",
"Your letter must say: 'He presented with a 3-day history of central crushing",
"chest pain radiating to the left arm, associated with diaphoresis.'",
],
bg="FDEDEC", border="C0392B", title="β WARNING")
# --- Writing daily plan
add_sub_heading(doc, "Writing Practice Method", color="1B3A6B")
add_table(doc,
headers=["Week", "Task", "External Feedback"],
rows_data=[
["Week 1-2", "1 letter/day using SortβFilterβWrite. Timing: 5+3+2+35 = 45 min total.", "Get 3 letters corrected externally"],
["Week 3-4", "1 letter/day. Sort/filter in your head (no written buckets).", "1-2 corrections for quality check"],
["Week 5", "Timed letter under exam conditions. Aim for 420+ (comfortable buffer above 350).", "Final check on any remaining patterns"],
],
header_bg="D4AC0D",
col_widths=[Cm(2.5), Cm(10.5), Cm(4)])
add_callout(doc,
lines=[
"Where to get Writing corrected:",
"β’ E2Language writing correction service: e2language.com (approx Β£8-10 per letter)",
"β’ OET Medicine Facebook groups β post anonymised letters, request peer feedback",
"β’ Swoosh English writing correction: swooshenglish.com/oet",
"",
"You cannot self-assess Writing effectively β you don't notice your own patterns.",
"Get at least 5 letters corrected by someone else.",
], bg="D5F5E3", border="1E8449")
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 4 β SPEAKING
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_section_heading(doc, ' PROBLEM 4: SPEAKING β Needs Polishing', bg="1E8449")
add_body(doc,
"Since you feel relatively comfortable with Speaking, you are likely doing the clinical "
"content correctly. The polish is about 3 very specific structural elements: the opening, "
"the ICE check, and the close. Nail these three and your score lifts noticeably.")
# --- Polish 1: Opening
add_sub_heading(doc, "Polish Point 1 β The Opening 30 Seconds (Heavily Marked)", color="C0392B")
add_body(doc,
"Most doctors jump straight into clinical questions. OET assessors are specifically "
"watching the first 30 seconds for: rapport, empathy, patient-centredness. "
"This is your first impression and it is marked.")
add_example_box(doc,
wrong_text='"Hello, I\'m Dr Kumar. So you have chest pain. When did it start?"',
right_text='"Hello Mr White, I\'m Dr Kumar, one of the doctors here today. Please do come in and sit down. '
'I understand you\'ve been having some chest pain β that must be quite worrying for you. '
'I\'d like to hear all about it in your own words. Could you start by telling me what\'s been happening?"')
add_callout(doc,
lines=[
"Drill this opening template until it is automatic:",
"",
"\"Hello [name], I'm Dr [name], one of the [doctors/nurses] here today.",
"Please do [sit down / come in].\"",
"",
"\"I understand you've been [experiencing / having / dealing with] [issue].",
"That must be [difficult / worrying / frustrating] for you.\"",
"",
"\"I'd like to hear about it in your own words. Could you tell me",
"what's been happening?\"",
], bg="D5F5E3", border="1E8449", title="Opening Template")
# --- Polish 2: ICE
add_sub_heading(doc, "Polish Point 2 β The ICE Check (Non-Negotiable)", color="C0392B")
add_body(doc,
"Around the 3-minute mark, you MUST explicitly check the patient's Ideas, "
"Concerns, and Expectations. The roleplay card usually hints at what the patient "
"is worried about β you must address it directly.")
add_table(doc,
headers=["ICE Component", "What to Ask", "Why It Matters"],
rows_data=[
["I β Ideas", '"What do you think might be causing this?"', "Shows you respect the patient's perspective"],
["C β Concerns", '"Is there something specific you were worried about today?"', "Directly addresses the patient's fear (often stated on the card)"],
["E β Expectations", '"What were you hoping we might be able to do for you today?"', "Aligns your plan with patient goals"],
],
header_bg="1E8449",
col_widths=[Cm(3.5), Cm(8.5), Cm(5)])
add_body(doc,
"You do not need all three in every scenario. At minimum, address the Concern. "
"Reassure warmly and specifically: 'I can completely understand why that would worry you. "
"Let me explain what we found...'", italic=True, color="444444")
# --- Polish 3: Close
add_sub_heading(doc, "Polish Point 3 β The Close (Most Candidates Forget This)", color="C0392B")
add_body(doc,
"Most candidates run out of time or simply stop talking when they finish the clinical content. "
"A polished close takes only 20 seconds and ticks 4 assessor criteria simultaneously.")
add_callout(doc,
lines=[
"Polished Close Template:",
"",
'"So just to summarise what we\'ve discussed β [1-sentence plan]."',
"",
'"Do you have any questions about that?"',
"",
'"Please don\'t hesitate to come back if you\'re worried about anything at all,',
' or if things get worse before your appointment."',
"",
'"Is there anything else I can help you with today?"',
"",
"This covers: Summary β | Check understanding β | Safety-net β | Open door β",
], bg="D5F5E3", border="1E8449", title="Close Template")
# --- 5-minute structure
add_sub_heading(doc, "The 5-Minute Consultation Structure", color="1B3A6B")
add_table(doc,
headers=["Time", "Phase", "What to Do"],
rows_data=[
["0:00-1:00", "Open + Rapport", "Warm greeting, empathy statement, invite patient to speak in own words"],
["1:00-3:00", "Clinical Content", "Gather information OR give explanation (depending on station type)"],
["3:00-4:00", "ICE Check", "Address patient's Ideas, Concerns, Expectations explicitly"],
["4:00-5:00", "Plan + Close", "Management plan, safety-net, summary, open door"],
],
header_bg="1E8449",
col_widths=[Cm(2), Cm(4.5), Cm(10.5)])
add_body(doc,
"5 minutes is short. Practice with a timer every single time. "
"The most common mistake is spending 4 minutes on clinical history and having no time "
"for ICE or the close β both of which are marked.", italic=True, color="C0392B")
# --- Key empathy phrases
add_sub_heading(doc, "Empathy Phrases to Use Naturally", color="1B3A6B")
add_body(doc, "These signal patient-centredness to the assessor. Use 2-3 per roleplay:")
phrases = [
'"That must have been really difficult for you..."',
'"I completely understand your concern about that..."',
'"It makes complete sense that you\'d be worried about that."',
'"Before I explain the treatment, could you tell me what you already know about...?"',
'"Is there anything specific you were concerned about today?"',
'"Does that make sense? Do you have any questions so far?"',
'"I want to make sure you feel fully informed about this."',
]
for ph in phrases:
add_bullet(doc, ph, color="1E8449")
# --- Speaking practice method
doc.add_paragraph()
add_sub_heading(doc, "Daily Speaking Practice Method", color="1B3A6B")
add_table(doc,
headers=["Activity", "Frequency", "How"],
rows_data=[
["Record 1 roleplay on your phone", "Every day", "Use OET practice cards. Play back the same evening."],
["Self-review checklist", "Every day", "Did you open warmly? Did you check ICE? Did you close properly?"],
["Roleplay with a study partner", "Daily if possible", "Find partners in Telegram/WhatsApp OET India groups"],
["Watch UK GP consultation videos", "3x per week", "YouTube: 'GP consultation UK' β notice tone, pace, language"],
["Practise explaining conditions simply","Daily (5 min)","Pick 1 condition. Explain in patient-friendly terms out loud."],
],
header_bg="1E8449",
col_widths=[Cm(5.5), Cm(4), Cm(7.5)])
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 5 β MASTER DAILY PLAN
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_section_heading(doc, ' YOUR MASTER DAILY STUDY PLAN (Aug 1 β Sep 19, 2026)', bg="1B3A6B")
add_table(doc,
headers=["Week", "Main Focus", "Daily Time", "Key Deliverable"],
rows_data=[
["Week 1\nAug 1-7", "Diagnostic + Format Familiarisation", "2 hrs", "Take 1 full OET mock (untimed). Identify exact weak spots per sub-test."],
["Week 2\nAug 8-14", "Listening MCQ + Reading MCQ Technique", "2.5 hrs", "Master Question First Method. Distractor avoidance drill daily."],
["Week 3\nAug 15-21", "Writing SortβFilterβWrite framework", "2.5 hrs", "Write 1 letter/day. Get 2-3 corrected externally."],
["Week 4\nAug 22-28", "Speaking Opening + ICE + Close", "2.5 hrs", "Record 1 roleplay/day. Speaking partner daily."],
["Week 5\nAug 29 - Sep 4", "Full Timed Mocks", "3 hrs", "Full mock every 2 days. Track scores. All sub-tests >320?"],
["Week 6\nSep 5-11", "Weak Spot Targeting", "2 hrs", "Focus only on your 1-2 lowest sub-tests. 3 more full mocks."],
["Exam Week\nSep 12-19", "Light Revision + Rest", "1 hr", "1 letter/day. 1 Speaking roleplay. Rest 2 days before exam."],
],
header_bg="1B3A6B",
row_colors=["EBF5FB","EBF5FB","D5F5E3","D5F5E3","FEF9E7","FEF9E7","FDEDEC"],
col_widths=[Cm(2.8), Cm(5), Cm(2.5), Cm(7)])
add_sub_heading(doc, "Daily Time Allocation (2.5 hrs)", color="1B3A6B")
add_table(doc,
headers=["Sub-test", "Daily Time", "Activity"],
rows_data=[
["Listening MCQ", "30 min", "Distractor drill β 5 MCQ questions with pause-and-label method"],
["Reading MCQ", "30 min", "Question First Method β Part A (15 min strict) + Part B/C MCQ"],
["Writing", "45 min", "1 complete letter (SortβFilterβWrite, fully timed)"],
["Speaking", "20 min", "1 roleplay recorded + self-review OR partner practice"],
["Review", "15 min", "Check previous day's answers. Label error types. Update error log."],
],
header_bg="1B3A6B",
col_widths=[Cm(3), Cm(3), Cm(11)])
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 6 β QUICK REFERENCE CARD
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_page_break()
add_section_heading(doc, ' QUICK REFERENCE CARD (Print and Keep on Your Desk)', bg="2C3E50")
add_sub_heading(doc, "Listening MCQ β Do This Every Time", color="2E86C1")
for tip in [
"NEVER mark an answer when first heard β wait for conclusion language",
"Conclusion words: ultimately / finally / in the end / we decided / however the result was",
"Absolute words in options (always/never/definitely) = almost always wrong",
"Before recording: paraphrase each option in the margin",
]:
add_bullet(doc, tip)
doc.add_paragraph()
add_sub_heading(doc, "Reading MCQ β Do This Every Time", color="6C3483")
for tip in [
"Question FIRST, text second β never read the full passage first",
"Scan for key words β read only that sentence + 1 before + 1 after",
"Absolute words in options = suspicious. Hedging words = likely correct",
"Inference: find author's attitude first, then match to option",
]:
add_bullet(doc, tip)
doc.add_paragraph()
add_sub_heading(doc, "Writing β 5 Buckets", color="D4AC0D")
for tip in [
"A = WHO (patient name, age) | B = WHY (referral purpose)",
"C = HISTORY (PMH, meds, allergies) | D = NOW (presentation, results)",
"E = DONE (management given, investigations pending)",
"FILTER before writing β exclude irrelevant details",
"PARAPHRASE case notes β never copy directly",
]:
add_bullet(doc, tip)
doc.add_paragraph()
add_sub_heading(doc, "Speaking β 3 Non-Negotiables", color="1E8449")
for tip in [
"Open warmly: greeting + empathy + invite patient to speak in own words",
"ICE check at ~3 min: Ideas / Concerns / Expectations",
"Close every roleplay: Summary + Questions? + Safety-net + Open door",
]:
add_bullet(doc, tip)
doc.add_paragraph()
add_sub_heading(doc, "Top Empathy Phrases (use 2-3 per roleplay)", color="1E8449")
for ph in [
'"That must have been really difficult for you."',
'"I completely understand your concern about that."',
'"Is there something specific you were worried about today?"',
'"Does that make sense? Do you have any questions so far?"',
'"Please don\'t hesitate to come back if you\'re worried about anything."',
]:
add_bullet(doc, ph, color="1E8449")
doc.add_paragraph()
add_callout(doc,
lines=[
"Register for September OET exam NOW: oet.com",
"Deadline for Sep 18/19 sitting: approximately August 21-25, 2026",
"Fee: approximately USD 455-470 (covers all 4 sub-tests)",
"Results: 10-16 working days after exam",
"Valid for 2 years β sufficient for PLAB/GMC application",
], bg="FDEDEC", border="C0392B", title="β ACTION: Register This Week")
# ββ footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.add_paragraph()
fp = doc.add_paragraph()
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
fr = fp.add_run(
"Prepared by Orris AI | Based on official OET 2026 guidelines | "
"Always verify current dates and fees at oet.com")
fr.font.size = Pt(9)
fr.font.color.rgb = RGBColor.from_string("888888")
fr.italic = True
# ββ SAVE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
out = "/tmp/workspace/plab-schedule/OET_Preparation_Guide_2026.docx"
doc.save(out)
print(f"Saved: {out}")
cd /tmp/workspace/plab-schedule && python3 build_oet_guide.py 2>&1
ls -lh /tmp/workspace/plab-schedule/OET_Preparation_Guide_2026.docx
OET Preparation Guide 2026
Word Document Β· DOCX
| Section | Content |
|---|---|
| Cover page | Your profile summary + priority table at a glance |
| Section 1 - Listening MCQ | 3 trap types (Distractor / Main Point / Paraphrase), signpost language table, paraphrase dictionary, 15-min daily drill |
| Section 2 - Reading MCQ | Question First Method (5 steps), absolute word trap table, 3-step inference method, daily schedule, free resources |
| Section 3 - Writing | Why the block happens, SortβFilterβWrite framework, 5 buckets explained with worked example, full letter template, essential language patterns, the #1 mistake warning, where to get feedback |
| Section 4 - Speaking | 3 polish points (Opening/ICE/Close), wrong vs right examples side by side, 5-minute consultation structure, empathy phrases list, daily practice method |
| Section 5 - Master Plan | 7-week daily schedule (Aug 1 - Sep 19), daily time allocation breakdown |
| Section 6 - Quick Reference Card | Print-and-keep cheatsheet for all 4 sub-tests on one page |
Can you able to tell me i initials of my future wife
Create a study plan for OET
ls /tmp/workspace/plab-schedule/
~/plab-schedule/build_oet_plan.py
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.styles.differential import DifferentialStyle
from openpyxl.formatting.rule import Rule, FormulaRule
import datetime
wb = Workbook()
# ββ COLOURS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
NAVY = "1B3A6B"
BLUE = "2E86C1"
LT_BLUE = "D6EAF8"
GREEN = "1E8449"
LT_GREEN = "D5F5E3"
AMBER = "D4AC0D"
LT_AMBER = "FEF9E7"
RED = "C0392B"
LT_RED = "FDEDEC"
PURPLE = "6C3483"
LT_PURPLE = "F4ECF7"
TEAL = "117A65"
LT_TEAL = "D1F2EB"
GREY_D = "2C3E50"
GREY_L = "F2F3F4"
WHITE = "FFFFFF"
INPUT_BG = "FFFDE7"
# Sub-test colours
ST_COLORS = {
"Listening": ("1A5276", "D6EAF8"),
"Reading": ("4A235A", "F4ECF7"),
"Writing": ("7D6608", "FEF9E7"),
"Speaking": ("1E8449", "D5F5E3"),
"Mock": ("922B21", "FDEDEC"),
"Rest": ("717D7E", "F2F3F4"),
"Admin": ("2C3E50", "EAECEE"),
}
def fill(c): return PatternFill("solid", fgColor=c)
def font(bold=False, size=10, color="000000", italic=False, name="Calibri"):
return Font(bold=bold, size=size, color=color, italic=italic, name=name)
def border(style="thin", color="CCCCCC"):
s = Side(style=style, color=color)
return Border(left=s, right=s, top=s, bottom=s)
def thick_border(color="888888"):
t = Side(style="medium", color=color)
return Border(left=t, right=t, top=t, bottom=t)
def align(h="left", v="center", wrap=False):
return Alignment(horizontal=h, vertical=v, wrap_text=wrap)
def w(ws, col, width): ws.column_dimensions[get_column_letter(col)].width = width
def h(ws, row, height): ws.row_dimensions[row].height = height
def hdr(ws, row, col, text, bg=NAVY, fg=WHITE, sz=10, bold=True,
halign="center", wrap=False):
c = ws.cell(row=row, column=col, value=text)
c.fill = fill(bg); c.font = font(bold=bold, size=sz, color=fg)
c.alignment = align(halign, "center", wrap)
c.border = border("thin", "999999")
return c
def cell(ws, row, col, value=None, bg=WHITE, fg="000000", sz=10,
bold=False, halign="left", wrap=True, num_fmt=None, border_col="CCCCCC"):
c = ws.cell(row=row, column=col, value=value)
c.fill = fill(bg); c.font = font(bold=bold, size=sz, color=fg)
c.alignment = align(halign, "center", wrap)
c.border = border("thin", border_col)
if num_fmt: c.number_format = num_fmt
return c
def merge_hdr(ws, r, cs, ce, text, bg=NAVY, fg=WHITE, sz=11, bold=True):
ws.merge_cells(start_row=r, start_column=cs, end_row=r, end_column=ce)
c = ws.cell(row=r, column=cs, value=text)
c.fill = fill(bg); c.font = font(bold=bold, size=sz, color=fg)
c.alignment = align("center", "center")
c.border = border("medium", "888888")
return c
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 1 β OVERVIEW DASHBOARD
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws1 = wb.active
ws1.title = "π Overview"
ws1.sheet_view.showGridLines = False
col_ws = [2, 22, 12, 12, 12, 12, 20, 3]
for i, cw in enumerate(col_ws, 1): w(ws1, i, cw)
h(ws1, 1, 6)
h(ws1, 2, 40); merge_hdr(ws1, 2, 2, 7,
"π¬π§ OET EXAM STUDY PLAN | August β September 2026",
bg=NAVY, sz=16)
h(ws1, 3, 20); merge_hdr(ws1, 3, 2, 7,
"Personalised for: MBBS (China) | IELTS 6.0 | Target: Grade B (350) in all 4 sub-tests | Exam: Sep 18/19, 2026",
bg=BLUE, sz=10, bold=False)
h(ws1, 4, 6)
# Profile snapshot
h(ws1, 5, 16)
merge_hdr(ws1, 5, 2, 4, "YOUR IELTS PROFILE", bg=GREY_D)
merge_hdr(ws1, 5, 5, 7, "OET TARGET & RISK", bg=GREY_D)
profile = [
("Writing", "6.5", "Moderate gap", "350", "Low risk β format only"),
("Listening", "6.5", "Moderate gap", "350", "Low risk β medical content helps"),
("Reading", "5.5", "BIGGEST GAP", "350", "HIGH RISK β technique needed"),
("Speaking", "6.0", "Clear gap", "350", "Medium β polish needed"),
]
prof_bg = [LT_AMBER, LT_BLUE, LT_RED, LT_GREEN]
for i, (sub, score, gap, target, risk) in enumerate(profile):
r = 6 + i
h(ws1, r, 18)
sc, lc = ST_COLORS[sub]
cell(ws1, r, 2, sub, bg=lc, bold=True, halign="center", border_col="AAAAAA")
cell(ws1, r, 3, score, bg=prof_bg[i], bold=True, halign="center", fg=NAVY)
cell(ws1, r, 4, gap, bg=prof_bg[i], bold=(i==2), halign="center",
fg=RED if i==2 else "333333")
cell(ws1, r, 5, target, bg=lc, bold=True, halign="center", fg=sc)
cell(ws1, r, 6, risk, bg=prof_bg[i], halign="left",
fg=RED if "HIGH" in risk else GREEN if "Low" in risk else AMBER)
ws1.merge_cells(start_row=r, start_column=6, end_row=r, end_column=7)
h(ws1, 10, 6)
# Study Phase Overview
h(ws1, 11, 16); merge_hdr(ws1, 11, 2, 7, "STUDY PHASES AT A GLANCE", bg=GREY_D)
phase_hdrs = ["Phase", "Period", "Weeks", "Primary Focus", "Daily Hours", "Key Milestone"]
for j, ph in enumerate(phase_hdrs, 2): hdr(ws1, 12, j, ph, bg=GREY_D)
h(ws1, 12, 20)
phases = [
("Phase 1", "Aug 1β7", "Week 1", "Diagnostic + Format Familiarisation", "2 hrs", "Full mock test done, weak spots identified"),
("Phase 2", "Aug 8β14", "Week 2", "Listening MCQ + Reading MCQ Technique", "2.5 hrs", "Distractor drill + Question First Method"),
("Phase 3", "Aug 15β21", "Week 3", "Writing: SortβFilterβWrite Framework", "2.5 hrs", "Write 1 letter/day, 3 corrected externally"),
("Phase 4", "Aug 22β28", "Week 4", "Speaking: Opening + ICE + Close Polish", "2.5 hrs", "1 recorded roleplay/day with self-review"),
("Phase 5", "Aug 29βSep 4","Week 5","Full Timed Mock Exams", "3 hrs", "All sub-tests consistently >320"),
("Phase 6", "Sep 5β11", "Week 6", "Weak Spot Targeting + Mock Refinement", "2 hrs", "3 full mocks done, all sub-tests >340"),
("Exam Wk", "Sep 12β19", "Week 7", "Light Revision + Rest", "1 hr", "Exam Sep 18/19 β Grade B target"),
]
ph_bgs = [LT_BLUE, LT_PURPLE, LT_AMBER, LT_GREEN, LT_RED, LT_TEAL, "EAECEE"]
for i, (ph, period, wk, focus, hrs, mile) in enumerate(phases):
r = 13 + i
h(ws1, r, 18)
bg = ph_bgs[i]
cell(ws1, r, 2, ph, bg=bg, bold=True, halign="center")
cell(ws1, r, 3, period, bg=bg, halign="center")
cell(ws1, r, 4, wk, bg=bg, halign="center")
cell(ws1, r, 5, focus, bg=bg, bold=False)
cell(ws1, r, 6, hrs, bg=bg, bold=True, halign="center")
cell(ws1, r, 7, mile, bg=bg, halign="left", wrap=True)
h(ws1, 20, 6)
# Daily time split
h(ws1, 21, 16); merge_hdr(ws1, 21, 2, 7, "DAILY TIME ALLOCATION (2.5 hrs average)", bg=GREY_D)
for j, ph in enumerate(["Sub-test", "Daily Time", "Core Activity", "Priority"], 2):
hdr(ws1, 22, j, ph, bg=GREY_D)
h(ws1, 22, 20)
daily = [
("Listening MCQ", "30 min", "Distractor drill β 5 MCQ questions, pause-before-answer method", "π΄ #1"),
("Reading MCQ", "30 min", "Question First Method β Part A 15-min timed + Part B/C MCQ", "π΄ #2"),
("Writing", "45 min", "1 complete letter (SortβFilterβWrite, fully timed)", "π #3"),
("Speaking", "20 min", "1 roleplay recorded + self-review OR partner practice", "π‘ #4"),
("Review", "15 min", "Check yesterday's answers. Label error types.", "Daily"),
]
for i, (sub, t, act, pri) in enumerate(daily):
r = 23 + i
h(ws1, r, 18)
sc, lc = ST_COLORS.get(sub.split()[0], (GREY_D, GREY_L))
bg = lc
cell(ws1, r, 2, sub, bg=bg, bold=True, halign="center", fg=sc)
cell(ws1, r, 3, t, bg=bg, bold=True, halign="center")
cell(ws1, r, 4, act, bg=bg)
cell(ws1, r, 5, pri, bg=bg, bold=True, halign="center",
fg=RED if "π΄" in pri else AMBER if "π " in pri else GREEN)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 2 β WEEK-BY-WEEK PLAN (detailed)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws2 = wb.create_sheet("π
Weekly Plan")
ws2.sheet_view.showGridLines = False
w2_widths = [2, 10, 10, 18, 18, 18, 18, 16, 3]
for i, cw in enumerate(w2_widths, 1): w(ws2, i, cw)
h(ws2, 1, 6)
h(ws2, 2, 36)
merge_hdr(ws2, 2, 2, 8,
"π
WEEK-BY-WEEK OET STUDY PLAN | 7 Weeks: Aug 1 β Sep 19, 2026",
bg=NAVY, sz=14)
h(ws2, 3, 18)
merge_hdr(ws2, 3, 2, 8,
"Each row = one day. Green = done. Update the STATUS column as you go.",
bg=BLUE, sz=10, bold=False)
h(ws2, 4, 6)
# Column headers
h(ws2, 5, 22)
day_hdrs = ["Date", "Day", "Phase / Focus", "Listening Task", "Reading Task", "Writing Task", "Speaking Task", "Status"]
day_hdr_bgs = [GREY_D, GREY_D, NAVY, "1A5276", "4A235A", "7D6608", GREEN, TEAL]
for j, (dh, dbg) in enumerate(zip(day_hdrs, day_hdr_bgs), 2):
hdr(ws2, 5, j, dh, bg=dbg, sz=10, wrap=True)
# ββ Day data βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Format: (date_str, day, phase, listening, reading, writing, speaking)
days = [
# WEEK 1 β DIAGNOSTIC
("01-Aug","Sat","W1: Diagnostic",
"OET sample test Part B β DO NOT prepare first. Just attempt cold.",
"OET sample test Part A (15 min timed). Note: did you finish?",
"OET sample Writing task β cold attempt. Time yourself.",
"Watch 1 OET Speaking roleplay video on YouTube. Note structure."),
("02-Aug","Sun","W1: Diagnostic",
"OET sample test Part C β attempt all MCQs. Mark and review.",
"OET sample test Parts B+C β Question types only, review answers.",
"Read OET Writing Criteria sheet (download free from oet.com).",
"Record yourself doing 1 OET roleplay card (free cards at oet.com)."),
("03-Aug","Mon","W1: Format Study",
"Watch E2Language: 'OET Listening Part B explained' (YouTube, free).",
"Read about Question First Method. Apply to 3 Part B questions.",
"Read the 5 Writing Criteria carefully. Annotate sample letters.",
"Watch: 'OET Speaking β How to open a consultation' (YouTube)."),
("04-Aug","Tue","W1: Format Study",
"Distractor trap drill: 5 Part B MCQs β pause before answering.",
"Part A practice: 15 min strict. Skim 4 texts. Match questions.",
"Sort exercise: take 1 case note set β sort into 5 buckets only.",
"Record Opening template: greeting + empathy + invite. Play back."),
("05-Aug","Wed","W1: Format Study",
"Signpost language drill: list 10 conclusion phrases. Say them aloud.",
"Inference question drill: 3 Part C questions. 3-step method.",
"Write 1 full letter (SortβFilterβWrite). Time: 45 min total.",
"ICE phrases drill: write + say Ideas/Concerns/Expectations prompts."),
("06-Aug","Thu","W1: Format Study",
"Part C: 1 long talk + all MCQs. Focus on main point only.",
"Absolute word hunt: find 5 wrong MCQ options with absolute words.",
"Review your Wednesday letter. Self-assess against 5 criteria.",
"Closing template drill: Summary + Questions? + Safety-net + Open door."),
("07-Aug","Fri","W1: Full Mock #1",
"FULL TIMED MOCK β Listening (45 min). Do not pause.",
"FULL TIMED MOCK β Reading (60 min). Do not pause.",
"FULL TIMED MOCK β Writing (45 min). Do not pause.",
"FULL TIMED MOCK β Speaking (2 roleplays, 5 min each)."),
# WEEK 2 β LISTENING MCQ + READING MCQ TECHNIQUE
("08-Aug","Sat","W2: Mock Review",
"Review Mock #1 Listening. Label each wrong answer: Distractor/Paraphrase/Conclusion missed.",
"Review Mock #1 Reading. Label: Speed/Absolute word/Inference.",
"Review Mock #1 Writing. Score yourself on each of 5 criteria.",
"Review Mock #1 Speaking. Was ICE addressed? Was close complete?"),
("09-Aug","Sun","W2: Rest + Light",
"Rest day. Optional: 15 min medical podcast (BMJ Podcast/NEJM audio).",
"Light: Read 1 BMJ news article (bmj.com/news). Note vocabulary.",
"Rest day for Writing.",
"Watch 1 UK GP consultation video on YouTube. Observe tone."),
("10-Aug","Mon","W2: Listening MCQ",
"Distractor drill: 8 Part B MCQs. Hold answer until conclusion language.",
"Question First: 5 Part B questions. Underline key word, paraphrase.",
"Paraphrase exercise: convert 5 case note lines into letter language.",
"Opening practice: record 3 different opening lines. Best one repeated 5x."),
("11-Aug","Tue","W2: Listening MCQ",
"Part C: 1 full long talk MCQs. Map each question to signpost phrase.",
"Part A timed (15 min). Did you finish? Note how many left unfinished.",
"Write letter #2 (SortβFilterβWrite). Time yourself strictly.",
"ICE roleplay: 5-min mock where you ONLY practice the ICE segment."),
("12-Aug","Wed","W2: Listening MCQ",
"Paraphrase matching drill: match 10 recording phrases to written options.",
"Inference drill: 4 Part C questions β label author's attitude first.",
"Bucket sorting speed drill: sort a case note into 5 buckets in 3 min.",
"Record roleplay #3. Focus: did you hit ICE at the 3-minute mark?"),
("13-Aug","Thu","W2: Reading MCQ",
"Part B: 6 questions using Question First Method only. Time each one.",
"Part C: 2 long texts + all questions. Strict 45 min. Review missed.",
"Write letter #3. Post to OET Facebook group for feedback.",
"Partner practice session: 2 roleplays. Debrief each station."),
("14-Aug","Fri","W2: Reading MCQ",
"Mixed drill: 5 Part B + 5 Part C MCQs. Track time per question.",
"Full Reading timed (60 min). Target: finish with 5 min to spare.",
"Review letter #2 corrections (if received). Note pattern in errors.",
"Close template drill: record 5 different closing statements."),
# WEEK 3 β WRITING DEEP DIVE
("15-Aug","Sat","W3: Writing Focus",
"Listening: 30 min Part B distractor drill. Maintain gains.",
"Reading: Part A 15-min timed. Beat last week's score.",
"Letter #4: Referral letter (GP β hospital). SortβFilterβWrite.",
"Watch: 'OET Writing β How to structure a letter' (E2Language YouTube)."),
("16-Aug","Sun","W3: Rest + Light",
"Rest. Optional: 10-min medical English podcast.",
"Light reading: 1 clinical guideline summary (cks.nice.org.uk).",
"Rest day for Writing.",
"Light: Practice explaining 1 condition in lay terms (speak aloud, 2 min)."),
("17-Aug","Mon","W3: Writing Focus",
"Listening: Part C 1 full talk. Map signposts. Review errors.",
"Reading: 5 Part C inference questions. 3-step method each.",
"Letter #5: Discharge summary letter. Focus on Bucket D (Now).",
"Empathy phrase drill: 7 phrases β say naturally without reading."),
("18-Aug","Tue","W3: Writing Focus",
"Listening: 8 Part B MCQs distractor drill. No new method β consolidate.",
"Reading: Part B 6 questions β absolute word hunt in wrong options.",
"Letter #6: Transfer letter (hospital ward β rehab). Filter carefully.",
"Record roleplay #6. Focus: natural empathy, no scripted feel."),
("19-Aug","Wed","W3: Writing Focus",
"Listening: Part A + Part B combo (30 min). Review medical spellings.",
"Reading: Part A 15-min timed. All 4 texts, skim first, then match.",
"Letter #7: GP referral to psychiatry. Tricky info β filter exercise.",
"Partner practice: 1 roleplay. Partner plays difficult/anxious patient."),
("20-Aug","Thu","W3: Writing Focus",
"Medical spelling drill: 20 UK spellings (haemorrhage, dyspnoea, etc.)",
"Mixed: 5 Part B + 5 Part C MCQs timed.",
"Letter #8: Paediatric referral. Practice adapting language for context.",
"Speaking: 5-min structure drill β allocate exact time per phase."),
("21-Aug","Fri","W3: Full Mock #2",
"FULL TIMED MOCK β Listening (45 min).",
"FULL TIMED MOCK β Reading (60 min).",
"FULL TIMED MOCK β Writing (45 min).",
"FULL TIMED MOCK β Speaking (2 roleplays)."),
# WEEK 4 β SPEAKING POLISH + INTEGRATION
("22-Aug","Sat","W4: Mock Review",
"Review Mock #2 Listening. Compare error types with Mock #1. Improving?",
"Review Mock #2 Reading. Compare to Mock #1. Is speed improving?",
"Review Mock #2 Writing. Any of 5 criteria still consistently failing?",
"Review Mock #2 Speaking. Record + self-review against criteria checklist."),
("23-Aug","Sun","W4: Rest + Light",
"Rest. Optional: BMJ podcast or Geeky Medics YouTube.",
"Light: 1 medical news article. Note lay language used.",
"Rest. Optional: Read 1 sample OET letter model answer.",
"Watch 1 UK GP consultation video. Note opening and closing style."),
("24-Aug","Mon","W4: Speaking Polish",
"Listening: Part B 8 MCQs β distractor only, no other focus.",
"Reading: Part A 15-min timed. Target: finish in 13 min.",
"Letter #9: Orthopaedic referral. Maintain speed β should feel automatic.",
"Opening drill: 10 different openings for 10 different patients. Record."),
("25-Aug","Tue","W4: Speaking Polish",
"Listening: Part C 1 full + MCQs. Signpost phrases review.",
"Reading: Part C 1 full text + inference MCQs.",
"Letter #10: Cardiology referral (chest pain). Urgent tone practice.",
"ICE deep dive: roleplay where patient has wrong ideas about diagnosis."),
("26-Aug","Wed","W4: Speaking Polish",
"Listening: Medical spelling β 20 terms. Test yourself without looking.",
"Reading: Mixed Part B + C. Track: how many minutes per question now?",
"Letter #11: Oncology referral. Sensitive content β register practice.",
"Breaking bad news roleplay: use SPIKES protocol (Stop/Perception/Invite/Knowledge/Empathy/Summary)."),
("27-Aug","Thu","W4: Speaking Polish",
"Listening: Full Part B + C under 35 min. Review all MCQ errors.",
"Reading: Full timed (60 min). Are you finishing on time now?",
"Letter #12: GP follow-up letter to patient re: test results.",
"Partner session: 2 roleplays β 1 easy, 1 difficult. Debrief."),
("28-Aug","Fri","W4: Full Mock #3",
"FULL TIMED MOCK β Listening (45 min).",
"FULL TIMED MOCK β Reading (60 min).",
"FULL TIMED MOCK β Writing (45 min).",
"FULL TIMED MOCK β Speaking (2 roleplays)."),
# WEEK 5 β FULL MOCKS + CONSOLIDATION
("29-Aug","Sat","W5: Mock Review",
"Review Mock #3 Listening. All 3 error types. Trend check.",
"Review Mock #3 Reading. Are you finishing on time? Inference improving?",
"Review Mock #3 Writing. Get feedback if you haven't already.",
"Review Mock #3 Speaking. Is your close now consistent?"),
("30-Aug","Sun","W5: Rest",
"FULL REST. No study. Walk, eat well, sleep early.",
"FULL REST.",
"FULL REST.",
"FULL REST."),
("31-Aug","Mon","W5: Targeted Fix",
"Target WEAKEST area from Mock #3 Listening only.",
"Target WEAKEST area from Mock #3 Reading only.",
"Letter #13: Revisit your lowest-scoring letter type from previous weeks.",
"Target WEAKEST speaking element from Mock #3."),
("01-Sep","Tue","W5: Full Mock #4",
"FULL TIMED MOCK β Listening (45 min).",
"FULL TIMED MOCK β Reading (60 min).",
"FULL TIMED MOCK β Writing (45 min).",
"FULL TIMED MOCK β Speaking (2 roleplays)."),
("02-Sep","Wed","W5: Mock Review",
"Review Mock #4 Listening. Are all MCQs now >70% correct?",
"Review Mock #4 Reading. Is Part A finished in <14 min?",
"Review Mock #4 Writing. Score each criteria 1-5. Total >18/25?",
"Review Mock #4 Speaking. Checklist: Openβ ICEβ Closeβ?"),
("03-Sep","Thu","W5: Targeted Fix",
"Any lingering Listening error type β 20 targeted questions.",
"Any lingering Reading error type β 15 targeted questions.",
"Letter #14: Your weakest letter type. Focus on that section only.",
"Record roleplay. Self-review. Share with partner for feedback."),
("04-Sep","Fri","W5: Full Mock #5",
"FULL TIMED MOCK β Listening (45 min).",
"FULL TIMED MOCK β Reading (60 min).",
"FULL TIMED MOCK β Writing (45 min).",
"FULL TIMED MOCK β Speaking (2 roleplays)."),
# WEEK 6 β FINAL REFINEMENT
("05-Sep","Sat","W6: Mock Review",
"Review Mock #5 Listening. Track improvement vs Mock #1.",
"Review Mock #5 Reading. Track improvement vs Mock #1.",
"Review Mock #5 Writing. Final corrections if needed.",
"Review Mock #5 Speaking. Any remaining weak spots?"),
("06-Sep","Sun","W6: Rest",
"Rest. Light medical English only if you want.",
"Rest. Optional: 1 clinical article from patient.info.",
"Rest.",
"Watch 1 OET Speaking model answer video."),
("07-Sep","Mon","W6: Consolidate",
"Listening: 10 Part B + 5 Part C MCQs. Pure consolidation.",
"Reading: Full timed (60 min). Target: all correct on Part A.",
"Letter #15: Write your best letter. Time yourself. It should feel easy.",
"Open + ICE + Close drill: 5 min timed. No roleplay β just the 3 elements."),
("08-Sep","Tue","W6: Consolidate",
"Listening: Medical spelling test β all 30 terms from your list.",
"Reading: 10 Part C MCQs only. Inference focus.",
"Letter #16: Any type. Aim for under 38 minutes. Quality over speed.",
"Partner roleplay: 2 sessions. Both with debrief."),
("09-Sep","Wed","W6: Full Mock #6",
"FULL TIMED MOCK β Listening (45 min).",
"FULL TIMED MOCK β Reading (60 min).",
"FULL TIMED MOCK β Writing (45 min).",
"FULL TIMED MOCK β Speaking (2 roleplays)."),
("10-Sep","Thu","W6: Final Review",
"Review Mock #6 Listening. This is your predicted score.",
"Review Mock #6 Reading. This is your predicted score.",
"Review Mock #6 Writing. Final self-assessment.",
"Review Mock #6 Speaking. Final self-assessment."),
("11-Sep","Fri","W6: Light Practice",
"30 min: 10 MCQs only. No pressure. Maintain confidence.",
"30 min: Part A only. Keep timing sharp.",
"Write 1 letter β any type β for confidence.",
"1 roleplay with partner. Keep it relaxed."),
# EXAM WEEK
("12-Sep","Sat","Exam Week",
"20 min: 5 MCQs. Very light.",
"20 min: Part A only.",
"Write 1 letter. No timer pressure.",
"1 roleplay. Focus on opening and close only."),
("13-Sep","Sun","Exam Week",
"15 min: Review your distractor trap notes only.",
"15 min: Review Question First Method notes only.",
"Read your best letter from the past 6 weeks.",
"Record yourself doing 1 perfect opening + 1 perfect close."),
("14-Sep","Mon","Exam Week",
"REST. No active study.",
"REST.",
"Read 5 key language patterns from your notes.",
"Practice 1 roleplay β keep it light, build confidence."),
("15-Sep","Tue","Exam Week",
"REST.",
"REST.",
"REST.",
"REST β prepare mentally."),
("16-Sep","Wed","Exam Week β Prep",
"Confirm exam venue address and travel plan.",
"Confirm exam venue address and travel plan.",
"Confirm all documents: passport, booking confirmation.",
"Confirm Speaking exam date/time if different from written."),
("17-Sep","Thu","Exam Eve",
"NO STUDY. Pack bag: passport, water, snacks.",
"NO STUDY.",
"NO STUDY.",
"Light Speaking warm-up only: 10-min roleplay tomorrow morning."),
("18-Sep","Fri","π― EXAM DAY",
"Listening sub-test β Remember: wait for conclusion language!",
"Reading sub-test β Question First Method every single question!",
"Writing sub-test β SortβFilterβWrite. Paraphrase, don't copy!",
"Speaking sub-test (if today) β Open warmly, ICE, Close properly!"),
("19-Sep","Sat","π― EXAM DAY 2",
"If Speaking is today: warm up voice, 1 quick roleplay at home.",
"β",
"β",
"Speaking sub-test β You've prepared for this. Trust your training."),
]
STATUS_OPTIONS = '"β
Done,β³ In Progress,β Skipped,π Partial"'
dv = DataValidation(type="list", formula1=STATUS_OPTIONS, allow_blank=True)
dv.sqref = f"I6:I{6+len(days)}"
ws2.add_data_validation(dv)
alt = [WHITE, GREY_L]
week_colors = [LT_BLUE, LT_PURPLE, LT_AMBER, LT_GREEN, LT_RED, LT_TEAL, "EAECEE"]
week_hdrs = ["WEEK 1: DIAGNOSTIC & FORMAT", "WEEK 2: MCQ TECHNIQUE",
"WEEK 3: WRITING DEEP DIVE", "WEEK 4: SPEAKING POLISH",
"WEEK 5: FULL MOCKS", "WEEK 6: FINAL REFINEMENT", "EXAM WEEK"]
week_bgs = [BLUE, PURPLE, AMBER, GREEN, RED, TEAL, GREY_D]
row = 6
week_breaks = [7, 14, 21, 28, 35, 42, 49] # day indices (1-indexed) where week changes
current_week = 0
days_in_week = [7, 7, 7, 7, 7, 7, 2]
day_count = 0
for wk_idx, wk_count in enumerate(days_in_week):
# Week header
h(ws2, row, 24)
merge_hdr(ws2, row, 2, 8, week_hdrs[wk_idx], bg=week_bgs[wk_idx], sz=11)
row += 1
for d_idx in range(wk_count):
if day_count >= len(days): break
date, day, phase, lst, rdr, wrt, spk = days[day_count]
h(ws2, row, 44)
bg = week_colors[wk_idx]
# Determine special rows
is_mock = "FULL TIMED MOCK" in lst
is_rest = "REST" in phase or "REST" in lst
is_exam = "EXAM DAY" in phase
if is_exam:
bg = LT_RED
elif is_mock:
bg = "FFE4E1"
elif is_rest:
bg = "F0F0F0"
cell(ws2, row, 2, date, bg=bg, bold=True, halign="center", fg=NAVY)
cell(ws2, row, 3, day, bg=bg, halign="center", fg="555555")
cell(ws2, row, 4, phase, bg=bg, bold=is_mock or is_exam, halign="left",
fg=RED if is_exam else (BLUE if is_mock else GREY_D))
cell(ws2, row, 5, lst, bg=bg, wrap=True, fg="1A5276")
cell(ws2, row, 6, rdr, bg=bg, wrap=True, fg="4A235A")
cell(ws2, row, 7, wrt, bg=bg, wrap=True, fg="7D6608")
cell(ws2, row, 8, spk, bg=bg, wrap=True, fg=GREEN)
# Status dropdown
c_stat = ws2.cell(row=row, column=9, value="β³ In Progress" if date == "01-Aug" else "")
c_stat.fill = fill(bg)
c_stat.font = font(size=10)
c_stat.alignment = align("center")
c_stat.border = border("medium", "AAAAAA")
row += 1
day_count += 1
row += 1 # spacer between weeks
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 3 β DAILY TASK CHECKLIST (printable)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws3 = wb.create_sheet("β
Daily Checklist")
ws3.sheet_view.showGridLines = False
w3_widths = [2, 8, 10, 32, 8, 8, 3]
for i, cw in enumerate(w3_widths, 1): w(ws3, i, cw)
h(ws3, 1, 6)
h(ws3, 2, 36)
merge_hdr(ws3, 2, 2, 6, "β
DAILY TASK CHECKLIST | Print this sheet",
bg=NAVY, sz=14)
h(ws3, 3, 18)
merge_hdr(ws3, 3, 2, 6,
"Tick each box as you complete it. Update your Weekly Plan status column.",
bg=BLUE, sz=10, bold=False)
h(ws3, 4, 6)
# Headers
h(ws3, 5, 20)
for j, ch in enumerate(["Date", "Sub-test", "Task", "Time", "Done?"], 2):
hdr(ws3, 5, j, ch, bg=GREY_D)
checklists = [
# Listening daily tasks
("Daily", "Listening", "Read all MCQ options BEFORE playing audio. Underline key word in each.", "5 min", "β"),
("Daily", "Listening", "During audio: hold your answer. Only mark after conclusion language heard.", "During", "β"),
("Daily", "Listening", "After each wrong answer: replay and label the trap type.", "5 min", "β"),
("Daily", "Listening", "Weekly: count error types. Same trap repeating? That is your pattern.", "5 min", "β"),
("", "", "", "", ""),
# Reading daily tasks
("Daily", "Reading", "Question First: read stem ONLY before looking at passage.", "Per Q", "β"),
("Daily", "Reading", "Underline key word in question. Write 1 paraphrase in margin.", "Per Q", "β"),
("Daily", "Reading", "Scan text for key word β read ONLY that sentence Β±1.", "Per Q", "β"),
("Daily", "Reading", "Check options: any absolute words? If yes, treat as suspicious.", "Per Q", "β"),
("Daily", "Reading", "Part A: strict 15-min timer. No exceptions.", "15 min", "β"),
("", "", "", "", ""),
# Writing daily tasks
("Daily", "Writing", "Set up 5 buckets on rough paper: A=Who, B=Why, C=History, D=Now, E=Done.", "3 min", "β"),
("Daily", "Writing", "Sort ALL case note information into buckets BEFORE writing.", "3 min", "β"),
("Daily", "Writing", "Filter: cross out any information NOT needed by the recipient.", "1 min", "β"),
("Daily", "Writing", "Write letter from buckets. PARAPHRASE β do not copy case notes.", "35 min", "β"),
("Daily", "Writing", "Check: does opening state purpose clearly? Is register formal throughout?", "2 min", "β"),
("Daily", "Writing", "Check: have you included all 5 criteria? Purpose/Content/Concise/Genre/Organisation", "1 min", "β"),
("", "", "", "", ""),
# Speaking daily tasks
("Daily", "Speaking", "Open: greeting + empathy statement + invite patient to speak.", "0:00-1:00", "β"),
("Daily", "Speaking", "Clinical content: gather info OR explain β whichever is required.", "1:00-3:00", "β"),
("Daily", "Speaking", "ICE check at ~3 min: Ideas? Concerns? Expectations?", "3:00-4:00", "β"),
("Daily", "Speaking", "Close: Summary + 'Any questions?' + Safety-net + 'Anything else?'", "4:00-5:00", "β"),
("Daily", "Speaking", "Record yourself. Listen back. Did all 4 elements appear?", "5 min", "β"),
("", "", "", "", ""),
# Mock test checklist
("Mock Day", "All", "Set up exam conditions: timer, no interruptions, phone away.", "Before", "β"),
("Mock Day", "All", "Listening: do not pause the audio. Simulate real exam.", "45 min", "β"),
("Mock Day", "All", "Reading: do not look up answers mid-test. Full 60 min.", "60 min", "β"),
("Mock Day", "All", "Writing: no notes or references. Only your memory.", "45 min", "β"),
("Mock Day", "All", "Speaking: record yourself. Full 2 roleplays, no breaks.", "20 min", "β"),
("Mock Day", "All", "Score mock. Record scores in Score Tracker sheet.", "After", "β"),
]
st_bgs = {
"Listening": LT_BLUE, "Reading": LT_PURPLE,
"Writing": LT_AMBER, "Speaking": LT_GREEN,
"All": LT_RED, "": WHITE
}
for i, (date, sub, task, time, done) in enumerate(checklists):
r = 6 + i
h(ws3, r, 20 if task else 8)
bg = st_bgs.get(sub, WHITE)
cell(ws3, r, 2, date, bg=bg, halign="center")
cell(ws3, r, 3, sub, bg=bg, bold=bool(sub), halign="center")
cell(ws3, r, 4, task, bg=bg, wrap=True)
cell(ws3, r, 5, time, bg=bg, halign="center")
cell(ws3, r, 6, done, bg=bg, bold=True, halign="center", sz=14)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 4 β SCORE TRACKER
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws4 = wb.create_sheet("π Score Tracker")
ws4.sheet_view.showGridLines = False
w4_widths = [2, 10, 12, 12, 12, 12, 14, 18, 3]
for i, cw in enumerate(w4_widths, 1): w(ws4, i, cw)
h(ws4, 1, 6)
h(ws4, 2, 36)
merge_hdr(ws4, 2, 2, 8,
"π MOCK TEST SCORE TRACKER | Enter your scores after every mock",
bg=NAVY, sz=14)
h(ws4, 3, 18)
merge_hdr(ws4, 3, 2, 8,
"Grade B = 350 in each sub-test. Blue cells = enter your scores. Target line shown.",
bg=BLUE, sz=10, bold=False)
h(ws4, 4, 6)
# Target line
h(ws4, 5, 18)
merge_hdr(ws4, 5, 2, 8, "TARGET: Grade B = 350 in EVERY sub-test (one sitting)", bg=GREEN, sz=11)
# Headers
h(ws4, 6, 22)
score_hdrs = ["Mock #", "Date", "Listening", "Reading", "Writing", "Speaking", "Average", "Notes"]
sc_hdr_bgs = [GREY_D, GREY_D, "1A5276", "4A235A", "7D6608", GREEN, NAVY, GREY_D]
for j, (sh, sbg) in enumerate(zip(score_hdrs, sc_hdr_bgs), 2):
hdr(ws4, 6, j, sh, bg=sbg)
# Score rows
mock_dates = ["07-Aug", "21-Aug", "28-Aug", "01-Sep", "04-Sep", "09-Sep"]
mock_names = ["Mock #1\n(Baseline)", "Mock #2", "Mock #3", "Mock #4", "Mock #5", "Mock #6\n(Final)"]
for i in range(6):
r = 7 + i
h(ws4, r, 22)
bg = [LT_BLUE, LT_PURPLE, LT_AMBER, LT_GREEN, LT_RED, LT_TEAL][i]
cell(ws4, r, 2, mock_names[i], bg=bg, bold=True, halign="center")
cell(ws4, r, 3, mock_dates[i], bg=bg, halign="center", fg="555555")
for j in range(4): # L, R, W, S
c = ws4.cell(row=r, column=4+j, value=None)
c.fill = fill("FFFDE7") # yellow = input
c.font = font(bold=True, size=11, color="0000FF")
c.alignment = align("center")
c.border = border("medium", "2E86C1")
c.number_format = "0"
# Average formula
c_avg = ws4.cell(row=r, column=8,
value=f"=IF(COUNT(D{r}:G{r})=4,AVERAGE(D{r}:G{r}),\"\")")
c_avg.fill = fill(bg)
c_avg.font = font(bold=True, size=11, color=NAVY)
c_avg.alignment = align("center")
c_avg.border = border("medium", NAVY)
c_avg.number_format = "0.0"
# Notes
cell(ws4, r, 9, "", bg=bg, wrap=True)
# Target line
h(ws4, 13, 22)
merge_hdr(ws4, 13, 2, 3, "π― GRADE B TARGET", bg=GREEN)
for j in range(4):
c = ws4.cell(row=13, column=4+j, value=350)
c.fill = fill(LT_GREEN)
c.font = font(bold=True, size=11, color=GREEN)
c.alignment = align("center")
c.border = thick_border(GREEN)
c.number_format = "0"
ws4.cell(row=13, column=8, value=350).fill = fill(LT_GREEN)
ws4.cell(row=13, column=8).font = font(bold=True, color=GREEN)
ws4.cell(row=13, column=8).alignment = align("center")
ws4.cell(row=13, column=9, value="All sub-tests must hit 350").fill = fill(LT_GREEN)
ws4.cell(row=13, column=9).font = font(italic=True, color=GREEN)
h(ws4, 14, 6)
# Progress summary
h(ws4, 15, 20)
merge_hdr(ws4, 15, 2, 8, "PROGRESS SUMMARY", bg=GREY_D)
h(ws4, 16, 20)
for j, sh in enumerate(["Sub-test", "Mock 1", "Mock 6", "Change", "Target", "Status"], 2):
hdr(ws4, 16, j, sh, bg=GREY_D)
sub_rows = ["Listening", "Reading", "Writing", "Speaking"]
sub_bgs_list = [LT_BLUE, LT_PURPLE, LT_AMBER, LT_GREEN]
for i, (sub, sbg) in enumerate(zip(sub_rows, sub_bgs_list)):
r = 17 + i
h(ws4, r, 20)
col_offset = i # L=col4, R=col5, W=col6, S=col7 in score rows
m1_col = get_column_letter(4 + col_offset)
m6_col = get_column_letter(4 + col_offset)
cell(ws4, r, 2, sub, bg=sbg, bold=True, halign="center")
# Mock 1 ref
c_m1 = ws4.cell(row=r, column=3, value=f"={m1_col}7")
c_m1.fill = fill(sbg); c_m1.font = font(size=11); c_m1.alignment = align("center")
c_m1.number_format = "0"; c_m1.border = border()
# Mock 6 ref
c_m6 = ws4.cell(row=r, column=4, value=f"={m6_col}12")
c_m6.fill = fill(sbg); c_m6.font = font(size=11, bold=True); c_m6.alignment = align("center")
c_m6.number_format = "0"; c_m6.border = border()
# Change
c_chg = ws4.cell(row=r, column=5, value=f"=IF(AND(ISNUMBER(C{r}),ISNUMBER(D{r})),D{r}-C{r},\"\")")
c_chg.fill = fill(sbg); c_chg.font = font(size=11); c_chg.alignment = align("center")
c_chg.number_format = "+0;-0;0"; c_chg.border = border()
# Target
cell(ws4, r, 6, 350, bg=sbg, bold=True, halign="center")
# Status
c_st = ws4.cell(row=r, column=7,
value=f'=IF(D{r}="","Pending",IF(D{r}>=350,"β
PASS","β Below B"))')
c_st.fill = fill(sbg); c_st.alignment = align("center"); c_st.border = border()
c_st.font = font(size=10, bold=True)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 5 β RESOURCES & TIPS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws5 = wb.create_sheet("π Resources")
ws5.sheet_view.showGridLines = False
w5_widths = [2, 22, 50, 3]
for i, cw in enumerate(w5_widths, 1): w(ws5, i, cw)
h(ws5, 1, 6)
h(ws5, 2, 36)
merge_hdr(ws5, 2, 2, 3, "π RESOURCES, LINKS & QUICK-REFERENCE TIPS", bg=NAVY, sz=14)
h(ws5, 3, 8)
resources_data = [
("OFFICIAL OET RESOURCES", BLUE, [
("OET Official Website", "oet.com β register for exam, free sample tests, Writing criteria PDF"),
("OET Sample Tests", "oet.com/test-takers/test-information β free Listening, Reading, Writing, Speaking samples"),
("OET Scoring Guide", "Download 'OET Writing sub-test assessment criteria' from oet.com β read before first letter"),
("OET YouTube Channel", "Search 'OET' on YouTube β official channel has free explanations of all 4 sub-tests"),
]),
("PREP PLATFORMS (PAID)", PURPLE, [
("E2Language OET", "e2language.com/oet β best all-in-one platform. Writing correction service included."),
("OETpractice.net", "oetpractice.net β full mock tests with answer explanations. Good for MCQ drill."),
("Swoosh English", "swooshenglish.com/oet β writing correction service only (~Β£8-10 per letter)"),
("PLAB2Ninja (Speaking)", "plab2ninja.com β OSCE/roleplay platform, also useful for OET Speaking prep"),
]),
("FREE READING MATERIALS", GREEN, [
("NHS CKS", "cks.nice.org.uk β clinical knowledge summaries. Read 1 topic/day for reading practice"),
("BMJ News", "bmj.com/news β free medical news in professional English"),
("Patient.info", "patient.info β patient information leaflets. Practice converting clinicalβlay language"),
("Cochrane Summaries", "cochrane.org/evidence β plain language summaries. Good for inference practice"),
("BNF Online", "bnf.nice.org.uk β British drug formulary. Free. Essential for Writing vocabulary"),
]),
("LISTENING MCQ β QUICK TIPS", "1A5276", [
("Conclusion language", "Wait for: 'ultimately', 'finally', 'in the end', 'we decided', 'however the result was'"),
("Main point words", "Listen for: 'The key issue', 'What this means', 'Most important', 'In conclusion'"),
("Paraphrase pairs", "compliance=adherence | deterioration=worsening | prognosis=outlook | aetiology=cause"),
("Distractor rule", "Wrong answers are MENTIONED first. Correct answer comes LAST or at conclusion."),
]),
("READING MCQ β QUICK TIPS", "4A235A", [
("Question First", "Read question stem β key words β scan text β read ONLY relevant sentence Β±1 β then options"),
("Absolute words", "always/never/all/definitely/proven = almost always WRONG"),
("Correct hedging", "may/might/some/suggests/evidence indicates/appears = correct signal words"),
("Inference: 3 steps", "1) Find relevant section 2) Author's attitude? 3) Match attitude to option"),
("Part A timing", "15 min MAXIMUM. Skim all 4 texts in 3 min. Match by scanning only. Never re-read."),
]),
("WRITING β QUICK TIPS", "7D6608", [
("5 Buckets", "A=Who | B=Why referred | C=History | D=Now (presentation/results) | E=Done (management)"),
("Opening line", "'I am writing to refer/inform/request regarding [Patient], who presented with...'"),
("Never copy", "Paraphrase case notes β never transcribe directly. Assessors penalise this heavily."),
("Filter test", "For each note item: 'Does the recipient NEED this to do their job?' If no, exclude."),
("Close line", "'Please do not hesitate to contact me should you require further information.'"),
]),
("SPEAKING β QUICK TIPS", GREEN, [
("Opening", "Greeting + empathy ('That must be worrying for you') + invite ('Tell me in your own words')"),
("ICE at 3 min", "Ideas: 'What do you think is causing this?' | Concerns: 'Is there something you were worried about?'"),
("Empathy phrases", "'I completely understand' | 'That must be really difficult' | 'I can see why you're concerned'"),
("Close", "Summary + 'Any questions?' + Safety-net ('Come back if worse') + 'Anything else?'"),
("Time structure", "Open: 0-1 min | Clinical: 1-3 min | ICE: 3-4 min | Close: 4-5 min"),
]),
("UK SPELLING β MUST KNOW", RED, [
("Respiratory", "dyspnoea | haemoptysis | tachypnoea | pneumonia | bronchiectasis"),
("Cardiac", "arrhythmia | tachycardia | bradycardia | haemorrhage | atherosclerosis"),
("Renal/GI", "haematuria | diarrhoea | anaemia | oedema | haematemesis"),
("Neuro/Other", "anaesthesia | paraesthesia | dysarthria | hypertension | hypothyroidism"),
("Note", "UK spelling uses -ae- and -oe- where US uses -e- (anaemia vs anemia, oedema vs edema)"),
]),
]
row = 4
for section_title, section_color, items in resources_data:
h(ws5, row, 22)
ws5.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
c = ws5.cell(row=row, column=2, value=section_title)
c.fill = fill(section_color); c.font = font(bold=True, size=11, color=WHITE)
c.alignment = align("left", "center")
c.border = border("medium", "888888")
row += 1
sc_light = {"1A5276": LT_BLUE, "4A235A": LT_PURPLE, "7D6608": LT_AMBER,
GREEN: LT_GREEN, RED: LT_RED, BLUE: LT_BLUE, PURPLE: LT_PURPLE, TEAL: LT_TEAL}
bg_light = sc_light.get(section_color, GREY_L)
for i, (label, detail) in enumerate(items):
h(ws5, row, 20)
bg = WHITE if i % 2 == 0 else bg_light
cell(ws5, row, 2, label, bg=bg, bold=True, halign="left")
cell(ws5, row, 3, detail, bg=bg, wrap=True)
row += 1
row += 1
# ββ SAVE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
out = "/tmp/workspace/plab-schedule/OET_Study_Plan_2026.xlsx"
wb.save(out)
print(f"Saved: {out}")
cd /tmp/workspace/plab-schedule && python3 build_oet_plan.py 2>&1
ls -lh /tmp/workspace/plab-schedule/OET_Study_Plan_2026.xlsx
OET Study Plan 2026
Spreadsheet Β· XLSX
Can you also make workout plan for gym as i am 174 kg, 6.2 ft
I was 155 kg back in university then i trained there and reduced to 98kg but after lock down i put on weight and due to studies I didnβt give attention to obesity. Then i joined gym on 9th july and have been doing 45 min treadmill and random strength workout.
~/plab-schedule/build_gym_plan.py
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
wb = Workbook()
# ββ COLOURS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
NAVY = "1B3A6B"
BLUE = "2E86C1"
LT_BLUE = "D6EAF8"
GREEN = "1E8449"
LT_GREEN = "D5F5E3"
AMBER = "D4AC0D"
LT_AMBER = "FEF9E7"
RED = "C0392B"
LT_RED = "FDEDEC"
PURPLE = "6C3483"
LT_PURPLE = "F4ECF7"
TEAL = "117A65"
LT_TEAL = "D1F2EB"
ORANGE = "CA6F1E"
LT_ORANGE = "FDEBD0"
GREY_D = "2C3E50"
GREY_L = "F2F3F4"
WHITE = "FFFFFF"
BLACK = "000000"
# Day type colours
DAY_COLORS = {
"Upper Body": ("1A5276", "D6EAF8"),
"Lower Body": ("1E8449", "D5F5E3"),
"Cardio": ("7D6608", "FEF9E7"),
"Full Body": ("6C3483", "F4ECF7"),
"Push": ("922B21", "FDEDEC"),
"Pull": ("117A65", "D1F2EB"),
"Legs": ("1E8449", "D5F5E3"),
"REST": ("717D7E", "F2F3F4"),
"Active Rest": ("2E86C1", "EBF5FB"),
}
def fill(c): return PatternFill("solid", fgColor=c)
def font(bold=False, size=10, color=BLACK, italic=False, name="Calibri"):
return Font(bold=bold, size=size, color=color, italic=italic, name=name)
def border(style="thin", color="CCCCCC"):
s = Side(style=style, color=color)
return Border(left=s, right=s, top=s, bottom=s)
def thick_border(color="888888"):
t = Side(style="medium", color=color)
return Border(left=t, right=t, top=t, bottom=t)
def align(h="left", v="center", wrap=False):
return Alignment(horizontal=h, vertical=v, wrap_text=wrap)
def w(ws, col, width): ws.column_dimensions[get_column_letter(col)].width = width
def h(ws, row, height): ws.row_dimensions[row].height = height
def hdr(ws, row, col, text, bg=NAVY, fg=WHITE, sz=10, bold=True,
halign="center", wrap=True):
c = ws.cell(row=row, column=col, value=text)
c.fill = fill(bg); c.font = font(bold=bold, size=sz, color=fg)
c.alignment = align(halign, "center", wrap)
c.border = border("thin", "999999")
return c
def cell(ws, row, col, value=None, bg=WHITE, fg=BLACK, sz=10,
bold=False, halign="left", wrap=True, border_col="CCCCCC"):
c = ws.cell(row=row, column=col, value=value)
c.fill = fill(bg); c.font = font(bold=bold, size=sz, color=fg)
c.alignment = align(halign, "center", wrap)
c.border = border("thin", border_col)
return c
def merge_hdr(ws, r, cs, ce, text, bg=NAVY, fg=WHITE, sz=12, bold=True):
ws.merge_cells(start_row=r, start_column=cs, end_row=r, end_column=ce)
c = ws.cell(row=r, column=cs, value=text)
c.fill = fill(bg); c.font = font(bold=bold, size=sz, color=fg)
c.alignment = align("center", "center")
c.border = border("medium", "888888")
return c
def section_hdr(ws, r, cs, ce, text, bg, fg=WHITE, sz=11):
ws.merge_cells(start_row=r, start_column=cs, end_row=r, end_column=ce)
c = ws.cell(row=r, column=cs, value=text)
c.fill = fill(bg); c.font = font(bold=True, size=sz, color=fg)
c.alignment = align("left", "center")
c.border = border("thin", "AAAAAA")
return c
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 1 β OVERVIEW & PROFILE
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws1 = wb.active
ws1.title = "π Overview"
ws1.sheet_view.showGridLines = False
for i, cw in enumerate([2,24,14,14,14,14,20,3], 1): w(ws1, i, cw)
h(ws1, 1, 6)
h(ws1, 2, 42)
merge_hdr(ws1, 2, 2, 7, "πͺ GYM WORKOUT PLAN | Weight Loss & Strength Programme",
bg=NAVY, sz=16)
h(ws1, 3, 20)
merge_hdr(ws1, 3, 2, 7,
"Height: 6'2\" (188cm) | Weight: 174kg | Previous Low: 98kg | Start Date: 09-Jul-2026",
bg=BLUE, sz=10, bold=False)
h(ws1, 4, 6)
# Profile & Stats
h(ws1, 5, 18)
merge_hdr(ws1, 5, 2, 4, "YOUR STATS", bg=GREY_D)
merge_hdr(ws1, 5, 5, 7, "PROGRAMME OVERVIEW", bg=GREY_D)
stats = [
("Current Weight", "174 kg"),
("Height", "6'2\" / 188 cm"),
("BMI", "49.2 (Class III)"),
("Previous Low", "98 kg"),
("Weight to Goal (98kg)", "76 kg to lose"),
("Currently Active Since", "09 July 2026"),
]
stat_bgs = [LT_RED, LT_BLUE, LT_RED, LT_GREEN, LT_AMBER, LT_TEAL]
for i, (lbl, val) in enumerate(stats):
r = 6 + i
h(ws1, r, 20)
cell(ws1, r, 2, lbl, bg=stat_bgs[i], bold=True)
cell(ws1, r, 3, val, bg=stat_bgs[i], bold=True, halign="center",
fg=RED if "BMI" in lbl else GREEN if "Previous" in lbl else NAVY)
ws1.merge_cells(start_row=r, start_column=3, end_row=r, end_column=4)
prog_info = [
("Programme Type", "Push/Pull/Legs + Daily Cardio"),
("Training Days", "5 days/week + 1 active rest"),
("Session Length", "75β90 minutes total"),
("Cardio", "45 min daily (treadmill/bike/elliptical)"),
("Strength Focus", "Compound lifts, progressive overload"),
("Phases", "Phase 1: Foundation (8 wks) β Phase 2: Strength (8 wks) β Phase 3: Cut"),
]
prog_bgs = [LT_BLUE, LT_GREEN, LT_AMBER, LT_TEAL, LT_PURPLE, LT_ORANGE]
for i, (lbl, val) in enumerate(prog_info):
r = 6 + i
h(ws1, r, 20)
cell(ws1, r, 5, lbl, bg=prog_bgs[i], bold=True)
cell(ws1, r, 6, val, bg=prog_bgs[i])
ws1.merge_cells(start_row=r, start_column=6, end_row=r, end_column=7)
h(ws1, 12, 6)
# Weekly schedule
h(ws1, 13, 18)
merge_hdr(ws1, 13, 2, 7, "WEEKLY TRAINING SPLIT", bg=GREY_D)
h(ws1, 14, 22)
for j, dh in enumerate(["Day", "Session Type", "Strength Focus", "Cardio", "Duration", "Intensity"], 2):
hdr(ws1, 14, j, dh, bg=GREY_D)
weekly = [
("Monday", "Push Day", "Chest, Shoulders, Triceps", "20 min HIIT treadmill", "75 min", "Moderate-High"),
("Tuesday", "Pull Day", "Back, Biceps, Rear Delts", "30 min steady treadmill", "80 min", "Moderate"),
("Wednesday", "Legs Day", "Quads, Hamstrings, Glutes, Calves","45 min treadmill (incline)", "90 min", "Moderate"),
("Thursday", "Active Rest", "Core + Stretching + Mobility", "45 min brisk walk/elliptical","60 min", "Low"),
("Friday", "Upper Body", "Full upper β chest/back/shoulders","25 min steady cardio", "85 min", "Moderate-High"),
("Saturday", "Legs + Core", "Legs (lighter) + Core circuit", "45 min treadmill (steady)", "90 min", "Moderate"),
("Sunday", "REST", "Full rest or 20-min walk only", "Optional: 20 min walk", "β", "Rest"),
]
day_bgs_map = {
"Monday": LT_RED, "Tuesday": LT_TEAL, "Wednesday": LT_GREEN,
"Thursday": LT_BLUE, "Friday": LT_ORANGE, "Saturday": LT_PURPLE, "Sunday": GREY_L
}
for i, (day, stype, focus, cardio, dur, intensity) in enumerate(weekly):
r = 15 + i
h(ws1, r, 22)
bg = day_bgs_map[day]
cell(ws1, r, 2, day, bg=bg, bold=True, halign="center")
cell(ws1, r, 3, stype, bg=bg, bold=True, halign="center")
cell(ws1, r, 4, focus, bg=bg)
cell(ws1, r, 5, cardio, bg=bg)
cell(ws1, r, 6, dur, bg=bg, bold=True, halign="center")
cell(ws1, r, 7, intensity, bg=bg, halign="center")
h(ws1, 22, 6)
# Phase overview
h(ws1, 23, 18)
merge_hdr(ws1, 23, 2, 7, "3-PHASE PROGRAMME ROADMAP", bg=GREY_D)
h(ws1, 24, 22)
for j, ph in enumerate(["Phase", "Duration", "Period", "Focus", "Expected Result", "Key Principle"], 2):
hdr(ws1, 24, j, ph, bg=GREY_D)
phases_data = [
("Phase 1\nFoundation", "8 weeks", "Jul 9 β Sep 2, 2026",
"Re-establish movement patterns, build work capacity, daily cardio habit",
"8β12 kg weight loss, improved stamina, joints adapting",
"High volume, moderate weight, perfect form. Don't ego-lift."),
("Phase 2\nStrength", "8 weeks", "Sep 3 β Oct 28, 2026",
"Progressive overload on compound lifts, increase intensity",
"Further 8β12 kg loss, noticeable strength gains, body recomposition",
"Add weight every 1-2 weeks. Track every session."),
("Phase 3\nCut", "8 weeks+", "Oct 29, 2026 onwards",
"Maintain muscle, accelerate fat loss with calorie deficit + cardio increase",
"Push toward 130 kg range by early 2027",
"Protein high, calories moderate deficit, cardio 60 min/day"),
]
phase_bgs = [LT_BLUE, LT_GREEN, LT_AMBER]
for i, (ph, dur, period, focus, result, principle) in enumerate(phases_data):
r = 25 + i
h(ws1, r, 44)
bg = phase_bgs[i]
cell(ws1, r, 2, ph, bg=bg, bold=True, halign="center")
cell(ws1, r, 3, dur, bg=bg, bold=True, halign="center")
cell(ws1, r, 4, period, bg=bg, halign="center")
cell(ws1, r, 5, focus, bg=bg, wrap=True)
cell(ws1, r, 6, result, bg=bg, wrap=True)
cell(ws1, r, 7, principle, bg=bg, wrap=True, italic=True, fg="444444")
# Important medical note
h(ws1, 28, 6)
h(ws1, 29, 20)
merge_hdr(ws1, 29, 2, 7,
"β MEDICAL NOTE: At 174kg, always warm up 10 min before lifting. Never hold breath during lifts (Valsalva carefully). "
"Stop if chest pain, dizziness, or severe joint pain. Ideally get BP checked before starting heavy lifting.",
bg=LT_RED, fg=RED, sz=10, bold=False)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 2 β PHASE 1 WORKOUTS (detailed)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws2 = wb.create_sheet("π Phase 1 Workouts")
ws2.sheet_view.showGridLines = False
for i, cw in enumerate([2,14,24,10,10,10,10,22,3], 1): w(ws2, i, cw)
h(ws2, 1, 6)
h(ws2, 2, 36)
merge_hdr(ws2, 2, 2, 8,
"π PHASE 1 WORKOUTS | Foundation | Weeks 1β8 (Jul 9 β Sep 2, 2026)",
bg=NAVY, sz=14)
h(ws2, 3, 20)
merge_hdr(ws2, 3, 2, 8,
"Focus: re-establish form, build work capacity, all compound movements. "
"Rest 90 sec between sets. Increase weight when all reps feel easy.",
bg=BLUE, sz=10, bold=False)
h(ws2, 4, 6)
# ββ WORKOUT DATA ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
workouts = {
"MONDAY β PUSH DAY (Chest / Shoulders / Triceps)": (LT_RED, "922B21", [
("Warm-up", "Treadmill walk", "10 min", "β", "β", "β", "Build heart rate to 120 bpm"),
("A1", "Incline Machine Press", "4 Γ 10", "RPE 7","90s", "Wk5+","Safer than free bench at high BW. Full ROM."),
("A2", "Cable Chest Fly", "3 Γ 12", "RPE 6","60s", "β", "Squeeze chest at peak. Control the negative."),
("B1", "Seated Dumbbell Shoulder Press", "4 Γ 10", "RPE 7","90s", "Wk4+","Seated = safer for lower back at this weight."),
("B2", "Lateral Raise (cable or DB)", "3 Γ 15", "RPE 6","60s", "β", "Lightweight, strict form, no swinging."),
("C1", "Tricep Rope Pushdown", "3 Γ 12", "RPE 6","60s", "β", "Full extension, elbows fixed at sides."),
("C2", "Overhead Tricep Extension (DB)", "3 Γ 12", "RPE 6","60s", "β", "Stretch at top, control the negative."),
("Cardio Finisher","Treadmill steady (incline 3%)", "20 min", "β", "β", "β", "HR 130-145 bpm. Can split: 10 min pre + 10 min post."),
]),
"TUESDAY β PULL DAY (Back / Biceps / Rear Delts)": (LT_TEAL, TEAL, [
("Warm-up", "Treadmill / Bike", "10 min", "β", "β", "β", "Easy pace, loosen up shoulders and back"),
("A1", "Lat Pulldown (wide grip)", "4 Γ 10", "RPE 7","90s", "Wk5+","Pull to upper chest. Don't lean back excessively."),
("A2", "Seated Cable Row", "4 Γ 10", "RPE 7","90s", "Wk5+","Drive elbows back. Squeeze shoulder blades together."),
("B1", "Single-Arm DB Row", "3 Γ 12", "RPE 7","75s", "Wk4+","Support on bench. Full stretch at bottom."),
("B2", "Face Pull (cable rope)", "3 Γ 15", "RPE 6","60s", "β", "Crucial for shoulder health at this volume. Never skip."),
("C1", "EZ Bar Curl", "3 Γ 12", "RPE 6","60s", "β", "No swinging. Pause at top."),
("C2", "Hammer Curl (alternating)", "3 Γ 12", "RPE 6","60s", "β", "Works brachialis β builds arm thickness."),
("Cardio Finisher","Treadmill steady", "30 min", "β", "β", "β", "HR 125-140. Longest cardio day β this is your fat burner."),
]),
"WEDNESDAY β LEGS DAY (Quads / Hamstrings / Glutes / Calves)": (LT_GREEN, GREEN, [
("Warm-up", "Treadmill walk (incline 5%)", "10 min", "β", "β", "β", "Hip circles and leg swings after. CRITICAL: never skip leg warm-up."),
("A1", "Leg Press (wide stance)", "4 Γ 12", "RPE 7","120s","Wk5+","Wide stance = less knee stress. Don't lock knees at top."),
("A2", "Leg Extension (machine)", "3 Γ 15", "RPE 6","60s", "β", "Light weight, high reps. Control on the way down."),
("B1", "Romanian Deadlift (RDL)", "3 Γ 10", "RPE 7","90s", "Wk4+","Hinge at hips. Feel hamstring stretch. Back flat always."),
("B2", "Leg Curl (machine, lying)", "3 Γ 12", "RPE 6","75s", "β", "Full range. Squeeze at top of contraction."),
("C1", "Glute Bridge or Hip Thrust", "3 Γ 15", "RPE 6","60s", "β", "Activate glutes β protects knees and lower back."),
("C2", "Calf Raise (standing, machine)", "4 Γ 15", "RPE 6","45s", "β", "Full stretch at bottom. Don't bounce."),
("Cardio Finisher","Treadmill (incline 4-6%)", "20 min", "β", "β", "β", "Walking at incline burns more than flat running at this weight."),
]),
"THURSDAY β ACTIVE REST (Core / Mobility / Light Cardio)": (LT_BLUE, BLUE, [
("Warm-up", "5-min easy walk", "5 min", "β", "β", "β", "Just get moving. No rush."),
("A1", "Dead Bug", "3 Γ 10", "RPE 5","45s", "β", "Core stability. Lower back stays flat on ground at all times."),
("A2", "Plank (modified on knees OK)", "3 Γ 30s", "RPE 5","45s", "β", "Build to full plank over weeks. Quality over duration."),
("B1", "Bird Dog", "3 Γ 10", "RPE 4","45s", "β", "Opposite arm/leg. Great for lower back stability."),
("B2", "Hip Flexor Stretch", "3 Γ 45s", "β", "β", "β", "Each side. Tight hip flexors from sitting = injury risk."),
("C1", "Foam Roll (quads, IT band, back)","10 min", "β", "β", "β", "Slow rolls on sore spots. 30 sec each area."),
("C2", "Shoulder Mobility Circuit", "5 min", "β", "β", "β", "Arm circles, cross-body stretch, door frame pec stretch."),
("Cardio", "Brisk walk or elliptical", "45 min", "β", "β", "β", "HR 110-125. Active recovery burns cals without taxing muscles."),
]),
"FRIDAY β UPPER BODY (Full Upper β Chest / Back / Shoulders)": (LT_ORANGE, ORANGE, [
("Warm-up", "Bike or treadmill", "10 min", "β", "β", "β", "Band pull-aparts x 15 after β shoulder warm-up"),
("A1", "Machine Chest Press (flat)", "4 Γ 10", "RPE 7","90s", "Wk5+","Compound push. Full ROM, controlled negative."),
("A2", "Cable Row (seated, narrow grip)", "4 Γ 10", "RPE 7","90s", "Wk5+","Compound pull. Superset with A1 if gym allows."),
("B1", "Incline DB Press", "3 Γ 10", "RPE 7","90s", "Wk4+","Upper chest emphasis. Don't flare elbows too wide."),
("B2", "Single-Arm Cable Row", "3 Γ 12", "RPE 7","75s", "β", "Unilateral β addresses muscle imbalances."),
("C1", "DB Lateral Raise", "3 Γ 15", "RPE 6","60s", "β", "Shoulder width and definition."),
("C2", "Tricep Pushdown (straight bar)", "3 Γ 12", "RPE 6","60s", "β", "Arms straight at sides. Elbows locked."),
("C3", "DB Hammer Curl", "3 Γ 12", "RPE 6","60s", "β", "Arms."),
("Cardio Finisher","Treadmill steady", "25 min", "β", "β", "β", "HR 130-145. Good post-lift fat burning window."),
]),
"SATURDAY β LEGS + CORE (Lighter Legs + Core Circuit)": (LT_PURPLE, PURPLE, [
("Warm-up", "Treadmill walk (incline)", "10 min", "β", "β", "β", "10 bodyweight squats + hip circles after"),
("A1", "Goblet Squat (DB or KB)", "4 Γ 12", "RPE 6","90s", "Wk4+","Squat depth to ability. Heels flat. Chest up."),
("A2", "Step-Up (low box, bodyweight)", "3 Γ 10", "RPE 5","60s", "β", "Functional movement. Builds glutes and quads safely."),
("B1", "Leg Curl (machine)", "3 Γ 12", "RPE 6","60s", "β", "Hamstring maintenance from Wednesday."),
("B2", "Calf Raise (seated machine)", "3 Γ 15", "RPE 5","45s", "β", "Seated hits soleus (deeper calf). Variety."),
("C1", "Cable Crunch (kneeling)", "3 Γ 15", "RPE 6","60s", "β", "Best ab exercise at this weight. Weighted ab work > crunches."),
("C2", "Pallof Press (cable anti-rotation)","3 Γ 12", "RPE 6","60s", "β", "Core stability β protects lumbar spine under load."),
("C3", "Reverse Crunch", "3 Γ 12", "RPE 5","45s", "β", "Lower abs. Slow and controlled."),
("Cardio Finisher","Treadmill (steady, incline 4%)", "20 min", "β", "β", "β", "Keep HR 125-140. Don't push hard β recovery day."),
]),
}
row = 5
for workout_title, (bg_light, fg_dark, exercises) in workouts.items():
# Section header
h(ws2, row, 24)
ws2.merge_cells(start_row=row, start_column=2, end_row=row, end_column=8)
c = ws2.cell(row=row, column=2, value=f" {workout_title}")
c.fill = fill(fg_dark); c.font = font(bold=True, size=11, color=WHITE)
c.alignment = align("left", "center")
c.border = thick_border(fg_dark)
row += 1
# Column headers
h(ws2, row, 22)
ex_hdrs = ["#", "Exercise", "Sets Γ Reps", "RPE", "Rest", "Progress", "Notes / Form Cues"]
ex_hdr_bg = [fg_dark] * 7
for j, (eh, eb) in enumerate(zip(ex_hdrs, ex_hdr_bg), 2):
hdr(ws2, row, j, eh, bg=eb)
row += 1
# Exercise rows
alt = [WHITE, bg_light]
for i, (num, ex, sets, rpe, rest, prog, notes) in enumerate(exercises):
h(ws2, row, 36)
bg = alt[i % 2]
is_cardio = "Cardio" in num or "Warm" in num
cell(ws2, row, 2, num, bg=bg, bold=True, halign="center",
fg=BLUE if is_cardio else fg_dark)
cell(ws2, row, 3, ex, bg=bg, bold=not is_cardio)
cell(ws2, row, 4, sets, bg=bg, bold=True, halign="center", fg=fg_dark)
cell(ws2, row, 5, rpe, bg=bg, halign="center")
cell(ws2, row, 6, rest, bg=bg, halign="center")
cell(ws2, row, 7, prog, bg=bg if not prog else LT_GREEN,
halign="center", fg=GREEN if prog else "999999",
bold=bool(prog))
cell(ws2, row, 8, notes, bg=bg, wrap=True, fg="333333", sz=9)
row += 1
row += 1 # spacer
# Progress note
h(ws2, row, 28)
ws2.merge_cells(start_row=row, start_column=2, end_row=row, end_column=8)
c = ws2.cell(row=row, column=2,
value=" PROGRESSION RULE: When all reps in all sets feel RPE β€6, increase weight by 2.5β5kg next session. "
"Track in the Session Log sheet. Never increase weight if form breaks down.")
c.fill = fill(LT_GREEN); c.font = font(bold=True, size=10, color=GREEN)
c.alignment = align("left", "center")
c.border = thick_border(GREEN)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 3 β CARDIO PLAN
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws3 = wb.create_sheet("π Cardio Plan")
ws3.sheet_view.showGridLines = False
for i, cw in enumerate([2,14,16,14,14,14,26,3], 1): w(ws3, i, cw)
h(ws3, 1, 6)
h(ws3, 2, 36)
merge_hdr(ws3, 2, 2, 7,
"π CARDIO PLAN | Your most important fat-loss tool at this stage",
bg=NAVY, sz=14)
h(ws3, 3, 18)
merge_hdr(ws3, 3, 2, 7,
"At 174kg, 45 min treadmill burns ~550-700 kcal/session. This is your #1 fat loss lever. "
"Keep it consistent before adding intensity.",
bg=BLUE, sz=10, bold=False)
h(ws3, 4, 6)
# Cardio types explained
h(ws3, 5, 18)
merge_hdr(ws3, 5, 2, 7, "CARDIO TYPES β KNOW THESE", bg=GREY_D)
h(ws3, 6, 22)
for j, ch in enumerate(["Type", "HR Zone", "Effort", "Duration", "Best For", "Notes"], 2):
hdr(ws3, 6, j, ch, bg=GREY_D)
cardio_types = [
("LISS\n(Low Intensity Steady State)", "60-70% max HR\n(~125-140 bpm)", "Can hold a conversation",
"30-60 min", "Fat burning, recovery, daily calorie burn",
"Your main cardio right now. Treadmill walk at incline 3-5%. Burns fat without taxing muscles."),
("MISS\n(Moderate Intensity)", "70-80% max HR\n(~140-155 bpm)", "Can speak in short sentences",
"20-40 min", "Increased calorie burn, cardiovascular fitness",
"Add from Week 5 onwards. Brisk walk + occasional jog intervals on treadmill."),
("HIIT\n(High Intensity Intervals)", "80-90% max HR\n(~155-170 bpm)", "Difficult to speak",
"15-25 min", "Post-exercise calorie burn (EPOC), fitness spike",
"Add ONLY from Week 8+ and only on Push/Upper days. NOT on leg days. 30s on / 60s off format."),
]
ct_bgs = [LT_GREEN, LT_AMBER, LT_RED]
for i, (ct, hr, effort, dur, best, notes) in enumerate(cardio_types):
r = 7 + i
h(ws3, r, 44)
bg = ct_bgs[i]
cell(ws3, r, 2, ct, bg=bg, bold=True, halign="center")
cell(ws3, r, 3, hr, bg=bg, halign="center")
cell(ws3, r, 4, effort, bg=bg, halign="center")
cell(ws3, r, 5, dur, bg=bg, bold=True, halign="center")
cell(ws3, r, 6, best, bg=bg, wrap=True)
cell(ws3, r, 7, notes, bg=bg, wrap=True, fg="333333")
h(ws3, 10, 6)
# Weekly cardio plan
h(ws3, 11, 18)
merge_hdr(ws3, 11, 2, 7, "WEEKLY CARDIO SCHEDULE", bg=GREY_D)
h(ws3, 12, 22)
for j, ch in enumerate(["Day", "Type", "Duration", "Intensity", "HR Target", "Notes"], 2):
hdr(ws3, 12, j, ch, bg=GREY_D)
cardio_week = [
("Monday", "LISS β MISS (Wk5+)", "20 min post-strength", "Low-Moderate", "125-145", "Post-push day. Don't exhaust yourself for Tuesday pull."),
("Tuesday", "LISS", "30 min post-strength", "Low", "120-135", "Longest cardio. Treadmill or bike. Fat burning focus."),
("Wednesday", "LISS (incline walk)", "20 min post-strength", "Moderate", "130-145", "Incline 4-6%. Burns more calories than flat running at this weight."),
("Thursday", "LISS (Active Rest)", "45 min walk/elliptical", "Very Low", "110-130", "Recovery cardio. The elliptical is excellent β no impact on joints."),
("Friday", "LISS β HIIT (Wk8+)", "25 min post-strength", "Moderate", "130-150", "HIIT only from Wk 8+. 30s sprint / 60s walk on treadmill."),
("Saturday", "LISS", "20 min post-strength", "Low", "120-135", "Light day. Keep HR low. Recovery."),
("Sunday", "Optional walk", "20 min (optional)", "Very Low", "100-120", "Just a walk. Or full rest. Listen to your body."),
]
day_bgs_c = [LT_RED, LT_TEAL, LT_GREEN, LT_BLUE, LT_ORANGE, LT_PURPLE, GREY_L]
for i, (day, ctype, dur, intensity, hr, notes) in enumerate(cardio_week):
r = 13 + i
h(ws3, r, 28)
bg = day_bgs_c[i]
cell(ws3, r, 2, day, bg=bg, bold=True, halign="center")
cell(ws3, r, 3, ctype, bg=bg, bold=True)
cell(ws3, r, 4, dur, bg=bg, bold=True, halign="center")
cell(ws3, r, 5, intensity, bg=bg, halign="center")
cell(ws3, r, 6, hr, bg=bg, bold=True, halign="center", fg=GREEN)
cell(ws3, r, 7, notes, bg=bg, wrap=True, fg="333333")
h(ws3, 20, 6)
# 8-week cardio progression
h(ws3, 21, 18)
merge_hdr(ws3, 21, 2, 7, "8-WEEK CARDIO PROGRESSION", bg=GREY_D)
h(ws3, 22, 22)
for j, ch in enumerate(["Weeks", "Mon", "Tue", "Wed", "Thu", "Fri + Sat"], 2):
hdr(ws3, 22, j, ch, bg=GREY_D)
prog_8wk = [
("Wk 1-2", "20 min LISS", "30 min LISS", "20 min incline walk", "45 min walk", "25 + 20 min LISS"),
("Wk 3-4", "20 min LISS", "35 min LISS", "25 min incline walk", "45 min walk", "25 + 20 min LISS"),
("Wk 5-6", "20 min MISS", "35 min LISS", "30 min incline walk", "45 elliptical","25 min MISS + 20 LISS"),
("Wk 7-8", "20 min MISS", "40 min LISS", "30 min incline walk", "45 elliptical","25 MISS + 20 LISS"),
("Wk 8+", "20 min HIIT","40 min LISS", "30 min incline walk", "45 elliptical","25 HIIT + 20 LISS"),
]
for i, row_data in enumerate(prog_8wk):
r = 23 + i
h(ws3, r, 24)
bgs = [LT_GREEN, LT_GREEN, LT_AMBER, LT_AMBER, LT_RED]
bg = bgs[i]
for j, val in enumerate(row_data, 2):
cell(ws3, r, j, val, bg=bg, halign="center" if j == 2 else "left",
bold=(j == 2), wrap=True)
h(ws3, 28, 6)
# Max HR note
h(ws3, 29, 20)
merge_hdr(ws3, 29, 2, 7,
"YOUR MAX HR ESTIMATE: 220 - 29 (age) = ~191 bpm. "
"Fat burning zone (60-70%): 115-134 bpm. Aerobic zone (70-80%): 134-153 bpm. "
"Get a cheap HR monitor watch β this is the single best investment for cardio.",
bg=LT_AMBER, fg=AMBER, sz=10, bold=False)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 4 β NUTRITION GUIDELINES
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws4 = wb.create_sheet("π₯ Nutrition")
ws4.sheet_view.showGridLines = False
for i, cw in enumerate([2,26,52,3], 1): w(ws4, i, cw)
h(ws4, 1, 6)
h(ws4, 2, 36)
merge_hdr(ws4, 2, 2, 3,
"π₯ NUTRITION GUIDELINES | Exercise without diet = 50% of results",
bg=NAVY, sz=14)
h(ws4, 3, 18)
merge_hdr(ws4, 3, 2, 3,
"You don't need a complex diet. These simple rules will accelerate your results.",
bg=BLUE, sz=10, bold=False)
h(ws4, 4, 6)
nutrition_sections = [
("CALORIE TARGETS", GREEN, [
("Maintenance calories (est.)", "~3,200-3,500 kcal/day at 174kg, moderately active"),
("Target for weight loss", "2,500-2,800 kcal/day (deficit of ~500-700 kcal)"),
("Expected loss at this deficit","~0.5-0.8 kg per week β realistic and sustainable"),
("Do NOT drop below", "2,200 kcal β muscle loss increases, energy crashes, training suffers"),
("Weigh yourself", "Once per week, same time (morning, after toilet, before food). Log it."),
]),
("PROTEIN β YOUR #1 PRIORITY", RED, [
("Daily protein target", "2.0-2.2g per kg of TARGET bodyweight (use 100kg target = 200-220g/day)"),
("Why this matters", "Preserves muscle while losing fat. Keeps you fuller. Higher thermic effect."),
("Best sources", "Chicken breast, eggs, Greek yoghurt, paneer, dal, fish, whey protein"),
("Protein per meal (roughly)", "Aim for 30-40g protein per main meal Γ 3 meals + snacks"),
("Supplement", "1-2 whey protein shakes/day if hitting target from food is difficult"),
]),
("CARBOHYDRATES", AMBER, [
("Carb timing", "Eat most carbs around your workout β pre and post training"),
("Best sources", "Oats, brown rice, sweet potato, roti (1-2), fruits, vegetables"),
("Reduce / avoid", "White bread, maida products, fried snacks, packaged biscuits"),
("Portion strategy", "Fill half your plate with vegetables, quarter protein, quarter carbs"),
("Pre-workout meal", "1-2 hrs before gym: rice/roti + protein (chicken/eggs). Don't train empty."),
]),
("FATS", BLUE, [
("Daily fat target", "60-80g/day. Fat is essential β don't eliminate it."),
("Best sources", "Almonds, walnuts, olive oil, ghee (small amount), avocado, eggs"),
("Avoid", "Trans fats, excessive deep-fried food, vanaspati/dalda"),
("Omega-3 specifically", "Fish or fish oil supplement β reduces inflammation from heavy training"),
]),
("HYDRATION β CRITICAL AT 174KG", TEAL, [
("Daily water target", "Minimum 4-5 litres/day. More on training days."),
("Why it matters", "At high body weight, kidney load is high. Water also aids fat metabolism."),
("During workout", "500ml-750ml during session. Don't wait to feel thirsty."),
("Easy habit", "1 large glass of water before every meal and before every gym session"),
("Electrolytes", "Add a pinch of salt + squeeze of lemon to 1-2 water bottles on heavy training days"),
]),
("MEAL TIMING & STRUCTURE", PURPLE, [
("Breakfast (pre-workout or morning)","Oats + milk + banana + 2 boiled eggs OR Greek yoghurt + fruit + protein shake"),
("Lunch (main meal)", "Rice/roti + dal/chicken/fish + vegetables + salad"),
("Pre-workout snack (if evening gym)","Banana + handful of almonds OR 1 slice brown bread + peanut butter"),
("Post-workout", "Protein shake within 30-45 min + a piece of fruit. Don't delay this."),
("Dinner", "Lighter than lunch. Protein + vegetables. Reduce carbs if not training next morning."),
("Avoid late night eating", "Try to finish eating 2-3 hrs before sleep. Reduces insulin resistance."),
]),
("WHAT TO TRACK", GREY_D, [
("Track protein daily", "Even rough tracking (use MyFitnessPal or Cronometer app) helps enormously"),
("Track body weight weekly", "Every Monday morning. Note in Session Log sheet."),
("Track measurements monthly", "Waist, hips, chest, thighs. The scale doesn't show muscle gain but measurements do."),
("Track energy levels", "Rate 1-10 daily. If consistently below 5, eat more or rest more."),
("Don't track obsessively", "Rough tracking is better than no tracking. Perfection is the enemy of progress."),
]),
]
row = 5
for sec_title, sec_color, items in nutrition_sections:
h(ws4, row, 22)
ws4.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
c = ws4.cell(row=row, column=2, value=f" {sec_title}")
c.fill = fill(sec_color); c.font = font(bold=True, size=11, color=WHITE)
c.alignment = align("left", "center")
c.border = thick_border(sec_color)
row += 1
sc_lite = {GREEN: LT_GREEN, RED: LT_RED, AMBER: LT_AMBER,
BLUE: LT_BLUE, TEAL: LT_TEAL, PURPLE: LT_PURPLE, GREY_D: GREY_L}
bg_l = sc_lite.get(sec_color, GREY_L)
for i, (lbl, detail) in enumerate(items):
h(ws4, row, 28)
bg = WHITE if i % 2 == 0 else bg_l
cell(ws4, r=row, col=2, value=lbl, bg=bg, bold=True)
cell(ws4, r=row, col=3, value=detail, bg=bg, wrap=True)
row += 1
row += 1
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 5 β SESSION LOG (tracker)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws5 = wb.create_sheet("π Session Log")
ws5.sheet_view.showGridLines = False
for i, cw in enumerate([2,10,8,14,10,10,10,10,14,14,3], 1): w(ws5, i, cw)
h(ws5, 1, 6)
h(ws5, 2, 36)
merge_hdr(ws5, 2, 2, 10,
"π SESSION LOG | Track every workout. Progress only happens when you measure it.",
bg=NAVY, sz=14)
h(ws5, 3, 18)
merge_hdr(ws5, 3, 2, 10,
"Fill in after every session. Blue cells = enter data. The pattern over weeks tells you everything.",
bg=BLUE, sz=10, bold=False)
h(ws5, 4, 6)
# Weekly weight box
h(ws5, 5, 18)
merge_hdr(ws5, 5, 2, 5, "WEEKLY WEIGHT (Monday morning, before food)", bg=GREEN)
merge_hdr(ws5, 5, 6, 10, "Starting: 174 kg | Target: 98 kg | To lose: 76 kg", bg=LT_GREEN, fg=GREEN, sz=10)
h(ws5, 6, 20)
for j, wh in enumerate(["Week #", "Date", "Weight (kg)", "Change (kg)", "Running Total Lost"], 2):
hdr(ws5, 6, j, wh, bg=GREY_D)
for i in range(24): # 24 weeks of tracking
r = 7 + i
h(ws5, r, 20)
bg = LT_GREEN if i == 0 else (WHITE if i % 2 == 0 else GREY_L)
cell(ws5, r, 2, f"Week {i+1}", bg=bg, bold=True, halign="center")
# Date (empty for user)
c_date = ws5.cell(row=r, column=3, value=None)
c_date.fill = fill("FFFDE7"); c_date.font = font(color="0000FF", size=10)
c_date.alignment = align("center"); c_date.border = border()
# Weight (empty for user)
c_wt = ws5.cell(row=r, column=4, value=174 if i == 0 else None)
c_wt.fill = fill("FFFDE7"); c_wt.font = font(color="0000FF", size=11, bold=True)
c_wt.alignment = align("center"); c_wt.border = border("medium", BLUE)
c_wt.number_format = "0.0"
# Change
if i > 0:
c_chg = ws5.cell(row=r, column=5, value=f"=IF(D{r}=\"\",\"\",D{r}-D{r-1})")
c_chg.fill = fill(bg); c_chg.font = font(size=10)
c_chg.alignment = align("center"); c_chg.border = border()
c_chg.number_format = "+0.0;-0.0;0.0"
else:
cell(ws5, r, 5, "β", bg=bg, halign="center")
# Running total
c_tot = ws5.cell(row=r, column=6, value=f"=IF(D{r}=\"\",\"\",174-D{r})")
c_tot.fill = fill(bg); c_tot.font = font(bold=True, color=GREEN, size=10)
c_tot.alignment = align("center"); c_tot.border = border()
c_tot.number_format = "0.0"
ws5.merge_cells(start_row=r, start_column=6, end_row=r, end_column=10)
h(ws5, 31, 6)
# Session log headers
h(ws5, 32, 18)
merge_hdr(ws5, 32, 2, 10, "WORKOUT SESSION LOG", bg=GREY_D)
h(ws5, 33, 22)
session_hdrs = ["Date", "Day Type", "Exercises Done", "Avg Weight", "Total Sets", "Cardio (min)", "HR Avg", "Energy (1-10)", "Notes"]
for j, sh in enumerate(session_hdrs, 2):
hdr(ws5, 33, j, sh, bg=GREY_D, sz=9)
# Pre-fill recent sessions
recent = [
("09-Jul", "Push/Cardio", "Treadmill + random strength", "", "", "45", "", "?", "First day back"),
("10-Jul", "β", "Session 2", "", "", "45", "", "?", ""),
]
for i, sess in enumerate(recent):
r = 34 + i
h(ws5, r, 20)
bg = LT_BLUE if i == 0 else WHITE
for j, val in enumerate(sess, 2):
c = ws5.cell(row=r, column=j, value=val)
c.fill = fill("FFFDE7" if j in [4,5,6,7,8,9] else bg)
c.font = font(color="0000FF" if j in [4,5,6,7,8,9] else BLACK, size=10)
c.alignment = align("center" if j > 4 else "left", "center", True)
c.border = border()
# Add 50 blank log rows
for i in range(50):
r = 34 + len(recent) + i
h(ws5, r, 20)
bg = WHITE if i % 2 == 0 else GREY_L
for j in range(2, 11):
c = ws5.cell(row=r, column=j)
c.fill = fill("FFFDE7" if j in [4,5,6,7,8,9] else bg)
c.border = border()
if j in [4,5,6,7,8]:
c.font = font(color="0000FF", size=10)
c.alignment = align("center" if j > 4 else "left", "center")
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 6 β MILESTONES & MOTIVATION
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws6 = wb.create_sheet("π Milestones")
ws6.sheet_view.showGridLines = False
for i, cw in enumerate([2,18,14,20,18,3], 1): w(ws6, i, cw)
h(ws6, 1, 6)
h(ws6, 2, 36)
merge_hdr(ws6, 2, 2, 5,
"π MILESTONES & GOAL TRACKER | From 174 kg back to 98 kg",
bg=NAVY, sz=14)
h(ws6, 3, 18)
merge_hdr(ws6, 3, 2, 5,
"You did it before β 155 kg β 98 kg. You know the way. "
"This time, track every milestone. Each one matters.",
bg=BLUE, sz=10, bold=False)
h(ws6, 4, 6)
# Milestone table
h(ws6, 5, 18)
merge_hdr(ws6, 5, 2, 5, "WEIGHT MILESTONES", bg=GREY_D)
h(ws6, 6, 22)
for j, mh in enumerate(["Milestone", "Target Weight", "Est. Timeline", "Achieved Date", "Notes"], 2):
hdr(ws6, 6, j, mh, bg=GREY_D)
milestones = [
("First 5 kg lost", "169 kg", "4-5 weeks from start", "", "Habit formation milestone"),
("First 10 kg lost", "164 kg", "8-10 weeks", "", "Clothes start to feel looser"),
("First 15 kg lost", "159 kg", "12-15 weeks", "", "Cardio noticeably easier"),
("Back to university entry","155 kg", "4-5 months", "", "You were this weight starting uni"),
("20% of journey done", "151 kg", "5-6 months", "", "Real body recomposition visible"),
("Halfway to goal", "136 kg", "8-10 months", "", "MAJOR milestone β celebrate well"),
("University best ever", "120 kg", "12-14 months", "", "Approaching athletic range"),
("Under 110 kg", "109 kg", "15-18 months", "", "Significant health improvement"),
("GOAL: 98 kg", "98 kg", "18-24 months", "", "You've been here before. You'll be here again."),
]
mile_bgs = [LT_BLUE,LT_BLUE,LT_TEAL,LT_TEAL,LT_GREEN,LT_GREEN,LT_AMBER,LT_AMBER,LT_RED]
for i, (mile, wt, timeline, date, notes) in enumerate(milestones):
r = 7 + i
h(ws6, r, 24)
bg = mile_bgs[i]
cell(ws6, r, 2, mile, bg=bg, bold=(i==8), fg=RED if i==8 else NAVY)
cell(ws6, r, 3, wt, bg=bg, bold=True, halign="center", fg=GREEN)
cell(ws6, r, 4, timeline, bg=bg, halign="center")
c_date = ws6.cell(row=r, column=5, value=date)
c_date.fill = fill("FFFDE7"); c_date.font = font(color="0000FF", size=10)
c_date.alignment = align("center"); c_date.border = border("medium", BLUE)
cell(ws6, r, 6, notes, bg=bg, fg="555555", italic=True)
h(ws6, 16, 6)
# Non-scale victories
h(ws6, 17, 18)
merge_hdr(ws6, 17, 2, 5, "NON-SCALE VICTORIES β TRACK THESE TOO", bg=TEAL)
nscv = [
("Cardio endurance", "Can complete 45 min treadmill without stopping"),
("Strength", "Added weight to any exercise since starting"),
("Sleep quality", "Waking up feeling more rested"),
("Energy levels", "Afternoon energy better than before"),
("Resting HR", "HR monitor showing lower resting HR over weeks"),
("Clothes fit", "Jeans/shirt looser than when you started"),
("Confidence", "Walking into gym feels normal, not daunting"),
("Consistency", "5 sessions in a row without skipping"),
("PLAB studies", "Gym + study both happening simultaneously β discipline is discipline"),
]
for i, (nsc, detail) in enumerate(nscv):
r = 18 + i
h(ws6, r, 20)
bg = WHITE if i % 2 == 0 else LT_TEAL
cell(ws6, r, 2, "β", bg=bg, bold=True, sz=14, halign="center")
cell(ws6, r, 3, nsc, bg=bg, bold=True)
ws6.merge_cells(start_row=r, start_column=4, end_row=r, end_column=6)
cell(ws6, r, 4, detail, bg=bg, fg="333333")
h(ws6, 27, 6)
# Motivational note
h(ws6, 28, 36)
merge_hdr(ws6, 28, 2, 5,
"You went from 155 kg β 98 kg once before. That is 57 kg. "
"Your body knows how. Your muscles remember. "
"You are now 3 weeks in and already moving. That is the hardest part β and you have already done it. "
"The only question now is consistency. Trust the process.",
bg=LT_GREEN, fg=GREEN, sz=10, bold=False)
# ββ SAVE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
out = "/tmp/workspace/plab-schedule/Gym_Workout_Plan_2026.xlsx"
wb.save(out)
print(f"Saved: {out}")
cd /tmp/workspace/plab-schedule && python3 build_gym_plan.py 2>&1
cd /tmp/workspace/plab-schedule && sed -i 's/def cell(ws, row, col, value=None, bg=WHITE, fg=BLACK, sz=10,\n bold=False, halign="left", wrap=True, border_col="CCCCCC"):/def cell(ws, row, col, value=None, bg=WHITE, fg=BLACK, sz=10, bold=False, halign="left", wrap=True, border_col="CCCCCC", italic=False):/' build_gym_plan.py 2>&1 | head -5 grep -n "def cell" build_gym_plan.py
~/plab-schedule/build_gym_plan.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/plab-schedule/build_gym_plan.py'
cd /tmp/workspace/plab-schedule && python3 build_gym_plan.py 2>&1
ls -lh /tmp/workspace/plab-schedule/Gym_Workout_Plan_2026.xlsx
Gym Workout Plan 2026
Spreadsheet Β· XLSX
Create a nutrition meal plan template for Indian diet (dal, rice, chicken, vegetables)
~/plab-schedule/build_meal_plan.py
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
wb = Workbook()
# ββ COLOURS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
NAVY = "1B3A6B"
BLUE = "2E86C1"
LT_BLUE = "D6EAF8"
GREEN = "1E8449"
LT_GRN = "D5F5E3"
AMBER = "D4AC0D"
LT_AMB = "FEF9E7"
RED = "C0392B"
LT_RED = "FDEDEC"
PURPLE = "6C3483"
LT_PUR = "F4ECF7"
TEAL = "117A65"
LT_TEAL = "D1F2EB"
ORANGE = "CA6F1E"
LT_ORG = "FDEBD0"
GREY_D = "2C3E50"
GREY_L = "F2F3F4"
WHITE = "FFFFFF"
INPUT = "FFFDE7"
BLACK = "000000"
# Meal colours
MEAL_CLR = {
"Breakfast": ("1A5276", "D6EAF8"),
"Mid-Morning": ("117A65", "D1F2EB"),
"Lunch": ("1E8449", "D5F5E3"),
"Pre-Workout": ("7D6608", "FEF9E7"),
"Post-Workout": ("922B21", "FDEDEC"),
"Dinner": ("4A235A", "F4ECF7"),
"Bedtime Snack": ("2C3E50", "EAECEE"),
}
def fill(c): return PatternFill("solid", fgColor=c)
def font(bold=False, size=10, color=BLACK, italic=False, name="Calibri"):
return Font(bold=bold, size=size, color=color, italic=italic, name=name)
def border(style="thin", color="CCCCCC"):
s = Side(style=style, color=color)
return Border(left=s, right=s, top=s, bottom=s)
def med_border(color="888888"):
t = Side(style="medium", color=color)
return Border(left=t, right=t, top=t, bottom=t)
def align(h="left", v="center", wrap=False):
return Alignment(horizontal=h, vertical=v, wrap_text=wrap)
def cw(ws, col, width): ws.column_dimensions[get_column_letter(col)].width = width
def rh(ws, row, height): ws.row_dimensions[row].height = height
def hdr(ws, r, c, text, bg=NAVY, fg=WHITE, sz=10, bold=True, ha="center", wrap=True):
cl = ws.cell(row=r, column=c, value=text)
cl.fill = fill(bg); cl.font = font(bold=bold, size=sz, color=fg)
cl.alignment = align(ha, "center", wrap)
cl.border = border("thin", "999999")
return cl
def cell(ws, r, c, val=None, bg=WHITE, fg=BLACK, sz=10, bold=False,
ha="left", wrap=True, bc="CCCCCC", italic=False, num_fmt=None):
cl = ws.cell(row=r, column=c, value=val)
cl.fill = fill(bg); cl.font = font(bold=bold, size=sz, color=fg, italic=italic)
cl.alignment = align(ha, "center", wrap)
cl.border = border("thin", bc)
if num_fmt: cl.number_format = num_fmt
return cl
def mhdr(ws, r, cs, ce, text, bg=NAVY, fg=WHITE, sz=12, bold=True):
ws.merge_cells(start_row=r, start_column=cs, end_row=r, end_column=ce)
cl = ws.cell(row=r, column=cs, value=text)
cl.fill = fill(bg); cl.font = font(bold=bold, size=sz, color=fg)
cl.alignment = align("center", "center")
cl.border = border("medium", "888888")
return cl
def sec_hdr(ws, r, cs, ce, text, bg, fg=WHITE, sz=11):
ws.merge_cells(start_row=r, start_column=cs, end_row=r, end_column=ce)
cl = ws.cell(row=r, column=cs, value=f" {text}")
cl.fill = fill(bg); cl.font = font(bold=True, size=sz, color=fg)
cl.alignment = align("left", "center")
cl.border = border("medium", bg)
return cl
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 1 β OVERVIEW & MACRO TARGETS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws1 = wb.active
ws1.title = "π Overview & Macros"
ws1.sheet_view.showGridLines = False
for i, w_ in enumerate([2,22,12,12,12,12,20,3], 1): cw(ws1, i, w_)
rh(ws1, 1, 6)
rh(ws1, 2, 42)
mhdr(ws1, 2, 2, 7,
"π INDIAN DIET MEAL PLAN | Weight Loss & Muscle Preservation",
bg=NAVY, sz=16)
rh(ws1, 3, 20)
mhdr(ws1, 3, 2, 7,
"Target: 174kg β 98kg | Daily Calories: 2,500β2,800 kcal | Protein: 200β220g/day | 5 meals/day",
bg=BLUE, sz=10, bold=False)
rh(ws1, 4, 6)
# Daily macro targets
rh(ws1, 5, 18)
mhdr(ws1, 5, 2, 7, "DAILY MACRO TARGETS", bg=GREY_D)
rh(ws1, 6, 22)
for j, h_ in enumerate(["Nutrient","Target","Why This Amount","Best Indian Sources","Avoid","Notes"], 2):
hdr(ws1, 6, j, h_, bg=GREY_D)
macros = [
("Calories", "2,500β2,800 kcal",
"~500-700 kcal deficit from maintenance (3,200 kcal). Sustainable fat loss.",
"Portion control of rice, roti, dal, chicken",
"Fried snacks, sweets, packaged food, cold drinks",
"Weigh food for first 2 weeks to calibrate eye."),
("Protein", "200β220g/day",
"2.2g per kg target body weight (100kg). Preserves muscle while losing fat.",
"Chicken breast, eggs, dal, paneer, Greek yoghurt, whey",
"Replacing protein meals with carb-heavy snacks",
"Most important macro. Hit this EVERY day."),
("Carbs", "250β300g/day",
"Moderate carbs. Enough for gym energy, not so much it blocks fat loss.",
"Brown rice, oats, roti (1-2 per meal), fruits, vegetables",
"White bread, maida, mithai, excess rice",
"Time carbs around workouts (pre/post training)."),
("Fats", "70β85g/day",
"Essential for hormones, joint health, vitamin absorption.",
"Ghee (small amount), almonds, walnuts, eggs, mustard oil",
"Trans fats, vanaspati, excess coconut oil",
"Don't eliminate fat β it's necessary. Just control quantity."),
("Fibre", "35β40g/day",
"Keeps you full, supports gut health, slows carb absorption.",
"Dal, vegetables, salad, whole fruits, oats",
"Processed foods, fruit juices (eat the fruit, don't drink it)",
"Add a large salad to lunch and dinner daily."),
("Water", "4β5 litres/day",
"Kidney health at high body weight. Fat metabolism. Reduces false hunger.",
"Plain water. Chaas (buttermilk). Green tea.",
"Soft drinks, packaged juices, excess chai with sugar",
"1 large glass before every meal and before gym."),
]
macro_bgs = [LT_RED, LT_BLUE, LT_AMB, LT_GRN, LT_TEAL, LT_PUR]
for i, (nut, tgt, why, sources, avoid, notes) in enumerate(macros):
r = 7 + i
rh(ws1, r, 20)
bg = macro_bgs[i]
cell(ws1, r, 2, nut, bg=bg, bold=True, ha="center")
cell(ws1, r, 3, tgt, bg=bg, bold=True, ha="center", fg=NAVY)
cell(ws1, r, 4, why, bg=bg, wrap=True)
cell(ws1, r, 5, sources, bg=bg, wrap=True, fg=GREEN)
cell(ws1, r, 6, avoid, bg=bg, wrap=True, fg=RED)
cell(ws1, r, 7, notes, bg=bg, wrap=True, italic=True, fg="444444")
rh(ws1, 13, 6)
# Meal timing
rh(ws1, 14, 18)
mhdr(ws1, 14, 2, 7, "MEAL TIMING & STRUCTURE (5 meals/day)", bg=GREY_D)
rh(ws1, 15, 22)
for j, h_ in enumerate(["Meal", "Ideal Time", "Kcal Target", "Protein Target", "Purpose", "Notes"], 2):
hdr(ws1, 15, j, h_, bg=GREY_D)
meals_timing = [
("Breakfast", "7:00β8:00 AM", "450β550 kcal", "35β45g",
"Break overnight fast. Fuel morning energy and gym (if morning session).",
"Largest meal for morning gym-goers. Include oats/eggs/yoghurt."),
("Mid-Morning Snack","10:00β11:00 AM","150β200 kcal", "15β20g",
"Prevent mid-morning hunger. Maintain blood sugar.",
"Fruits + nuts OR chaas. Keep it light."),
("Lunch", "1:00β2:00 PM", "650β750 kcal", "50β60g",
"Largest meal of the day for evening gym-goers. Main carb meal.",
"Rice + dal + chicken + vegetables + salad. This is your fuel."),
("Pre-Workout Snack","4:30β5:30 PM", "200β250 kcal", "15β20g",
"Fuel for gym session. Quick-release energy.",
"Banana + protein shake OR roti + egg. 45β60 min before gym."),
("Post-Workout", "Within 30β45 min","250β300 kcal","35β45g",
"Muscle recovery. Glycogen replenishment. Critical window.",
"Whey protein shake + banana. Don't delay this."),
("Dinner", "8:30β9:30 PM", "550β650 kcal", "45β55g",
"Recovery meal. Lighter carbs than lunch. Protein-heavy.",
"Chicken/dal/paneer + vegetables + 1 roti OR small rice. Less carb than lunch."),
("Bedtime (optional)","10:30β11:00 PM","150β200 kcal","15β20g",
"Slow-release protein during overnight fast. Prevents muscle breakdown.",
"Greek yoghurt OR paneer + small handful of almonds."),
]
meal_bgs_t = [LT_BLUE, LT_TEAL, LT_GRN, LT_AMB, LT_RED, LT_PUR, GREY_L]
for i, (meal, time, kcal, prot, purpose, notes) in enumerate(meals_timing):
r = 16 + i
rh(ws1, r, 28)
bg = meal_bgs_t[i]
cell(ws1, r, 2, meal, bg=bg, bold=True, ha="center")
cell(ws1, r, 3, time, bg=bg, ha="center")
cell(ws1, r, 4, kcal, bg=bg, bold=True, ha="center", fg=NAVY)
cell(ws1, r, 5, prot, bg=bg, bold=True, ha="center", fg=GREEN)
cell(ws1, r, 6, purpose, bg=bg, wrap=True)
cell(ws1, r, 7, notes, bg=bg, wrap=True, italic=True, fg="333333")
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 2 β 7-DAY MEAL PLAN
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws2 = wb.create_sheet("π 7-Day Meal Plan")
ws2.sheet_view.showGridLines = False
for i, w_ in enumerate([2,14,28,10,10,10,22,3], 1): cw(ws2, i, w_)
rh(ws2, 1, 6)
rh(ws2, 2, 36)
mhdr(ws2, 2, 2, 7,
"π 7-DAY INDIAN MEAL PLAN | ~2,600 kcal/day | ~210g Protein/day",
bg=NAVY, sz=14)
rh(ws2, 3, 20)
mhdr(ws2, 3, 2, 7,
"All quantities given for one serving. Adjust portions up/down to hit your calorie target. "
"Protein shake = 1 scoop whey (25g protein, 120 kcal) in water.",
bg=BLUE, sz=10, bold=False)
rh(ws2, 4, 6)
# 7-day plan data: (meal_type, food_item, qty, kcal, protein, carbs, notes)
day_plans = {
"MONDAY": [
("Breakfast", "Steel-cut oats / rolled oats", "80g dry", "290", "10g", "52g", "Cook in water or milk"),
("Breakfast", "Full-fat milk", "200ml", "124", "6g", "10g", "Or use in oats"),
("Breakfast", "Boiled eggs (whole)", "3 eggs", "234", "18g", "1g", "Best breakfast protein"),
("Breakfast", "Banana", "1 medium", "90", "1g", "23g", "Pre-gym energy"),
("Mid-Morning", "Greek yoghurt (plain)", "150g", "130", "15g", "6g", "Or chaas (buttermilk)"),
("Mid-Morning", "Mixed nuts (almonds + walnuts)", "25g", "155", "4g", "4g", "Small handful"),
("Lunch", "Boiled rice / brown rice", "150g cooked","195","4g", "44g", "Use smaller katori"),
("Lunch", "Dal (moong or masoor, cooked)", "1 katori (150ml)","130","9g","20g","Use less oil"),
("Lunch", "Chicken breast (grilled/tandoor)", "200g raw", "220", "46g", "0g", "Remove skin. Grill/bake/tandoor only."),
("Lunch", "Mixed vegetable sabzi (dry)", "1 katori", "80", "3g", "12g", "Use 1 tsp oil only"),
("Lunch", "Cucumber + tomato salad", "1 plate", "40", "2g", "8g", "No dressing β lemon + salt only"),
("Pre-Workout", "Banana", "1 large", "105", "1g", "27g", "45-60 min before gym"),
("Pre-Workout", "Whey protein shake", "1 scoop", "120", "25g", "3g", "In water, pre or post gym"),
("Post-Workout", "Whey protein shake", "1 scoop", "120", "25g", "3g", "Within 30 min of finishing"),
("Post-Workout", "Banana (or apple)", "1", "90", "1g", "23g", "Replenish glycogen"),
("Dinner", "Roti (whole wheat)", "2 rotis", "170", "5g", "35g", "Medium size. No butter."),
("Dinner", "Grilled chicken or egg bhurji", "150g / 3 eggs","165","35g","2g", "High protein, lower carb"),
("Dinner", "Dal tadka", "1 katori", "130", "9g", "20g", "Moderate oil"),
("Dinner", "Stir-fried vegetables", "1 katori", "60", "3g", "10g", "Spinach, broccoli, beans"),
("Bedtime Snack", "Greek yoghurt or paneer", "100g", "100", "11g", "4g", "Slow protein overnight"),
],
"TUESDAY": [
("Breakfast", "Besan chilla (gram flour pancakes)", "3 medium", "300", "18g", "35g", "Add onion, tomato, green chilli"),
("Breakfast", "Boiled eggs", "2 eggs", "156", "12g", "1g", "Side protein boost"),
("Breakfast", "Green tea", "1 cup", "2", "0g", "0g", "No sugar"),
("Mid-Morning", "Roasted chana (chickpeas)", "40g", "160", "8g", "20g", "High protein snack"),
("Mid-Morning", "Apple or guava", "1", "80", "0.5g", "20g", "Fibre + vitamins"),
("Lunch", "Brown rice or 2 rotis", "130g / 2 roti","195","4g", "40g", "Choose one"),
("Lunch", "Rajma (kidney beans) curry", "1 large katori","220","12g","32g", "Excellent protein+fibre"),
("Lunch", "Paneer tikka / baked paneer", "100g", "265", "18g", "3g", "Bake or grill β don't fry"),
("Lunch", "Salad (cucumber, carrot, onion)", "1 large", "50", "2g", "10g", "Eat before main meal"),
("Pre-Workout", "Dates (khajoor)", "4-5", "120", "1g", "30g", "Quick energy, natural sugar"),
("Pre-Workout", "Protein shake", "1 scoop", "120", "25g", "3g", ""),
("Post-Workout", "Protein shake + banana", "1 + 1", "210", "26g", "26g", ""),
("Dinner", "Egg curry (2 eggs in tomato gravy)", "2 eggs", "220", "14g", "8g", "Less oil. More tomato."),
("Dinner", "Dal", "1 katori", "130", "9g", "20g", ""),
("Dinner", "Roti", "2", "170", "5g", "35g", ""),
("Dinner", "Palak (spinach) sabzi", "1 katori", "80", "4g", "8g", "Iron + fibre rich"),
("Bedtime Snack", "Almonds + small glass milk", "15g + 150ml","185","9g", "10g", ""),
],
"WEDNESDAY": [
("Breakfast", "Oats porridge with chia seeds", "80g + 10g", "340", "12g", "54g", "Add chia seeds for omega-3"),
("Breakfast", "Boiled eggs", "3", "234", "18g", "1g", ""),
("Breakfast", "Papaya or orange", "1 cup", "55", "1g", "14g", "Vitamin C post-eggs"),
("Mid-Morning", "Protein shake", "1 scoop", "120", "25g", "3g", "Mid-morning protein top-up"),
("Mid-Morning", "Handful of peanuts", "30g", "170", "8g", "5g", "Cheap, high protein"),
("Lunch", "Rice + chicken biryani (homemade, light)", "1 plate", "450", "38g", "52g", "Use less oil. No fried onions."),
("Lunch", "Raita", "1 katori", "80", "5g", "8g", "Curd + cucumber + jeera"),
("Lunch", "Salad", "1 plate", "40", "2g", "8g", ""),
("Pre-Workout", "Banana", "1", "90", "1g", "23g", ""),
("Pre-Workout", "Handful of almonds", "15g", "87", "3g", "3g", ""),
("Post-Workout", "Protein shake + banana", "1 + 1", "210", "26g", "26g", ""),
("Dinner", "Grilled fish (surmai/rohu/pomfret)", "200g", "220", "40g", "0g", "Best dinner protein. Bake/grill."),
("Dinner", "Dal", "1 katori", "130", "9g", "20g", ""),
("Dinner", "1 roti", "1", "85", "2.5g", "17g", "Reduce roti at dinner"),
("Dinner", "Mixed sabzi", "1 katori", "80", "3g", "12g", ""),
("Bedtime Snack", "Paneer (raw)", "80g", "200", "15g", "3g", "Casein protein β slow release"),
],
"THURSDAY": [
("Breakfast", "Moong dal chilla (2-3 medium)", "3 chillas", "280", "20g", "32g", "Best protein breakfast. Add veggies inside."),
("Breakfast", "Curd (plain)", "150g", "100", "8g", "7g", "Side with chilla"),
("Breakfast", "Green tea or black coffee (no sugar)", "1 cup", "5", "0g", "1g", ""),
("Mid-Morning", "Chaas (buttermilk with jeera)", "300ml", "60", "3g", "6g", "Excellent digestion + hydration"),
("Mid-Morning", "Roasted makhana (fox nuts)", "30g", "100", "4g", "18g", "Low calorie, filling snack"),
("Lunch", "2 rotis (whole wheat)", "2", "170", "5g", "35g", ""),
("Lunch", "Chicken curry (home cooked, less oil)", "200g chicken","280","42g", "8g", "Curry OK but limit oil to 1 tbsp"),
("Lunch", "Chana dal or toor dal", "1 katori", "150", "10g", "22g", ""),
("Lunch", "Salad + curd", "1 each", "110", "7g", "14g", ""),
("Pre-Workout", "Banana + protein shake", "1 + 1 scoop","210","26g", "26g", ""),
("Post-Workout", "Protein shake", "1 scoop", "120", "25g", "3g", ""),
("Dinner", "Paneer bhurji (scrambled paneer, low oil)", "150g paneer","350","25g", "8g", "Pan with 1 tsp oil, onion, tomato, spices"),
("Dinner", "2 rotis", "2", "170", "5g", "35g", ""),
("Dinner", "Palak or methi sabzi", "1 katori", "80", "4g", "8g", "Iron-rich leafy greens"),
("Bedtime Snack", "Greek yoghurt + 5 walnuts", "150g + 5", "200", "16g", "8g", ""),
],
"FRIDAY": [
("Breakfast", "Egg white omelette (4 whites + 1 whole)", "4W+1 whole", "200", "25g", "2g", "Most protein per calorie breakfast"),
("Breakfast", "2 slices whole wheat toast", "2 slices", "160", "6g", "30g", ""),
("Breakfast", "Banana", "1", "90", "1g", "23g", ""),
("Mid-Morning", "Protein shake", "1 scoop", "120", "25g", "3g", ""),
("Mid-Morning", "Apple", "1", "80", "0.5g", "21g", "Fibre keeps you full"),
("Lunch", "Brown rice", "150g cooked","195","4g", "44g", ""),
("Lunch", "Dal makhani (moderate butter)", "1 katori", "200", "10g", "26g", "Limit butter. Rich in protein."),
("Lunch", "Tandoori chicken leg/breast", "200g", "280", "40g", "2g", "Best restaurant order β minimal oil"),
("Lunch", "Salad (large)", "1 large", "50", "2g", "10g", ""),
("Pre-Workout", "Banana + almonds", "1 + 20g", "202", "5g", "30g", ""),
("Post-Workout", "Protein shake + banana", "1 + 1", "210", "26g", "26g", ""),
("Dinner", "Egg curry (3 eggs)", "3 eggs", "330", "21g", "12g", ""),
("Dinner", "2 rotis", "2", "170", "5g", "35g", ""),
("Dinner", "Mixed dal", "1 katori", "130", "9g", "20g", ""),
("Dinner", "Sabzi (any)", "1 katori", "80", "3g", "12g", ""),
("Bedtime Snack", "Paneer / Greek yoghurt", "100g", "130", "13g", "4g", ""),
],
"SATURDAY": [
("Breakfast", "Poha (flattened rice, vegetable poha)", "1 plate", "300", "8g", "52g", "Add peanuts for protein. Less oil."),
("Breakfast", "Protein shake", "1 scoop", "120", "25g", "3g", "Compensate for lower poha protein"),
("Breakfast", "Boiled egg", "2", "156", "12g", "1g", ""),
("Mid-Morning", "Coconut water", "1 large", "60", "1g", "14g", "Natural electrolytes"),
("Mid-Morning", "Mixed nuts", "25g", "155", "4g", "4g", ""),
("Lunch", "Chicken rice (pulao, home cooked)", "1 plate", "480", "40g", "50g", "Saturday treat meal β still clean"),
("Lunch", "Dal", "1 katori", "130", "9g", "20g", ""),
("Lunch", "Raita", "1 katori", "80", "5g", "8g", ""),
("Lunch", "Salad", "1 plate", "50", "2g", "10g", ""),
("Pre-Workout", "Banana + protein shake", "1 + 1", "210", "26g", "26g", ""),
("Post-Workout", "Protein shake", "1 scoop", "120", "25g", "3g", ""),
("Dinner", "Grilled chicken or fish", "200g", "240", "44g", "0g", "Light dinner β you already had big lunch"),
("Dinner", "1β2 rotis", "1-2", "85-170","3-5g","17-35g","Just 1 roti if lunch was large"),
("Dinner", "Dal / sabzi", "1 katori each","200","11g", "28g", ""),
("Bedtime Snack", "Dahi (curd) with jeera", "200g", "120", "8g", "10g", "Light and easy to digest"),
],
"SUNDAY": [
("Breakfast", "Masala omelette (3 eggs)", "3 eggs", "280", "20g", "4g", "Onion, tomato, green chilli, coriander"),
("Breakfast", "2 rotis or 2 whole wheat toast", "2", "170", "5g", "35g", ""),
("Breakfast", "Chai (1 cup, less sugar, low fat milk)", "1 cup", "60", "2g", "8g", "Limit to 1 cup"),
("Mid-Morning", "Fruit bowl (papaya + banana + apple)", "1 bowl", "180", "2g", "44g", "Vitamins + fibre + natural sugar"),
("Mid-Morning", "Greek yoghurt", "150g", "130", "15g", "6g", ""),
("Lunch", "Mutton / lamb curry (lean cuts)", "150g cooked","350","32g", "6g", "Sunday special β lean cuts, less oil"),
("Lunch", "Rice", "150g cooked","195","4g", "44g", ""),
("Lunch", "Dal", "1 katori", "130", "9g", "20g", ""),
("Lunch", "Papad (1, roasted not fried)", "1", "35", "1g", "6g", "Roasted OK. Fried = avoid."),
("Lunch", "Salad + raita", "1 each", "120", "7g", "16g", ""),
("Pre-Workout", "Protein shake + banana", "1 + 1", "210", "26g", "26g", "Sunday gym session"),
("Post-Workout", "Protein shake", "1 scoop", "120", "25g", "3g", ""),
("Dinner", "Chicken breast (grilled)", "200g", "220", "46g", "0g", "Light dinner after Sunday lunch"),
("Dinner", "Dal or rajma", "1 katori", "150", "10g", "22g", ""),
("Dinner", "1 roti + sabzi", "1 + 1k", "165", "5g", "29g", ""),
("Bedtime Snack", "Paneer + almonds", "80g + 15g", "287", "18g", "4g", "Sunday recovery protein"),
],
}
day_colors = {
"MONDAY": (LT_BLUE, BLUE, "1A5276"),
"TUESDAY": (LT_GRN, GREEN, "1E8449"),
"WEDNESDAY": (LT_AMB, AMBER, "7D6608"),
"THURSDAY": (LT_TEAL, TEAL, TEAL),
"FRIDAY": (LT_ORG, ORANGE, ORANGE),
"SATURDAY": (LT_PUR, PURPLE, PURPLE),
"SUNDAY": (LT_RED, RED, RED),
}
row = 5
for day, meals in day_plans.items():
bg_lite, bg_dark, fg_dark = day_colors[day]
# Day header
rh(ws2, row, 26)
ws2.merge_cells(start_row=row, start_column=2, end_row=row, end_column=7)
c = ws2.cell(row=row, column=2, value=f" π
{day}")
c.fill = fill(bg_dark); c.font = font(bold=True, size=12, color=WHITE)
c.alignment = align("left", "center")
c.border = med_border(bg_dark)
row += 1
# Column headers
rh(ws2, row, 20)
for j, ch in enumerate(["Meal", "Food Item", "Quantity", "Kcal", "Protein", "Notes"], 2):
hdr(ws2, row, j, ch, bg=fg_dark)
row += 1
# Meal rows
prev_meal = ""
for i, (meal, food, qty, kcal, prot, carbs, notes) in enumerate(meals):
rh(ws2, row, 24)
m_dark, m_lite = MEAL_CLR[meal]
bg = m_lite if meal == prev_meal or meal != prev_meal else m_lite
# Alternate shade within same meal
bg = m_lite
# Meal label (only on first item of each meal)
if meal != prev_meal:
ws2.cell(row=row, column=2, value=meal).fill = fill(m_dark)
ws2.cell(row=row, column=2).font = font(bold=True, size=9, color=WHITE)
ws2.cell(row=row, column=2).alignment = align("center", "center")
ws2.cell(row=row, column=2).border = border("medium", m_dark)
prev_meal = meal
else:
ws2.cell(row=row, column=2, value="").fill = fill(m_lite)
ws2.cell(row=row, column=2).border = border("thin", "CCCCCC")
cell(ws2, row, 3, food, bg=bg, bold=True)
cell(ws2, row, 4, qty, bg=bg, ha="center")
cell(ws2, row, 5, int(kcal), bg=bg, ha="center", bold=True, fg=NAVY)
cell(ws2, row, 6, prot, bg=bg, ha="center", bold=True, fg=GREEN)
cell(ws2, row, 7, notes, bg=bg, italic=True, fg="444444")
row += 1
# Day total
rh(ws2, row, 20)
day_kcal = sum(int(m[3]) for m in meals)
day_prot = sum(float(m[4].replace("g","").split("/")[0].split("-")[0]) for m in meals)
ws2.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
c_tot = ws2.cell(row=row, column=2, value=f" DAILY TOTAL")
c_tot.fill = fill(bg_dark); c_tot.font = font(bold=True, size=10, color=WHITE)
c_tot.alignment = align("left", "center"); c_tot.border = med_border(bg_dark)
cell(ws2, row, 4, "", bg=bg_dark)
cell(ws2, row, 5, day_kcal, bg=bg_dark, bold=True, fg=WHITE, ha="center")
ws2.cell(row=row, column=5).font = font(bold=True, size=11, color=WHITE)
cell(ws2, row, 6, f"~{int(day_prot)}g", bg=bg_dark, bold=True, fg=WHITE, ha="center")
ws2.cell(row=row, column=6).font = font(bold=True, size=11, color=WHITE)
cell(ws2, row, 7, "β
Within target" if 2400 <= day_kcal <= 2900 else "β Check portions",
bg=LT_GRN if 2400 <= day_kcal <= 2900 else LT_AMB)
row += 2 # spacer
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 3 β FOOD REFERENCE (macros per common Indian food)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws3 = wb.create_sheet("π Food Reference")
ws3.sheet_view.showGridLines = False
for i, w_ in enumerate([2,26,14,10,10,10,10,22,3], 1): cw(ws3, i, w_)
rh(ws3, 1, 6)
rh(ws3, 2, 36)
mhdr(ws3, 2, 2, 8,
"π INDIAN FOOD MACRO REFERENCE | Quick lookup for meal planning",
bg=NAVY, sz=14)
rh(ws3, 3, 18)
mhdr(ws3, 3, 2, 8,
"All values per standard serving. Use this to swap foods in your meal plan.",
bg=BLUE, sz=10, bold=False)
rh(ws3, 4, 6)
food_sections = {
"PROTEINS (High Protein Foods)": (GREEN, [
("Chicken breast (grilled, no skin)", "100g", "165 kcal","31g","0g", "3.6g", "Best lean protein source"),
("Chicken thigh (skinless)", "100g", "209 kcal","26g","0g", "11g", "More flavour, slightly more fat"),
("Egg (whole)", "1 large", "78 kcal", "6g", "1g", "5g", "Most complete protein"),
("Egg white only", "1 white", "17 kcal", "4g", "0.2g","0g", "Pure protein"),
("Paneer (full fat)", "100g", "265 kcal","18g","3g", "21g", "Excellent veg protein + fat"),
("Greek yoghurt (plain)", "100g", "97 kcal", "10g","4g", "5g", "Best dairy protein"),
("Dahi (plain curd)", "100g", "60 kcal", "3g", "5g", "3g", "Less protein than Greek yoghurt"),
("Whey protein (1 scoop, 30g)", "30g powder", "120 kcal","25g","3g", "2g", "Supplement β not a replacement for food"),
("Dal (moong, cooked)", "1 katori 150ml","130 kcal","9g","20g","0.5g","High protein, high fibre"),
("Dal (masoor/red lentil, cooked)", "1 katori", "140 kcal","10g","22g","0.5g", "Highest protein among dals"),
("Rajma (cooked)", "1 katori", "220 kcal","12g","32g","0.5g", "Excellent plant protein + fibre"),
("Chana (whole chickpeas, cooked)", "1 katori", "210 kcal","11g","30g","3g", ""),
("Roasted chana (snack)", "30g", "100 kcal","6g", "15g","2g", "Great snack"),
("Fish (surmai, rohu, pomfret 100g)", "100g", "110 kcal","20g","0g", "3g", "Excellent lean protein"),
("Tuna (canned in water)", "100g", "116 kcal","26g","0g", "1g", "Cheapest high protein"),
]),
"CARBOHYDRATES": (AMBER, [
("White rice (cooked)", "150g cooked", "195 kcal","4g", "44g","0.3g","Use smaller katori"),
("Brown rice (cooked)", "150g cooked", "185 kcal","4g", "40g","1.5g","More fibre, similar calories"),
("Roti (whole wheat)", "1 medium", "85 kcal", "2.5g","17g","0.5g","Better than maida"),
("Oats (dry)", "50g", "185 kcal","7g", "33g","3g", "Best breakfast carb"),
("Banana", "1 medium", "90 kcal", "1g", "23g","0.3g","Pre-workout fuel"),
("Sweet potato (cooked)", "100g", "86 kcal", "1.6g","20g","0.1g","Better than regular potato"),
("Poha (flattened rice)", "50g dry", "180 kcal","3.5g","40g","0.2g","Quick energy"),
("Besan (gram flour)", "50g", "180 kcal","9g", "28g","2g", "Higher protein than wheat"),
("Whole wheat bread", "2 slices", "160 kcal","6g", "30g","1g", ""),
("Apple", "1 medium", "80 kcal", "0.4g","21g","0.2g",""),
("Dates (khajoor)", "4-5 pieces", "120 kcal","1g", "30g","0.1g","Pre-workout energy"),
]),
"VEGETABLES (Low Calorie, High Fibre)": (TEAL, [
("Spinach (palak, cooked)", "1 katori", "40 kcal", "3g", "5g", "0.5g","Iron + fibre"),
("Broccoli (steamed)", "100g", "34 kcal", "2.8g","7g", "0.3g","Best vegetable for diet"),
("Beans (French beans)", "100g cooked", "35 kcal", "2g", "7g", "0.2g",""),
("Cauliflower", "100g", "25 kcal", "2g", "5g", "0.1g",""),
("Tomato", "1 medium", "22 kcal", "1g", "5g", "0.2g","Lycopene β good for heart"),
("Cucumber", "1 cup sliced","14 kcal", "0.7g","3g", "0.1g","Almost zero calorie"),
("Carrot", "1 medium", "25 kcal", "0.6g","6g", "0.1g",""),
("Bhindi (okra)", "100g cooked", "30 kcal", "1.5g","7g", "0.1g",""),
("Capsicum (bell pepper)", "1 medium", "25 kcal", "1g", "6g", "0.3g","Vitamin C"),
("Methi (fenugreek leaves)", "1 cup", "13 kcal", "1g", "3g", "0.2g","Blood sugar control"),
]),
"FATS & NUTS": (ORANGE, [
("Almonds", "25g (18 nuts)","145 kcal","5g", "5g", "13g", "Best nut for diet"),
("Walnuts", "25g", "165 kcal","4g", "3g", "16g", "Best omega-3 nut"),
("Peanuts (roasted)", "30g", "170 kcal","8g", "5g", "14g", "Cheap high protein nut"),
("Ghee", "1 tsp (5g)", "45 kcal", "0g", "0g", "5g", "Use sparingly. Good fat."),
("Mustard oil", "1 tsp", "40 kcal", "0g", "0g", "5g", "Better than refined oils"),
("Coconut oil", "1 tsp", "40 kcal", "0g", "0g", "4.5g","Use occasionally only"),
("Peanut butter (natural)", "2 tbsp", "190 kcal","8g", "7g", "16g", "Excellent fat + protein combo"),
("Chia seeds", "15g", "70 kcal", "2.5g","6g", "4.5g","Omega-3, fibre, calcium"),
]),
"DRINKS": (PURPLE, [
("Water", "1 glass 250ml","0 kcal", "0g", "0g", "0g", "DRINK 4-5 litres/day"),
("Chaas (buttermilk, plain)", "300ml", "60 kcal", "3g", "6g", "2g", "Excellent digestion aid"),
("Coconut water", "1 glass", "45 kcal", "1g", "10g","0.5g","Natural electrolytes"),
("Green tea", "1 cup", "2 kcal", "0g", "0g", "0g", "Metabolism boost. No sugar."),
("Black coffee (no sugar)", "1 cup", "5 kcal", "0.3g","0.8g","0g", "Pre-workout or morning"),
("Whole milk", "200ml", "124 kcal","6g", "10g","7g", ""),
("Low fat milk", "200ml", "85 kcal", "7g", "12g","2g", "Better for diet"),
("Protein shake (whey+water)","1 scoop", "120 kcal","25g", "3g", "1g", "Supplement only"),
("AVOID: Cold drinks/soda", "330ml can", "140 kcal","0g", "35g","0g", "Empty calories. No fibre."),
("AVOID: Packaged fruit juice","200ml", "100 kcal","0g", "24g","0g", "No fibre. High sugar. Eat the fruit."),
]),
}
row = 5
for section_title, (sec_color, foods) in food_sections.items():
rh(ws3, row, 22)
ws3.merge_cells(start_row=row, start_column=2, end_row=row, end_column=8)
c = ws3.cell(row=row, column=2, value=f" {section_title}")
c.fill = fill(sec_color); c.font = font(bold=True, size=11, color=WHITE)
c.alignment = align("left", "center")
c.border = med_border(sec_color)
row += 1
rh(ws3, row, 20)
for j, ch in enumerate(["Food Item","Serving","Calories","Protein","Carbs","Fats","Notes"], 2):
hdr(ws3, row, j, ch, bg=sec_color)
row += 1
sc_lite = {GREEN: LT_GRN, AMBER: LT_AMB, TEAL: LT_TEAL,
ORANGE: LT_ORG, PURPLE: LT_PUR}
bg_l = sc_lite.get(sec_color, GREY_L)
for i, (food, serving, kcal, prot, carbs, fats, notes) in enumerate(foods):
rh(ws3, row, 24)
bg = WHITE if i % 2 == 0 else bg_l
avoid = "AVOID" in food
cell(ws3, row, 2, food, bg=LT_RED if avoid else bg, bold=avoid, fg=RED if avoid else BLACK)
cell(ws3, row, 3, serving,bg=LT_RED if avoid else bg, ha="center")
cell(ws3, row, 4, kcal, bg=LT_RED if avoid else bg, ha="center",
bold=True, fg=RED if avoid else NAVY)
cell(ws3, row, 5, prot, bg=LT_RED if avoid else bg, ha="center", bold=True, fg=GREEN)
cell(ws3, row, 6, carbs, bg=LT_RED if avoid else bg, ha="center")
cell(ws3, row, 7, fats, bg=LT_RED if avoid else bg, ha="center")
cell(ws3, row, 8, notes, bg=LT_RED if avoid else bg, italic=True, fg="444444")
row += 1
row += 1
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 4 β MEAL TRACKER (daily log)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws4 = wb.create_sheet("π Daily Tracker")
ws4.sheet_view.showGridLines = False
for i, w_ in enumerate([2,14,24,12,10,10,10,16,3], 1): cw(ws4, i, w_)
rh(ws4, 1, 6)
rh(ws4, 2, 36)
mhdr(ws4, 2, 2, 8,
"π DAILY MEAL TRACKER | Log what you actually eat",
bg=NAVY, sz=14)
rh(ws4, 3, 18)
mhdr(ws4, 3, 2, 8,
"Fill in actual food + quantity. Targets: ~2,600 kcal | ~210g protein | 4-5L water",
bg=BLUE, sz=10, bold=False)
rh(ws4, 4, 6)
# Water + weight header
rh(ws4, 5, 18)
for j, h_ in enumerate(["Date", "Body Weight (kg)", "Water (L)", "Energy (1-10)", "Notes/Mood"], 2):
hdr(ws4, 5, j, h_, bg=GREY_D)
ws4.merge_cells(start_row=5, start_column=7, end_row=5, end_column=8)
for i in range(7):
r = 6 + i
rh(ws4, r, 20)
bg = WHITE if i % 2 == 0 else GREY_L
for j in range(2, 7):
c = ws4.cell(row=r, column=j)
c.fill = fill(INPUT); c.font = font(color="0000FF", sz=10)
c.alignment = align("center"); c.border = border("medium", BLUE)
ws4.merge_cells(start_row=r, start_column=7, end_row=r, end_column=8)
c2 = ws4.cell(row=r, column=7)
c2.fill = fill(INPUT); c2.alignment = align(); c2.border = border("thin")
rh(ws4, 13, 6)
# Daily meal log headers
rh(ws4, 14, 18)
mhdr(ws4, 14, 2, 8, "DAILY MEAL LOG β Enter below", bg=GREY_D)
rh(ws4, 15, 22)
for j, h_ in enumerate(["Date", "Meal", "Food Item", "Quantity", "Kcal", "Protein (g)", "Carbs (g)", "Notes"], 2):
hdr(ws4, 15, j, h_, bg=GREY_D)
meals_order = ["Breakfast","Mid-Morning","Lunch","Pre-Workout","Post-Workout","Dinner","Bedtime Snack"]
meal_bgs_log = [LT_BLUE, LT_TEAL, LT_GRN, LT_AMB, LT_RED, LT_PUR, GREY_L]
row = 16
for week in range(1): # 1 week template
for day_num in range(7):
day_label = f"Day {day_num+1}"
for m_idx, (meal, mbg) in enumerate(zip(meals_order, meal_bgs_log)):
for entry in range(3): # 3 rows per meal
rh(ws4, row, 20)
if entry == 0 and m_idx == 0:
c_date = ws4.cell(row=row, column=2, value=day_label if entry == 0 else "")
c_date.fill = fill(INPUT); c_date.font = font(color="0000FF", bold=True)
c_date.alignment = align("center"); c_date.border = border("medium", BLUE)
elif entry == 0:
ws4.cell(row=row, column=2).fill = fill(GREY_L)
ws4.cell(row=row, column=2).border = border()
else:
ws4.cell(row=row, column=2).fill = fill(GREY_L)
ws4.cell(row=row, column=2).border = border()
if entry == 0:
c_m = ws4.cell(row=row, column=3, value=meal)
c_m.fill = fill(mbg); c_m.font = font(bold=True, size=9)
c_m.alignment = align("center"); c_m.border = border("medium", "888888")
else:
ws4.cell(row=row, column=3).fill = fill(mbg)
ws4.cell(row=row, column=3).border = border()
for j in range(4, 10):
c = ws4.cell(row=row, column=j)
c.fill = fill(INPUT if j < 9 else mbg)
c.font = font(color="0000FF" if j < 9 else BLACK, size=10)
c.alignment = align("center" if j >= 5 else "left")
c.border = border("thin" if j < 9 else "thin")
row += 1
# Day total row
rh(ws4, row, 20)
ws4.merge_cells(start_row=row, start_column=2, end_row=row, end_column=4)
c = ws4.cell(row=row, column=2, value=f" β
DAY {day_num+1} TOTAL")
c.fill = fill(NAVY); c.font = font(bold=True, color=WHITE, size=10)
c.alignment = align(); c.border = med_border(NAVY)
for j, col_label in enumerate(["", "= TOTAL KCAL", "= TOTAL PROTEIN"]):
col_c = ws4.cell(row=row, column=5+j, value=col_label if col_label else "")
col_c.fill = fill(NAVY if not col_label else (LT_GRN if "PROTEIN" in col_label else LT_BLUE))
col_c.font = font(bold=True, color=WHITE if not col_label else NAVY)
col_c.alignment = align("center")
ws4.cell(row=row, column=8).fill = fill(NAVY)
ws4.cell(row=row, column=9).fill = fill(NAVY)
row += 2
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SHEET 5 β TIPS & SWAPS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ws5 = wb.create_sheet("π‘ Tips & Swaps")
ws5.sheet_view.showGridLines = False
for i, w_ in enumerate([2,28,50,3], 1): cw(ws5, i, w_)
rh(ws5, 1, 6)
rh(ws5, 2, 36)
mhdr(ws5, 2, 2, 3,
"π‘ PRACTICAL TIPS, FOOD SWAPS & MEAL PREP GUIDE",
bg=NAVY, sz=14)
rh(ws5, 3, 8)
tips_sections = [
("SMART FOOD SWAPS (Same Taste, Better Macros)", GREEN, [
("Instead of: White rice (150g) β Use: Brown rice", "Save 10 kcal, gain 1g extra fibre. Negligible calorie change but better for blood sugar."),
("Instead of: Fried chicken β Use: Tandoori / grilled chicken", "Save 150-200 kcal per serving. Same protein. Massive difference over a week."),
("Instead of: Paneer bhurji in lots of oil β Use: 1 tsp oil only","Save 80-120 kcal. Paneer protein stays the same."),
("Instead of: Dahi + sugar β Use: Greek yoghurt plain", "Double the protein. No added sugar. More filling."),
("Instead of: Fruit juice β Use: Whole fruit", "Same vitamins. Extra fibre. Half the calories. Slower sugar spike."),
("Instead of: Maida roti β Use: Whole wheat roti", "More fibre, slower digestion, keeps you fuller longer."),
("Instead of: Deep fried snacks (pakoda, samosa) β Roasted chana","Save 200+ kcal. 6g protein vs 0g protein."),
("Instead of: Chawal ka doodh (sweetened rice milk) β Whey + milk","Triple the protein at same calories."),
("Instead of: Mithai as post-workout β Banana + whey shake", "Same quick energy, far more protein for muscle recovery."),
("Instead of: Chips/namkeen β Makhana (roasted fox nuts)", "Less calories, more fibre, no trans fat."),
]),
("MEAL PREP TIPS (Save Time, Stay Consistent)", BLUE, [
("Cook chicken in bulk (Sunday)", "Grill or pressure cook 1 kg chicken breast. Store in fridge. Use across 3-4 days in different meals."),
("Boil eggs in batches", "Boil 8-10 eggs at once. Keep in fridge. Grab-and-go protein for 3-4 days."),
("Make dal in large quantity", "Cook a large pot of dal (moong/masoor). Stays fresh 3-4 days. Add tadka fresh each time."),
("Chop vegetables on Sunday", "Pre-cut cucumber, carrots, tomato for salads. Store in containers. Makes daily salads effortless."),
("Pre-portion snacks", "Portion nuts (25g per serving) into small containers. Prevents overeating when hungry."),
("Keep protein shake ready", "Pre-measure whey scoop. Keep shaker bottle in gym bag. No excuses post-workout."),
("Cook brown rice in large batches", "Rice stores well. Cook 500g at once, refrigerate. Reheat portions as needed."),
]),
("RESTAURANT / EATING OUT GUIDE", AMBER, [
("Safest order", "Tandoori roti + dal makhani + grilled chicken / grilled fish + salad. Skip the naan."),
("Dhaba strategy", "Dal + 2 rotis + dahi = high protein, moderate calorie meal. Ask for less oil in sabzi."),
("Chinese restaurant", "Chilli chicken (dry) + rice instead of noodles. Avoid butter naan, avoid fried rice."),
("Order protein first, carbs second", "Ask for extra chicken/dal. Skip one roti or half the rice if you overate at lunch."),
("Biryani damage control", "Remove excess ghee/oil from top. Eat protein pieces first, rice second. Add raita."),
("Compensation rule", "Big restaurant lunch? Light dinner (egg bhurji + 1 roti + salad). Don't skip the gym."),
("Avoid: Butter naan", "Single butter naan = 300+ kcal, 15g fat, no protein. Swap for 2 whole wheat rotis."),
]),
("PROTEIN TIMING β CRITICAL RULES", RED, [
("Morning (within 1 hr of waking)", "Eat within 1 hour of waking. Prevents morning muscle breakdown (cortisol spike)."),
("Pre-workout (45-60 min before)", "Banana + protein shake OR 1 roti + 2 eggs. Don't train on completely empty stomach."),
("Post-workout (within 30-45 min)", "MOST IMPORTANT: Protein shake + banana immediately post-workout. Don't shower first."),
("Spread protein through day", "Aim for 40-50g per meal Γ 4-5 meals. Don't try to eat 200g in 1-2 meals β poor absorption."),
("Before bed protein", "Casein protein (paneer, Greek yoghurt) before sleep = muscle repair overnight. Don't skip."),
("Protein after every workout", "Even on rest days, hit your protein target. Muscles repair on rest days, not gym days."),
]),
("FOODS TO LIMIT / AVOID", PURPLE, [
("Maida (refined flour) products", "Naan, white bread, biscuits, cake, samosa wrappers. Zero fibre, high glycaemic, no satiety."),
("Fried foods (daily)", "Pakoda, puri, bhatura, fried chicken, chips. Save for once a week maximum."),
("Sugary drinks", "Cold drinks, packaged juice, sweetened lassi, masala chai Γ 4 per day. Liquid calories don't fill you up."),
("Excessive ghee / oil", "1 tsp per dish = fine. 1 tbsp per dish Γ 4 dishes = 400+ extra kcal just from cooking fat."),
("Late night heavy meals", "Eating a full dal-rice at 11 PM spikes insulin before sleep. Light dinner or protein + veggies only."),
("Mithai and sweets", "Gulab jamun, ladoo, barfi β pure sugar + fat. Occasional treat OK (1 piece), daily = problem."),
("Ultra-processed snacks", "Maggi, instant noodles, packaged namkeen. Low protein, high sodium, high calories, no satiety."),
]),
("HYDRATION SCHEDULE", TEAL, [
("7:00 AM (wake up)", "2 large glasses of water (500ml) before anything else. Rehydrates after overnight fast."),
("Before each meal", "1 glass before breakfast, lunch, and dinner. Reduces appetite by ~20%."),
("During workout", "500-750ml during your gym session. Sip every 10-15 min, don't gulp."),
("Post-workout", "500ml within 30 min of finishing gym. Essential for recovery."),
("Add chaas at lunch", "300ml chaas (buttermilk) with lunch = hydration + probiotics + electrolytes."),
("Green tea (2 cups/day max)", "One mid-morning, one mid-afternoon. Mild metabolism boost. NO sugar."),
("AVOID soda and packaged juice", "Replace with water, chaas, coconut water, or green tea completely."),
]),
]
row = 4
for sec_title, sec_color, items in tips_sections:
rh(ws5, row, 22)
ws5.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
c = ws5.cell(row=row, column=2, value=f" {sec_title}")
c.fill = fill(sec_color); c.font = font(bold=True, size=11, color=WHITE)
c.alignment = align("left", "center")
c.border = med_border(sec_color)
row += 1
sc_lite = {GREEN: LT_GRN, BLUE: LT_BLUE, AMBER: LT_AMB,
RED: LT_RED, PURPLE: LT_PUR, TEAL: LT_TEAL}
bg_l = sc_lite.get(sec_color, GREY_L)
for i, (lbl, detail) in enumerate(items):
rh(ws5, row, 28)
bg = WHITE if i % 2 == 0 else bg_l
cell(ws5, row, 2, lbl, bg=bg, bold=True)
cell(ws5, row, 3, detail, bg=bg, wrap=True)
row += 1
row += 1
# ββ SAVE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
out = "/tmp/workspace/plab-schedule/Indian_Meal_Plan_2026.xlsx"
wb.save(out)
print(f"Saved: {out}")
cd /tmp/workspace/plab-schedule && python3 build_meal_plan.py 2>&1
~/plab-schedule/build_meal_plan.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/plab-schedule/build_meal_plan.py'
cd /tmp/workspace/plab-schedule && python3 build_meal_plan.py 2>&1
~/plab-schedule/build_meal_plan.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/plab-schedule/build_meal_plan.py'
cd /tmp/workspace/plab-schedule && python3 build_meal_plan.py 2>&1
~/plab-schedule/build_meal_plan.py
Error: String not found in file: ' c.fill = fill(INPUT); c.font = font(color="0000FF", sz=10)'
grep -n "sz=10" /tmp/workspace/plab-schedule/build_meal_plan.py | head -20