Add Python web scraper for NJC travel rates with currency extraction

- Implemented Python scraper using BeautifulSoup and pandas to automatically collect travel rates from official NJC website
- Added currency extraction from table titles (supports EUR, USD, AUD, CAD, ARS, etc.)
- Added country extraction from table titles for international rates
- Flatten pandas MultiIndex columns for cleaner data structure
- Default to CAD for domestic Canadian sources (accommodations and domestic tables)
- Created SQLite database schema (raw_tables, rate_entries, exchange_rates, accommodations)
- Successfully scraped 92 tables with 17,205 rate entries covering 25 international cities
- Added migration script to convert scraped data to Node.js database format
- Updated .gitignore for Python files (.venv/, __pycache__, *.pyc, *.sqlite3)
- Fixed city validation and currency conversion in main app
- Added comprehensive debug and verification scripts

This replaces manual JSON maintenance with automated data collection from official government source.
This commit is contained in:
2026-01-13 09:21:43 -05:00
commit 15094ac94b
84 changed files with 19859 additions and 0 deletions

3704
data/accommodationRates.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,363 @@
{
"metadata": {
"effectiveDate": "2025-10-30",
"version": "1.0",
"source": "Government Accommodation Directory (PWGSC)",
"lastUpdated": "2025-10-30",
"notes": "Sample rates for common Canadian cities. Actual rates should be verified at https://rehelv-acrd.tpsgc-pwgsc.gc.ca/lth-crl-eng.aspx"
},
"cities": {
"ottawa": {
"name": "Ottawa, ON",
"province": "Ontario",
"region": "canada",
"standardRate": 165.00,
"maxRate": 200.00,
"currency": "CAD",
"notes": "National Capital Region"
},
"toronto": {
"name": "Toronto, ON",
"province": "Ontario",
"region": "canada",
"standardRate": 180.00,
"maxRate": 220.00,
"currency": "CAD",
"notes": "Major urban center"
},
"montreal": {
"name": "Montreal, QC",
"province": "Quebec",
"region": "canada",
"standardRate": 170.00,
"maxRate": 210.00,
"currency": "CAD",
"notes": "Major urban center"
},
"vancouver": {
"name": "Vancouver, BC",
"province": "British Columbia",
"region": "canada",
"standardRate": 190.00,
"maxRate": 240.00,
"currency": "CAD",
"notes": "Major urban center, high cost area"
},
"calgary": {
"name": "Calgary, AB",
"province": "Alberta",
"region": "canada",
"standardRate": 160.00,
"maxRate": 195.00,
"currency": "CAD",
"notes": "Major urban center"
},
"edmonton": {
"name": "Edmonton, AB",
"province": "Alberta",
"region": "canada",
"standardRate": 155.00,
"maxRate": 190.00,
"currency": "CAD",
"notes": "Major urban center"
},
"winnipeg": {
"name": "Winnipeg, MB",
"province": "Manitoba",
"region": "canada",
"standardRate": 140.00,
"maxRate": 175.00,
"currency": "CAD",
"notes": "Urban center"
},
"halifax": {
"name": "Halifax, NS",
"province": "Nova Scotia",
"region": "canada",
"standardRate": 150.00,
"maxRate": 185.00,
"currency": "CAD",
"notes": "Regional center"
},
"quebec": {
"name": "Quebec City, QC",
"province": "Quebec",
"region": "canada",
"standardRate": 155.00,
"maxRate": 190.00,
"currency": "CAD",
"notes": "Provincial capital"
},
"victoria": {
"name": "Victoria, BC",
"province": "British Columbia",
"region": "canada",
"standardRate": 175.00,
"maxRate": 215.00,
"currency": "CAD",
"notes": "Provincial capital"
},
"whitehorse": {
"name": "Whitehorse, YT",
"province": "Yukon",
"region": "yukon",
"standardRate": 185.00,
"maxRate": 230.00,
"currency": "CAD",
"notes": "Territorial capital, limited availability"
},
"yellowknife": {
"name": "Yellowknife, NT",
"province": "Northwest Territories",
"region": "nwt",
"standardRate": 210.00,
"maxRate": 270.00,
"currency": "CAD",
"notes": "Territorial capital, high cost area, limited availability"
},
"iqaluit": {
"name": "Iqaluit, NU",
"province": "Nunavut",
"region": "nunavut",
"standardRate": 280.00,
"maxRate": 350.00,
"currency": "CAD",
"notes": "Territorial capital, very high cost area, very limited availability"
},
"newyork": {
"name": "New York, NY",
"state": "New York",
"region": "usa",
"standardRate": 250.00,
"maxRate": 350.00,
"currency": "USD",
"notes": "Major metropolitan area, very high cost"
},
"washington": {
"name": "Washington, DC",
"state": "District of Columbia",
"region": "usa",
"standardRate": 220.00,
"maxRate": 300.00,
"currency": "USD",
"notes": "Capital city, high cost area"
},
"chicago": {
"name": "Chicago, IL",
"state": "Illinois",
"region": "usa",
"standardRate": 180.00,
"maxRate": 240.00,
"currency": "USD",
"notes": "Major metropolitan area"
},
"losangeles": {
"name": "Los Angeles, CA",
"state": "California",
"region": "usa",
"standardRate": 200.00,
"maxRate": 280.00,
"currency": "USD",
"notes": "Major metropolitan area, high cost"
},
"sanfrancisco": {
"name": "San Francisco, CA",
"state": "California",
"region": "usa",
"standardRate": 240.00,
"maxRate": 340.00,
"currency": "USD",
"notes": "Major metropolitan area, very high cost"
},
"seattle": {
"name": "Seattle, WA",
"state": "Washington",
"region": "usa",
"standardRate": 195.00,
"maxRate": 260.00,
"currency": "USD",
"notes": "Major metropolitan area"
},
"boston": {
"name": "Boston, MA",
"state": "Massachusetts",
"region": "usa",
"standardRate": 210.00,
"maxRate": 280.00,
"currency": "USD",
"notes": "Major metropolitan area, high cost"
},
"anchorage": {
"name": "Anchorage, AK",
"state": "Alaska",
"region": "alaska",
"standardRate": 180.00,
"maxRate": 240.00,
"currency": "USD",
"notes": "Limited availability, seasonal variations"
}
},
"defaults": {
"canada": {
"standardRate": 150.00,
"maxRate": 185.00,
"currency": "CAD"
},
"yukon": {
"standardRate": 185.00,
"maxRate": 230.00,
"currency": "CAD"
},
"nwt": {
"standardRate": 210.00,
"maxRate": 270.00,
"currency": "CAD"
},
"nunavut": {
"standardRate": 280.00,
"maxRate": 350.00,
"currency": "CAD"
},
"usa": {
"standardRate": 150.00,
"maxRate": 200.00,
"currency": "USD"
},
"alaska": {
"standardRate": 180.00,
"maxRate": 240.00,
"currency": "USD"
},
"international": {
"standardRate": 200.00,
"maxRate": 300.00,
"currency": "CAD",
"notes": "Varies significantly by country and city"
}
},
"internationalCities": {
"london": {
"name": "London, UK",
"country": "United Kingdom",
"region": "international",
"standardRate": 280.00,
"maxRate": 380.00,
"currency": "CAD",
"notes": "High cost city, convert from GBP"
},
"paris": {
"name": "Paris, France",
"country": "France",
"region": "international",
"standardRate": 260.00,
"maxRate": 350.00,
"currency": "CAD",
"notes": "High cost city, convert from EUR"
},
"tokyo": {
"name": "Tokyo, Japan",
"country": "Japan",
"region": "international",
"standardRate": 240.00,
"maxRate": 340.00,
"currency": "CAD",
"notes": "High cost city, convert from JPY"
},
"beijing": {
"name": "Beijing, China",
"country": "China",
"region": "international",
"standardRate": 180.00,
"maxRate": 250.00,
"currency": "CAD",
"notes": "Convert from CNY"
},
"sydney": {
"name": "Sydney, Australia",
"country": "Australia",
"region": "international",
"standardRate": 220.00,
"maxRate": 300.00,
"currency": "CAD",
"notes": "High cost city, convert from AUD"
},
"dubai": {
"name": "Dubai, UAE",
"country": "United Arab Emirates",
"region": "international",
"standardRate": 200.00,
"maxRate": 280.00,
"currency": "CAD",
"notes": "Convert from AED"
},
"brussels": {
"name": "Brussels, Belgium",
"country": "Belgium",
"region": "international",
"standardRate": 210.00,
"maxRate": 280.00,
"currency": "CAD",
"notes": "EU headquarters, convert from EUR"
},
"geneva": {
"name": "Geneva, Switzerland",
"country": "Switzerland",
"region": "international",
"standardRate": 320.00,
"maxRate": 450.00,
"currency": "CAD",
"notes": "Very high cost city, convert from CHF"
},
"reykjavik": {
"name": "Reykjavik, Iceland",
"country": "Iceland",
"region": "international",
"standardRate": 240.00,
"maxRate": 320.00,
"currency": "CAD",
"notes": "High cost Nordic city, convert from ISK"
},
"oslo": {
"name": "Oslo, Norway",
"country": "Norway",
"region": "international",
"standardRate": 260.00,
"maxRate": 350.00,
"currency": "CAD",
"notes": "High cost Nordic city, convert from NOK"
},
"stockholm": {
"name": "Stockholm, Sweden",
"country": "Sweden",
"region": "international",
"standardRate": 230.00,
"maxRate": 310.00,
"currency": "CAD",
"notes": "High cost Nordic city, convert from SEK"
},
"copenhagen": {
"name": "Copenhagen, Denmark",
"country": "Denmark",
"region": "international",
"standardRate": 250.00,
"maxRate": 330.00,
"currency": "CAD",
"notes": "High cost Nordic city, convert from DKK"
},
"helsinki": {
"name": "Helsinki, Finland",
"country": "Finland",
"region": "international",
"standardRate": 220.00,
"maxRate": 290.00,
"currency": "CAD",
"notes": "High cost Nordic city, convert from EUR"
}
},
"rateNotes": {
"standardRate": "Typical government-approved hotel rate",
"maxRate": "Maximum allowable without additional authorization",
"exceedingMax": "Rates exceeding max require justification and approval",
"verification": "Always verify current rates at government accommodation directory before booking"
}
}

View File

@@ -0,0 +1,924 @@
{
"metadata": {
"effectiveDate": "2026-01-01",
"version": "2.0",
"source": "NJC Travel Directive Appendix D - Module 4",
"url": "https://www.njc-cnm.gc.ca/directive/app_d/en",
"lastUpdated": "2026-01-12",
"notes": "International travel allowances. C-Day = Commercial Accommodation, P-Day = Private/Non-commercial. Duration: 1-30, 31-120, 121+ days. All rates in original currencies as per NJC."
},
"countries": {
"latvia": {
"name": "Latvia",
"currency": "EUR",
"cities": {
"riga": {
"name": "Riga",
"meals": {
"cDay_1_30": {
"breakfast": 23.85,
"lunch": 41.60,
"dinner": 55.15,
"total": 120.60
},
"cDay_31_120": {
"breakfast": 17.89,
"lunch": 31.20,
"dinner": 41.36,
"total": 90.45
},
"cDay_121_plus": {
"breakfast": 11.93,
"lunch": 20.80,
"dinner": 27.58,
"total": 60.30
},
"pDay_1_30": {
"breakfast": 23.85,
"lunch": 41.60,
"dinner": 55.15,
"total": 120.60
},
"pDay_31_120": {
"breakfast": 17.89,
"lunch": 31.20,
"dinner": 41.36,
"total": 90.45
},
"pDay_121_plus": {
"breakfast": 11.93,
"lunch": 20.80,
"dinner": 27.58,
"total": 60.30
}
},
"accommodation": {
"cDay_1_30": 38.59,
"cDay_31_120": 28.94,
"cDay_121_plus": 28.94,
"pDay_1_30": 24.12,
"pDay_31_120": 18.09,
"pDay_121_plus": 18.09
},
"dailyTotal": {
"cDay_1_30": 159.19,
"cDay_31_120": 119.39,
"cDay_121_plus": 89.24,
"pDay_1_30": 144.72,
"pDay_31_120": 108.54,
"pDay_121_plus": 78.39
}
},
"other": {
"name": "Other cities in Latvia",
"meals": {
"cDay_1_30": {
"breakfast": 19.08,
"lunch": 33.28,
"dinner": 44.12,
"total": 96.48
},
"cDay_31_120": {
"breakfast": 14.31,
"lunch": 24.96,
"dinner": 33.09,
"total": 72.36
},
"cDay_121_plus": {
"breakfast": 9.54,
"lunch": 16.64,
"dinner": 22.06,
"total": 48.24
},
"pDay_1_30": {
"breakfast": 19.08,
"lunch": 33.28,
"dinner": 44.12,
"total": 96.48
},
"pDay_31_120": {
"breakfast": 14.31,
"lunch": 24.96,
"dinner": 33.09,
"total": 72.36
},
"pDay_121_plus": {
"breakfast": 9.54,
"lunch": 16.64,
"dinner": 22.06,
"total": 48.24
}
},
"accommodation": {
"cDay_1_30": 30.87,
"cDay_31_120": 23.16,
"cDay_121_plus": 23.16,
"pDay_1_30": 19.30,
"pDay_31_120": 14.47,
"pDay_121_plus": 14.47
},
"dailyTotal": {
"cDay_1_30": 127.35,
"cDay_31_120": 95.52,
"cDay_121_plus": 71.40,
"pDay_1_30": 115.78,
"pDay_31_120": 86.83,
"pDay_121_plus": 62.71
}
}
}
},
"laos": {
"name": "Laos",
"currency": "USD",
"cities": {
"vientiane": {
"name": "Vientiane",
"meals": {
"cDay_1_30": {
"breakfast": 13.85,
"lunch": 16.30,
"dinner": 22.00,
"total": 52.15
},
"cDay_31_120": {
"breakfast": 10.39,
"lunch": 12.23,
"dinner": 16.50,
"total": 39.11
},
"cDay_121_plus": {
"breakfast": 6.93,
"lunch": 8.15,
"dinner": 11.00,
"total": 26.08
},
"pDay_1_30": {
"breakfast": 13.85,
"lunch": 16.30,
"dinner": 22.00,
"total": 52.15
},
"pDay_31_120": {
"breakfast": 10.39,
"lunch": 12.23,
"dinner": 16.50,
"total": 39.11
},
"pDay_121_plus": {
"breakfast": 6.93,
"lunch": 8.15,
"dinner": 11.00,
"total": 26.08
}
},
"accommodation": {
"cDay_1_30": 16.69,
"cDay_31_120": 12.52,
"cDay_121_plus": 12.52,
"pDay_1_30": 10.43,
"pDay_31_120": 7.82,
"pDay_121_plus": 7.82
},
"dailyTotal": {
"cDay_1_30": 68.84,
"cDay_31_120": 51.63,
"cDay_121_plus": 38.59,
"pDay_1_30": 62.58,
"pDay_31_120": 46.94,
"pDay_121_plus": 33.90
}
},
"other": {
"name": "Other cities in Laos",
"meals": {
"cDay_1_30": {
"breakfast": 11.08,
"lunch": 13.04,
"dinner": 17.60,
"total": 41.72
},
"cDay_31_120": {
"breakfast": 8.31,
"lunch": 9.78,
"dinner": 13.20,
"total": 31.29
},
"cDay_121_plus": {
"breakfast": 5.54,
"lunch": 6.52,
"dinner": 8.80,
"total": 20.86
},
"pDay_1_30": {
"breakfast": 11.08,
"lunch": 13.04,
"dinner": 17.60,
"total": 41.72
},
"pDay_31_120": {
"breakfast": 8.31,
"lunch": 9.78,
"dinner": 13.20,
"total": 31.29
},
"pDay_121_plus": {
"breakfast": 5.54,
"lunch": 6.52,
"dinner": 8.80,
"total": 20.86
}
},
"accommodation": {
"cDay_1_30": 13.35,
"cDay_31_120": 10.01,
"cDay_121_plus": 10.01,
"pDay_1_30": 8.34,
"pDay_31_120": 6.26,
"pDay_121_plus": 6.26
},
"dailyTotal": {
"cDay_1_30": 55.07,
"cDay_31_120": 41.30,
"cDay_121_plus": 30.87,
"pDay_1_30": 50.06,
"pDay_31_120": 37.55,
"pDay_121_plus": 27.12
}
}
}
},
"lebanon": {
"name": "Lebanon",
"currency": "USD",
"cities": {
"beirut": {
"name": "Beirut",
"meals": {
"cDay_1_30": {
"breakfast": 25.35,
"lunch": 41.75,
"dinner": 53.30,
"total": 120.40
},
"cDay_31_120": {
"breakfast": 19.01,
"lunch": 31.31,
"dinner": 39.98,
"total": 90.30
},
"cDay_121_plus": {
"breakfast": 12.68,
"lunch": 20.88,
"dinner": 26.65,
"total": 60.20
},
"pDay_1_30": {
"breakfast": 25.35,
"lunch": 41.75,
"dinner": 53.30,
"total": 120.40
},
"pDay_31_120": {
"breakfast": 19.01,
"lunch": 31.31,
"dinner": 39.98,
"total": 90.30
},
"pDay_121_plus": {
"breakfast": 12.68,
"lunch": 20.88,
"dinner": 26.65,
"total": 60.20
}
},
"accommodation": {
"cDay_1_30": 38.53,
"cDay_31_120": 28.90,
"cDay_121_plus": 28.90,
"pDay_1_30": 24.08,
"pDay_31_120": 18.06,
"pDay_121_plus": 18.06
},
"dailyTotal": {
"cDay_1_30": 158.93,
"cDay_31_120": 119.20,
"cDay_121_plus": 89.10,
"pDay_1_30": 144.48,
"pDay_31_120": 108.36,
"pDay_121_plus": 78.26
}
},
"other": {
"name": "Other cities in Lebanon",
"meals": {
"cDay_1_30": {
"breakfast": 20.28,
"lunch": 33.40,
"dinner": 42.64,
"total": 96.32
},
"cDay_31_120": {
"breakfast": 15.21,
"lunch": 25.05,
"dinner": 31.98,
"total": 72.24
},
"cDay_121_plus": {
"breakfast": 10.14,
"lunch": 16.70,
"dinner": 21.32,
"total": 48.16
},
"pDay_1_30": {
"breakfast": 20.28,
"lunch": 33.40,
"dinner": 42.64,
"total": 96.32
},
"pDay_31_120": {
"breakfast": 15.21,
"lunch": 25.05,
"dinner": 31.98,
"total": 72.24
},
"pDay_121_plus": {
"breakfast": 10.14,
"lunch": 16.70,
"dinner": 21.32,
"total": 48.16
}
},
"accommodation": {
"cDay_1_30": 30.82,
"cDay_31_120": 23.12,
"cDay_121_plus": 23.12,
"pDay_1_30": 19.26,
"pDay_31_120": 14.45,
"pDay_121_plus": 14.45
},
"dailyTotal": {
"cDay_1_30": 127.14,
"cDay_31_120": 95.36,
"cDay_121_plus": 71.28,
"pDay_1_30": 115.58,
"pDay_31_120": 86.69,
"pDay_121_plus": 62.61
}
}
}
},
"lesotho": {
"name": "Lesotho",
"currency": "LSL",
"cities": {
"maseru": {
"name": "Maseru",
"meals": {
"cDay_1_30": {
"breakfast": 222.00,
"lunch": 316.50,
"dinner": 392.50,
"total": 931.00
},
"cDay_31_120": {
"breakfast": 166.50,
"lunch": 237.38,
"dinner": 294.38,
"total": 698.25
},
"cDay_121_plus": {
"breakfast": 111.00,
"lunch": 158.25,
"dinner": 196.25,
"total": 465.50
},
"pDay_1_30": {
"breakfast": 222.00,
"lunch": 316.50,
"dinner": 392.50,
"total": 931.00
},
"pDay_31_120": {
"breakfast": 166.50,
"lunch": 237.38,
"dinner": 294.38,
"total": 698.25
},
"pDay_121_plus": {
"breakfast": 111.00,
"lunch": 158.25,
"dinner": 196.25,
"total": 465.50
}
},
"accommodation": {
"cDay_1_30": 297.92,
"cDay_31_120": 223.44,
"cDay_121_plus": 223.44,
"pDay_1_30": 186.20,
"pDay_31_120": 139.65,
"pDay_121_plus": 139.65
},
"dailyTotal": {
"cDay_1_30": 1228.92,
"cDay_31_120": 921.69,
"cDay_121_plus": 688.94,
"pDay_1_30": 1117.20,
"pDay_31_120": 837.90,
"pDay_121_plus": 605.15
}
},
"other": {
"name": "Other cities in Lesotho",
"meals": {
"cDay_1_30": {
"breakfast": 177.60,
"lunch": 253.20,
"dinner": 314.00,
"total": 744.80
},
"cDay_31_120": {
"breakfast": 133.20,
"lunch": 189.90,
"dinner": 235.50,
"total": 558.60
},
"cDay_121_plus": {
"breakfast": 88.80,
"lunch": 126.60,
"dinner": 157.00,
"total": 372.40
},
"pDay_1_30": {
"breakfast": 177.60,
"lunch": 253.20,
"dinner": 314.00,
"total": 744.80
},
"pDay_31_120": {
"breakfast": 133.20,
"lunch": 189.90,
"dinner": 235.50,
"total": 558.60
},
"pDay_121_plus": {
"breakfast": 88.80,
"lunch": 126.60,
"dinner": 157.00,
"total": 372.40
}
},
"accommodation": {
"cDay_1_30": 238.34,
"cDay_31_120": 178.75,
"cDay_121_plus": 178.75,
"pDay_1_30": 148.96,
"pDay_31_120": 111.72,
"pDay_121_plus": 111.72
},
"dailyTotal": {
"cDay_1_30": 983.14,
"cDay_31_120": 737.35,
"cDay_121_plus": 551.15,
"pDay_1_30": 893.76,
"pDay_31_120": 670.32,
"pDay_121_plus": 484.12
}
}
}
},
"liberia": {
"name": "Liberia",
"currency": "USD",
"oneRateForCountry": true,
"cities": {
"monrovia": {
"name": "Monrovia",
"meals": {
"cDay_1_30": {
"breakfast": 23.60,
"lunch": 34.05,
"dinner": 43.05,
"total": 100.70
},
"cDay_31_120": {
"breakfast": 17.70,
"lunch": 25.54,
"dinner": 32.29,
"total": 75.53
},
"cDay_121_plus": {
"breakfast": 11.80,
"lunch": 17.03,
"dinner": 21.53,
"total": 50.35
},
"pDay_1_30": {
"breakfast": 23.60,
"lunch": 34.05,
"dinner": 43.05,
"total": 100.70
},
"pDay_31_120": {
"breakfast": 17.70,
"lunch": 25.54,
"dinner": 32.29,
"total": 75.53
},
"pDay_121_plus": {
"breakfast": 11.80,
"lunch": 17.03,
"dinner": 21.53,
"total": 50.35
}
},
"accommodation": {
"cDay_1_30": 32.22,
"cDay_31_120": 24.17,
"cDay_121_plus": 24.17,
"pDay_1_30": 20.14,
"pDay_31_120": 15.11,
"pDay_121_plus": 15.11
},
"dailyTotal": {
"cDay_1_30": 132.92,
"cDay_31_120": 99.69,
"cDay_121_plus": 74.52,
"pDay_1_30": 120.84,
"pDay_31_120": 90.63,
"pDay_121_plus": 65.46
}
}
}
},
"libya": {
"name": "Libya",
"currency": "LYD",
"cities": {
"tripoli": {
"name": "Tripoli",
"meals": {
"cDay_1_30": {
"breakfast": 82.50,
"lunch": 104.50,
"dinner": 148.50,
"total": 335.50
},
"cDay_31_120": {
"breakfast": 61.88,
"lunch": 78.38,
"dinner": 111.38,
"total": 251.63
},
"cDay_121_plus": {
"breakfast": 41.25,
"lunch": 52.25,
"dinner": 74.25,
"total": 167.75
},
"pDay_1_30": {
"breakfast": 82.50,
"lunch": 104.50,
"dinner": 148.50,
"total": 335.50
},
"pDay_31_120": {
"breakfast": 61.88,
"lunch": 78.38,
"dinner": 111.38,
"total": 251.63
},
"pDay_121_plus": {
"breakfast": 41.25,
"lunch": 52.25,
"dinner": 74.25,
"total": 167.75
}
},
"accommodation": {
"cDay_1_30": 107.36,
"cDay_31_120": 80.52,
"cDay_121_plus": 80.52,
"pDay_1_30": 67.10,
"pDay_31_120": 50.33,
"pDay_121_plus": 50.33
},
"dailyTotal": {
"cDay_1_30": 442.86,
"cDay_31_120": 332.15,
"cDay_121_plus": 248.27,
"pDay_1_30": 402.60,
"pDay_31_120": 301.95,
"pDay_121_plus": 218.08
}
},
"other": {
"name": "Other cities in Libya",
"meals": {
"cDay_1_30": {
"breakfast": 66.00,
"lunch": 83.60,
"dinner": 118.80,
"total": 268.40
},
"cDay_31_120": {
"breakfast": 49.50,
"lunch": 62.70,
"dinner": 89.10,
"total": 201.30
},
"cDay_121_plus": {
"breakfast": 33.00,
"lunch": 41.80,
"dinner": 59.40,
"total": 134.20
},
"pDay_1_30": {
"breakfast": 66.00,
"lunch": 83.60,
"dinner": 118.80,
"total": 268.40
},
"pDay_31_120": {
"breakfast": 49.50,
"lunch": 62.70,
"dinner": 89.10,
"total": 201.30
},
"pDay_121_plus": {
"breakfast": 33.00,
"lunch": 41.80,
"dinner": 59.40,
"total": 134.20
}
},
"accommodation": {
"cDay_1_30": 85.89,
"cDay_31_120": 64.42,
"cDay_121_plus": 64.42,
"pDay_1_30": 53.68,
"pDay_31_120": 40.26,
"pDay_121_plus": 40.26
},
"dailyTotal": {
"cDay_1_30": 354.29,
"cDay_31_120": 265.72,
"cDay_121_plus": 198.62,
"pDay_1_30": 322.08,
"pDay_31_120": 241.56,
"pDay_121_plus": 174.46
}
}
}
},
"liechtenstein": {
"name": "Liechtenstein",
"currency": "CHF",
"oneRateForCountry": true,
"cities": {
"vaduz": {
"name": "Vaduz",
"meals": {
"cDay_1_30": {
"breakfast": 23.60,
"lunch": 48.25,
"dinner": 65.75,
"total": 137.60
},
"cDay_31_120": {
"breakfast": 17.70,
"lunch": 36.19,
"dinner": 49.31,
"total": 103.20
},
"cDay_121_plus": {
"breakfast": 11.80,
"lunch": 24.13,
"dinner": 32.88,
"total": 68.80
},
"pDay_1_30": {
"breakfast": 23.60,
"lunch": 48.25,
"dinner": 65.75,
"total": 137.60
},
"pDay_31_120": {
"breakfast": 17.70,
"lunch": 36.19,
"dinner": 49.31,
"total": 103.20
},
"pDay_121_plus": {
"breakfast": 11.80,
"lunch": 24.13,
"dinner": 32.88,
"total": 68.80
}
},
"accommodation": {
"cDay_1_30": 44.03,
"cDay_31_120": 33.02,
"cDay_121_plus": 33.02,
"pDay_1_30": 27.52,
"pDay_31_120": 20.64,
"pDay_121_plus": 20.64
},
"dailyTotal": {
"cDay_1_30": 181.63,
"cDay_31_120": 136.22,
"cDay_121_plus": 101.82,
"pDay_1_30": 165.12,
"pDay_31_120": 123.84,
"pDay_121_plus": 89.44
}
}
}
},
"lithuania": {
"name": "Lithuania",
"currency": "EUR",
"cities": {
"vilnius": {
"name": "Vilnius",
"meals": {
"cDay_1_30": {
"breakfast": 23.90,
"lunch": 50.25,
"dinner": 69.35,
"total": 143.50
},
"cDay_31_120": {
"breakfast": 17.93,
"lunch": 37.69,
"dinner": 52.01,
"total": 107.63
},
"cDay_121_plus": {
"breakfast": 11.95,
"lunch": 25.13,
"dinner": 34.68,
"total": 71.75
},
"pDay_1_30": {
"breakfast": 23.90,
"lunch": 50.25,
"dinner": 69.35,
"total": 143.50
},
"pDay_31_120": {
"breakfast": 17.93,
"lunch": 37.69,
"dinner": 52.01,
"total": 107.63
},
"pDay_121_plus": {
"breakfast": 11.95,
"lunch": 25.13,
"dinner": 34.68,
"total": 71.75
}
},
"accommodation": {
"cDay_1_30": 45.92,
"cDay_31_120": 34.44,
"cDay_121_plus": 34.44,
"pDay_1_30": 28.70,
"pDay_31_120": 21.53,
"pDay_121_plus": 21.53
},
"dailyTotal": {
"cDay_1_30": 189.42,
"cDay_31_120": 142.07,
"cDay_121_plus": 106.19,
"pDay_1_30": 172.20,
"pDay_31_120": 129.15,
"pDay_121_plus": 93.28
}
},
"other": {
"name": "Other cities in Lithuania",
"meals": {
"cDay_1_30": {
"breakfast": 19.12,
"lunch": 40.20,
"dinner": 55.48,
"total": 114.80
},
"cDay_31_120": {
"breakfast": 14.34,
"lunch": 30.15,
"dinner": 41.61,
"total": 86.10
},
"cDay_121_plus": {
"breakfast": 9.56,
"lunch": 20.10,
"dinner": 27.74,
"total": 57.40
},
"pDay_1_30": {
"breakfast": 19.12,
"lunch": 40.20,
"dinner": 55.48,
"total": 114.80
},
"pDay_31_120": {
"breakfast": 14.34,
"lunch": 30.15,
"dinner": 41.61,
"total": 86.10
},
"pDay_121_plus": {
"breakfast": 9.56,
"lunch": 20.10,
"dinner": 27.74,
"total": 57.40
}
},
"accommodation": {
"cDay_1_30": 36.74,
"cDay_31_120": 27.55,
"cDay_121_plus": 27.55,
"pDay_1_30": 22.96,
"pDay_31_120": 17.22,
"pDay_121_plus": 17.22
},
"dailyTotal": {
"cDay_1_30": 151.54,
"cDay_31_120": 113.65,
"cDay_121_plus": 84.95,
"pDay_1_30": 137.76,
"pDay_31_120": 103.32,
"pDay_121_plus": 74.62
}
}
}
},
"luxembourg": {
"name": "Luxembourg",
"currency": "EUR",
"oneRateForCountry": true,
"cities": {
"luxembourg": {
"name": "Luxembourg",
"meals": {
"cDay_1_30": {
"breakfast": 28.60,
"lunch": 59.35,
"dinner": 66.80,
"total": 154.75
},
"cDay_31_120": {
"breakfast": 21.45,
"lunch": 44.51,
"dinner": 50.10,
"total": 116.06
},
"cDay_121_plus": {
"breakfast": 14.30,
"lunch": 29.68,
"dinner": 33.40,
"total": 77.38
},
"pDay_1_30": {
"breakfast": 28.60,
"lunch": 59.35,
"dinner": 66.80,
"total": 154.75
},
"pDay_31_120": {
"breakfast": 21.45,
"lunch": 44.51,
"dinner": 50.10,
"total": 116.06
},
"pDay_121_plus": {
"breakfast": 14.30,
"lunch": 29.68,
"dinner": 33.40,
"total": 77.38
}
},
"accommodation": {
"cDay_1_30": 49.52,
"cDay_31_120": 37.14,
"cDay_121_plus": 37.14,
"pDay_1_30": 30.95,
"pDay_31_120": 23.21,
"pDay_121_plus": 23.21
},
"dailyTotal": {
"cDay_1_30": 204.27,
"cDay_31_120": 153.20,
"cDay_121_plus": 114.52,
"pDay_1_30": 185.70,
"pDay_31_120": 139.28,
"pDay_121_plus": 100.59
}
}
}
}
}
}

313
data/perDiemRates.json Normal file
View File

@@ -0,0 +1,313 @@
{
"metadata": {
"effectiveDate": "2025-10-01",
"version": "1.0",
"source": "NJC Travel Directive Appendix C & D",
"lastUpdated": "2025-10-30",
"notes": "All rates in Canadian Dollars (CAD). US rates are same as Canada but paid in USD."
},
"regions": {
"canada": {
"name": "Canada (Provinces)",
"currency": "CAD",
"meals": {
"breakfast": {
"rate100": 29.05,
"rate75": 21.80,
"rate50": 14.55
},
"lunch": {
"rate100": 29.60,
"rate75": 22.20,
"rate50": 14.80
},
"dinner": {
"rate100": 60.75,
"rate75": 45.55,
"rate50": 30.40
},
"total": {
"rate100": 119.40,
"rate75": 89.55,
"rate50": 59.75
}
},
"incidentals": {
"rate100": 17.30,
"rate75": 13.00
},
"privateAccommodation": {
"day1to120": 50.00,
"day121onward": 25.00
},
"dailyTotal": {
"rate100": 136.70,
"rate75": 102.55,
"rate50plus75": 72.75
}
},
"yukon": {
"name": "Yukon",
"currency": "CAD",
"meals": {
"breakfast": {
"rate100": 26.40,
"rate75": 19.80,
"rate50": 13.20
},
"lunch": {
"rate100": 33.50,
"rate75": 25.15,
"rate50": 16.75
},
"dinner": {
"rate100": 78.50,
"rate75": 58.90,
"rate50": 39.25
},
"total": {
"rate100": 138.40,
"rate75": 103.85,
"rate50": 69.20
}
},
"incidentals": {
"rate100": 17.30,
"rate75": 13.00
},
"privateAccommodation": {
"day1to120": 50.00,
"day121onward": 25.00
},
"dailyTotal": {
"rate100": 155.70,
"rate75": 116.85,
"rate50plus75": 82.20
}
},
"nwt": {
"name": "Northwest Territories",
"currency": "CAD",
"meals": {
"breakfast": {
"rate100": 30.05,
"rate75": 22.55,
"rate50": 15.05
},
"lunch": {
"rate100": 35.65,
"rate75": 26.75,
"rate50": 17.85
},
"dinner": {
"rate100": 76.05,
"rate75": 57.05,
"rate50": 38.05
},
"total": {
"rate100": 141.75,
"rate75": 106.35,
"rate50": 70.95
}
},
"incidentals": {
"rate100": 17.30,
"rate75": 13.00
},
"privateAccommodation": {
"day1to120": 50.00,
"day121onward": 25.00
},
"dailyTotal": {
"rate100": 159.05,
"rate75": 119.35,
"rate50plus75": 83.95
}
},
"nunavut": {
"name": "Nunavut",
"currency": "CAD",
"meals": {
"breakfast": {
"rate100": 35.05,
"rate75": 26.30,
"rate50": 17.55
},
"lunch": {
"rate100": 41.60,
"rate75": 31.20,
"rate50": 20.80
},
"dinner": {
"rate100": 100.45,
"rate75": 75.35,
"rate50": 50.25
},
"total": {
"rate100": 177.10,
"rate75": 132.85,
"rate50": 88.60
}
},
"incidentals": {
"rate100": 17.30,
"rate75": 13.00
},
"privateAccommodation": {
"day1to120": 50.00,
"day121onward": 25.00
},
"dailyTotal": {
"rate100": 194.40,
"rate75": 145.85,
"rate50plus75": 101.60
}
},
"usa": {
"name": "Continental USA",
"currency": "USD",
"meals": {
"breakfast": {
"rate100": 29.05,
"rate75": 21.80,
"rate50": 14.55
},
"lunch": {
"rate100": 29.60,
"rate75": 22.20,
"rate50": 14.80
},
"dinner": {
"rate100": 60.75,
"rate75": 45.55,
"rate50": 30.40
},
"total": {
"rate100": 119.40,
"rate75": 89.55,
"rate50": 59.75
}
},
"incidentals": {
"rate100": 17.30,
"rate75": 13.00
},
"privateAccommodation": {
"day1to120": 50.00,
"day121onward": 25.00
},
"dailyTotal": {
"rate100": 136.70,
"rate75": 102.55,
"rate50plus75": 72.75
}
},
"alaska": {
"name": "Alaska",
"currency": "USD",
"meals": {
"breakfast": {
"rate100": 26.40,
"rate75": 19.80,
"rate50": 13.20
},
"lunch": {
"rate100": 33.50,
"rate75": 25.15,
"rate50": 16.75
},
"dinner": {
"rate100": 78.50,
"rate75": 58.90,
"rate50": 39.25
},
"total": {
"rate100": 138.40,
"rate75": 103.85,
"rate50": 69.20
}
},
"incidentals": {
"rate100": 17.30,
"rate75": 13.00
},
"privateAccommodation": {
"day1to120": 50.00,
"day121onward": 25.00
},
"dailyTotal": {
"rate100": 155.70,
"rate75": 116.85,
"rate50plus75": 82.20
}
},
"international": {
"name": "International (Outside Canada/USA)",
"currency": "CAD",
"notes": "Rates vary by country. See Appendix D for specific country rates. These are average estimates.",
"meals": {
"breakfast": {
"rate100": 35.00,
"rate75": 26.25,
"rate50": 17.50
},
"lunch": {
"rate100": 40.00,
"rate75": 30.00,
"rate50": 20.00
},
"dinner": {
"rate100": 85.00,
"rate75": 63.75,
"rate50": 42.50
},
"total": {
"rate100": 160.00,
"rate75": 120.00,
"rate50": 80.00
}
},
"incidentals": {
"rate100": 20.00,
"rate75": 15.00
},
"privateAccommodation": {
"day1to120": 60.00,
"day121onward": 30.00
},
"dailyTotal": {
"rate100": 180.00,
"rate75": 135.00,
"rate50plus75": 95.00
}
}
},
"rateRules": {
"day1to30": "rate100",
"day31to120": "rate75",
"day121onward": "rate50",
"description": "75% of meal and incidental allowances paid starting day 31. 50% of meals paid starting day 121. Incidentals remain at 75% after day 31."
},
"specialRates": {
"hawaii": {
"reference": "See Appendix D for specific rates",
"currency": "USD"
},
"guam": {
"reference": "See Appendix D for specific rates",
"currency": "USD"
},
"puertoRico": {
"reference": "See Appendix D for specific rates",
"currency": "USD"
},
"virginIslands": {
"reference": "See Appendix D for specific rates",
"currency": "USD"
},
"northernMarianas": {
"reference": "See Appendix D for specific rates",
"currency": "USD"
}
}
}

46
data/sampleFlights.json Normal file
View File

@@ -0,0 +1,46 @@
[
{
"price": 1295.00,
"currency": "CAD",
"duration": "PT16H10M",
"durationHours": 16.2,
"businessClassEligible": true,
"stops": 1,
"carrier": "AC",
"departureTime": "2025-11-15T08:00:00",
"arrivalTime": "2025-11-15T16:10:00"
},
{
"price": 1420.50,
"currency": "CAD",
"duration": "PT14H25M",
"durationHours": 14.4,
"businessClassEligible": true,
"stops": 2,
"carrier": "BA",
"departureTime": "2025-11-15T09:30:00",
"arrivalTime": "2025-11-15T16:55:00"
},
{
"price": 980.25,
"currency": "CAD",
"duration": "PT20H05M",
"durationHours": 20.1,
"businessClassEligible": true,
"stops": 2,
"carrier": "QF",
"departureTime": "2025-11-15T07:15:00",
"arrivalTime": "2025-11-15T16:20:00"
},
{
"price": 875.75,
"currency": "CAD",
"duration": "PT18H40M",
"durationHours": 18.7,
"businessClassEligible": true,
"stops": 3,
"carrier": "SQ",
"departureTime": "2025-11-15T06:45:00",
"arrivalTime": "2025-11-15T15:25:00"
}
]

View File

@@ -0,0 +1,194 @@
{
"metadata": {
"effectiveDate": "2025-10-01",
"version": "1.0",
"source": "NJC Travel Directive Appendix B",
"lastUpdated": "2025-10-30",
"notes": "Kilometric rates for personal vehicle use. All rates in Canadian Dollars (CAD)."
},
"kilometricRates": {
"description": "Rates per kilometre for use of personal vehicle on government business",
"modules": {
"module1and2": {
"name": "Modules 1 and 2 - Local and day travel",
"rates": {
"perKm": 0.68,
"notes": "For travel within headquarters area or day trips"
}
},
"module3": {
"name": "Module 3 - Travel in Canada and Continental USA with overnight stay",
"rates": {
"tier1": {
"description": "First 5,000 km per year",
"perKm": 0.68
},
"tier2": {
"description": "Over 5,000 km per year",
"perKm": 0.58
}
}
}
},
"additionalExpenses": {
"parking": {
"description": "Reasonable parking expenses",
"reimbursable": true,
"requiresReceipt": true
},
"tolls": {
"description": "Highway tolls and ferry fees",
"reimbursable": true,
"requiresReceipt": true
},
"tunnel": {
"description": "Tunnel fees",
"reimbursable": true,
"requiresReceipt": true
}
}
},
"trainTravel": {
"description": "Rail travel within Canada and USA",
"policy": {
"domesticCanada": {
"class": "Economy class",
"provider": "VIA Rail or equivalent",
"notes": "Business class may be authorized with approval"
},
"usa": {
"class": "Economy class",
"provider": "Amtrak or equivalent",
"notes": "Business class may be authorized with approval"
},
"businessClassCriteria": {
"duration": "Extended travel periods",
"workRequirement": "Need to work during travel",
"authorization": "Requires prior approval"
}
},
"commonRoutes": {
"ottawaToMontreal": {
"distance": 200,
"estimatedCost": {
"economy": 45,
"business": 90
},
"provider": "VIA Rail"
},
"ottawaToToronto": {
"distance": 450,
"estimatedCost": {
"economy": 85,
"business": 170
},
"provider": "VIA Rail"
},
"torontoToMontreal": {
"distance": 550,
"estimatedCost": {
"economy": 95,
"business": 190
},
"provider": "VIA Rail"
},
"vancouverToSeattle": {
"distance": 230,
"estimatedCost": {
"economy": 55,
"business": 110
},
"provider": "Amtrak Cascades"
}
}
},
"comparativeAnalysis": {
"description": "When choosing transportation mode, consider:",
"factors": [
"Total cost (transportation + time)",
"Travel duration",
"Accommodation needs",
"Meal allowances during travel",
"Work productivity during travel",
"Environmental impact"
],
"costComparison": {
"ottawaToToronto": {
"flight": {
"cost": 250,
"duration": "1 hour flight + 2 hours airport",
"notes": "Fastest option"
},
"train": {
"cost": 85,
"duration": "4-5 hours",
"notes": "Can work during travel"
},
"vehicle": {
"cost": 306,
"calculation": "450 km × $0.68/km",
"duration": "4.5-5 hours",
"notes": "Plus parking, tolls"
}
}
}
},
"vehicleInsurance": {
"description": "Insurance coverage for personal vehicles on government business",
"coverage": {
"liability": "Covered by government if employee has minimum provincial insurance",
"collision": "Employee responsible for deductible",
"comprehensive": "Employee responsible for deductible"
},
"requirements": {
"minimumInsurance": "Must maintain minimum provincial/territorial insurance",
"proof": "May be required to provide proof of insurance",
"condition": "Vehicle must be in safe operating condition"
}
},
"calculationExamples": {
"example1": {
"scenario": "Day trip Ottawa to Kingston (180 km each way)",
"calculation": {
"totalDistance": 360,
"rate": 0.68,
"totalCost": 244.80,
"formula": "360 km × $0.68/km = $244.80"
}
},
"example2": {
"scenario": "Multi-day trip with 2,000 km total",
"calculation": {
"totalDistance": 2000,
"rate": 0.68,
"totalCost": 1360.00,
"formula": "2,000 km × $0.68/km = $1,360.00",
"notes": "All at tier 1 rate (under 5,000 km/year)"
}
},
"example3": {
"scenario": "Trip after already driving 5,500 km this year, new trip is 1,000 km",
"calculation": {
"totalDistance": 1000,
"rate": 0.58,
"totalCost": 580.00,
"formula": "1,000 km × $0.58/km = $580.00",
"notes": "At tier 2 rate (over 5,000 km/year)"
}
}
},
"specialConsiderations": {
"winterTravel": {
"recommendation": "Consider safety and weather conditions",
"allowances": "Additional travel time may be justified"
},
"remoteLocations": {
"recommendation": "Personal vehicle may be necessary if no public transit",
"considerations": "Check accommodation parking availability"
},
"multiplePassengers": {
"carPooling": "Kilometric rate covers multiple passengers",
"efficiency": "Cost-effective for group travel"
}
}
}