Add manual tax calculator for rule-based tax analysis and integrate with matching engine

This commit is contained in:
bolade
2025-10-05 20:48:05 +01:00
parent e3f610e01a
commit c2a7c5a087
2 changed files with 708 additions and 11 deletions
+125 -11
View File
@@ -5,14 +5,17 @@ from services.ai_matcher import AIMatcher
from services.ai_rules import AIRulesEngine
from services.feedback_logger import FeedbackLogger
from services.llm_tax_analyzer import LLMTaxAnalyzer
from services.manual_tax_calculator import ManualTaxCalculator
class MatchingEngine:
def __init__(self):
def __init__(self, use_manual_tax_calculator: bool = False):
self.ai_matcher = AIMatcher()
self.rules_engine = AIRulesEngine()
self.feedback_logger = FeedbackLogger()
self.llm_tax_analyzer = LLMTaxAnalyzer()
self.manual_tax_calculator = ManualTaxCalculator()
self.use_manual_tax_calculator = use_manual_tax_calculator
def process_matching(
self,
@@ -42,19 +45,28 @@ class MatchingEngine:
match.confidence_score = 1.0
match.match_reason += " (Auto-approved by rules)"
# Now apply LLM-based tax analysis in a SINGLE batch call
try:
enhanced_matches = self.llm_tax_analyzer.analyze_and_apply_tax_rules_batch(
# Apply tax analysis - use manual calculator or LLM based on configuration
if self.use_manual_tax_calculator:
# Use deterministic rule-based calculator
enhanced_matches = self._apply_manual_tax_analysis(
ai_matches, user_location
)
except Exception as e:
# If batch LLM analysis fails, log it and continue with matches as-is
import logging
else:
# Use LLM-based tax analysis in a SINGLE batch call
try:
enhanced_matches = (
self.llm_tax_analyzer.analyze_and_apply_tax_rules_batch(
ai_matches, user_location
)
)
except Exception as e:
# If batch LLM analysis fails, log it and continue with matches as-is
import logging
logging.error(f"Batch LLM tax analysis failed: {str(e)}")
for match in ai_matches:
match.match_reason += " (Note: Advanced tax analysis unavailable)"
enhanced_matches = ai_matches
logging.error(f"Batch LLM tax analysis failed: {str(e)}")
for match in ai_matches:
match.match_reason += " (Note: Advanced tax analysis unavailable)"
enhanced_matches = ai_matches
return enhanced_matches
@@ -155,6 +167,108 @@ class MatchingEngine:
return match
def _apply_manual_tax_analysis(
self, matches: List[Match], user_location: str = "ON"
) -> List[Match]:
"""
Apply deterministic rule-based tax analysis to all matches
No LLM calls - pure business logic for consistent results
"""
import logging
logger = logging.getLogger(__name__)
logger.info(
f"Applying manual tax analysis to {len(matches)} matches using rule-based calculator"
)
enhanced_matches = []
for match in matches:
try:
# Get comprehensive tax analysis from manual calculator
tax_analysis = self.manual_tax_calculator.calculate_tax_analysis(
match.receipt, match.transaction, user_location
)
# Store the complete tax analysis
match.tax_analysis = tax_analysis
# Apply confidence adjustments
confidence_adj = tax_analysis.get("confidence_adjustment", {})
# Boost confidence if tax rules validate the match
boost = confidence_adj.get("boost", 0.0)
if boost > 0:
match.confidence_score = min(1.0, match.confidence_score + boost)
match.match_reason += f" (Tax validated: +{boost:.2f})"
# Reduce confidence if tax issues detected
reduce = confidence_adj.get("reduce", 0.0)
if reduce > 0:
match.confidence_score = max(0.0, match.confidence_score - reduce)
match.match_reason += f" (Tax issues: -{reduce:.2f})"
# Add flags for manual review
review_flags = []
# Sales tax issues
sales_tax = tax_analysis.get("sales_tax", {})
if sales_tax.get("requires_review"):
if sales_tax.get("is_international"):
review_flags.append("International Transaction - FX Review")
else:
discrepancy_pct = sales_tax.get("discrepancy_percentage", 0)
review_flags.append(
f"Sales Tax Discrepancy: {discrepancy_pct:.1f}%"
)
# FX issues
fx = tax_analysis.get("foreign_exchange", {})
if fx.get("currency_mismatch"):
review_flags.append(
f"FX: {fx['receipt_currency']}{fx['transaction_currency']} (${fx['discrepancy']:.2f})"
)
# Capital asset depreciation
depreciation = tax_analysis.get("depreciation", {})
if depreciation.get("is_capital_asset"):
cca_class = depreciation.get("cca_class", "Unknown")
year1_cca = depreciation.get("cca_depreciation", {}).get(
"year_1_depreciation", 0
)
review_flags.append(
f"Capital Asset ({cca_class}) - Year 1 CCA: ${year1_cca:.2f}"
)
# Meals & entertainment
meals_ent = tax_analysis.get("meals_entertainment", {})
if meals_ent.get("is_meals_entertainment"):
tax_deduction = meals_ent.get("tax_deduction_amount", 0)
accounting_deduction = meals_ent.get(
"accounting_deduction_amount", 0
)
review_flags.append(
f"M&E: Tax ${tax_deduction:.2f} (50%), Accounting ${accounting_deduction:.2f} (100%)"
)
# Add review flags to match reason
if review_flags:
match.match_reason += " | " + "; ".join(review_flags)
enhanced_matches.append(match)
except Exception as e:
logger.error(
f"Manual tax analysis failed for match: {str(e)}", exc_info=True
)
match.match_reason += " (Tax analysis failed)"
enhanced_matches.append(match)
logger.info(
f"Manual tax analysis completed for {len(enhanced_matches)} matches"
)
return enhanced_matches
def approve_match(self, match: Match, user_id: str):
# Log the approval
self.feedback_logger.log_override(