Add user location support and tax analysis enhancements

- Introduced user location extraction from user tax info for improved matching.
- Normalized user location to province codes for tax calculations.
- Updated MatchResponse schema to include tax analysis data.
- Enhanced LLMTaxAnalyzer to handle various location formats and provide fallback logic.
This commit is contained in:
bolade
2025-10-05 18:34:35 +01:00
parent c78c4c6fe9
commit c45e3fa791
3 changed files with 141 additions and 10 deletions
+53 -1
View File
@@ -87,7 +87,13 @@ class LLMTaxAnalyzer:
# Extract location information
receipt_location = self._extract_receipt_location(receipt)
user_province = user_location.upper()
# Normalize user_location to province code (handle "Canada", "Ontario", "ON", etc.)
user_province = self._normalize_location_to_province(user_location)
logger.info(
f"Building tax analysis context - User Location: {user_location} → Province Code: {user_province}"
)
# Build tax rates reference
tax_rates_info = json.dumps(self.PROVINCIAL_TAX_RATES, indent=2)
@@ -130,6 +136,47 @@ CCA DEPRECIATION RATES BY ASSET CLASS:
"""
return context
def _normalize_location_to_province(self, location: str) -> str:
"""
Normalize various location formats to province code.
Handles: "ON", "Ontario", "Canada", etc.
"""
location_upper = location.upper().strip()
# Direct province code match
if location_upper in self.PROVINCIAL_TAX_RATES:
return location_upper
# Map full province names to codes
province_name_map = {
"ONTARIO": "ON",
"QUEBEC": "QC",
"BRITISH COLUMBIA": "BC",
"ALBERTA": "AB",
"SASKATCHEWAN": "SK",
"MANITOBA": "MB",
"NOVA SCOTIA": "NS",
"NEW BRUNSWICK": "NB",
"NEWFOUNDLAND AND LABRADOR": "NL",
"NEWFOUNDLAND": "NL",
"PRINCE EDWARD ISLAND": "PE",
"NORTHWEST TERRITORIES": "NT",
"NUNAVUT": "NU",
"YUKON": "YT",
}
if location_upper in province_name_map:
return province_name_map[location_upper]
# Default to Ontario if country is Canada or unspecified
if location_upper in ["CANADA", "CAN", "CA", ""]:
logger.warning(f"Location '{location}' is too generic, defaulting to ON")
return "ON"
# If nothing matches, default to Ontario
logger.warning(f"Could not parse location '{location}', defaulting to ON")
return "ON"
def _extract_receipt_location(self, receipt: Receipt) -> str:
"""Extract and format receipt location information"""
@@ -276,6 +323,7 @@ b) **CCA Depreciation** (for tax purposes - Canada):
Provide a structured JSON response with the following format:
{{
"final_tax_amount": XX.XX,
"sales_tax": {{
"applicable_province": "XX",
"applicable_rate": 0.XX,
@@ -321,6 +369,8 @@ Provide a structured JSON response with the following format:
"overall_assessment": "Comprehensive summary: which rules applied, why, what location used for what purpose, and any required actions"
}}
**IMPORTANT**: The "final_tax_amount" field at the top level must contain the final calculated tax amount. This should be the calculated_tax from sales_tax analysis. If this is a meals & entertainment expense, ensure you return the FULL tax amount here (not the 50% adjusted amount).
**Critical Reminders**:
- Sales tax uses RECEIPT location (or user location if receipt has none)
- Depreciation ALWAYS uses USER location
@@ -356,6 +406,7 @@ Provide a structured JSON response with the following format:
"""Return fallback analysis if LLM fails"""
return json.dumps(
{
"final_tax_amount": 0.0,
"sales_tax": {
"applicable_province": "ON",
"applicable_rate": 0.13,
@@ -424,6 +475,7 @@ Provide a structured JSON response with the following format:
# Return structured fallback
return {
"final_tax_amount": receipt.tax if receipt.tax else 0.0,
"sales_tax": {
"requires_review": True,
"reason": "Failed to parse LLM response",