// FILE: reasoning/schedule_queries.cypher // SA102: Employment Income Calculation // Box 1: Pay from employment MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (ii:IncomeItem {type: 'employment'}) WHERE (tp)-[:HAS_INCOME]->(ii) AND ii.valid_from >= ty.start_date AND ii.valid_from <= ty.end_date AND ii.retracted_at IS NULL WITH ii, ty MATCH (ii)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN sum(ii.gross) as box_1_total, collect(DISTINCT { amount: ii.gross, source: d.doc_id, page: e.page, bbox: e.bbox, text_hash: e.text_hash, confidence: e.ocr_confidence }) as evidence_trail, count(ii) as income_items_count; // Box 2: UK tax deducted MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (ii:IncomeItem {type: 'employment'}) WHERE (tp)-[:HAS_INCOME]->(ii) AND ii.valid_from >= ty.start_date AND ii.valid_from <= ty.end_date AND ii.retracted_at IS NULL AND ii.tax_withheld IS NOT NULL WITH ii, ty MATCH (ii)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN sum(ii.tax_withheld) as box_2_total, collect(DISTINCT { amount: ii.tax_withheld, source: d.doc_id, page: e.page, bbox: e.bbox, text_hash: e.text_hash }) as evidence_trail; // SA103: Self-Employment Income Calculation // Box 12: Turnover MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (ba:BusinessActivity)-[:OWNED_BY]->(tp) MATCH (ii:IncomeItem {type: 'self_employment'}) WHERE (ba)-[:GENERATES]->(ii) AND ii.valid_from >= ty.start_date AND ii.valid_from <= ty.end_date AND ii.retracted_at IS NULL WITH ii, ba, ty MATCH (ii)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN ba.business_name as business_name, sum(ii.gross) as box_12_turnover, collect(DISTINCT { amount: ii.gross, description: ii.description, source: d.doc_id, page: e.page, bbox: e.bbox, text_hash: e.text_hash }) as evidence_trail; // Box 31: Total allowable business expenses MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (ba:BusinessActivity)-[:OWNED_BY]->(tp) MATCH (ei:ExpenseItem {type: 'business', allowable: true}) WHERE (ba)-[:INCURS]->(ei) AND ei.valid_from >= ty.start_date AND ei.valid_from <= ty.end_date AND ei.retracted_at IS NULL WITH ei, ba, ty MATCH (ei)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN ba.business_name as business_name, sum(ei.amount) as box_31_expenses, collect(DISTINCT { amount: ei.amount, category: ei.category, description: ei.description, source: d.doc_id, page: e.page, bbox: e.bbox, text_hash: e.text_hash }) as evidence_trail; // SA105: Property Income Calculation // Box 20: Total rents and other income from property MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (pa:PropertyAsset)-[:OWNED_BY]->(tp) MATCH (ii:IncomeItem {type: 'property'}) WHERE (pa)-[:GENERATES]->(ii) AND ii.valid_from >= ty.start_date AND ii.valid_from <= ty.end_date AND ii.retracted_at IS NULL WITH ii, pa, ty MATCH (ii)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN pa.address as property_address, pa.usage as property_usage, sum(ii.gross) as box_20_rental_income, collect(DISTINCT { amount: ii.gross, period_start: ii.period_start, period_end: ii.period_end, source: d.doc_id, page: e.page, bbox: e.bbox, text_hash: e.text_hash }) as evidence_trail; // Box 29: Total allowable property expenses MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (pa:PropertyAsset)-[:OWNED_BY]->(tp) MATCH (ei:ExpenseItem {type: 'property', allowable: true}) WHERE (pa)-[:INCURS]->(ei) AND ei.valid_from >= ty.start_date AND ei.valid_from <= ty.end_date AND ei.retracted_at IS NULL WITH ei, pa, ty MATCH (ei)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN pa.address as property_address, sum(ei.amount) as box_29_expenses, collect(DISTINCT { amount: ei.amount, category: ei.category, description: ei.description, source: d.doc_id, page: e.page, bbox: e.bbox, text_hash: e.text_hash }) as evidence_trail; // SA110: Tax Calculation Summary // Personal allowance calculation with tapering MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (r:Rule {rule_id: 'personal_allowance_' + $tax_year}) WITH tp, ty, r, CASE WHEN $total_income <= r.taper_threshold THEN r.full_allowance WHEN $total_income >= r.taper_threshold + (2 * r.full_allowance) THEN 0 ELSE r.full_allowance - (($total_income - r.taper_threshold) / 2) END as personal_allowance RETURN personal_allowance; // Income tax calculation with bands MATCH (ty:TaxYear {label: $tax_year}) MATCH (r:Rule {rule_id: 'income_tax_bands_' + $tax_year}) WITH $taxable_income as income, r RETURN CASE WHEN income <= r.basic_rate_threshold THEN income * r.basic_rate WHEN income <= r.higher_rate_threshold THEN (r.basic_rate_threshold * r.basic_rate) + ((income - r.basic_rate_threshold) * r.higher_rate) ELSE (r.basic_rate_threshold * r.basic_rate) + ((r.higher_rate_threshold - r.basic_rate_threshold) * r.higher_rate) + ((income - r.higher_rate_threshold) * r.additional_rate) END as income_tax_due; // NIC Class 4 calculation for self-employed MATCH (ty:TaxYear {label: $tax_year}) MATCH (r:Rule {rule_id: 'nic_class4_' + $tax_year}) WITH $self_employment_profit as profit, r RETURN CASE WHEN profit <= r.lower_threshold THEN 0 WHEN profit <= r.upper_threshold THEN (profit - r.lower_threshold) * r.main_rate ELSE ((r.upper_threshold - r.lower_threshold) * r.main_rate) + ((profit - r.upper_threshold) * r.additional_rate) END as nic_class4_due; // Property interest restriction (20% credit) MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (pa:PropertyAsset)-[:OWNED_BY]->(tp) MATCH (ei:ExpenseItem {category: 'mortgage_interest'}) WHERE (pa)-[:INCURS]->(ei) AND ei.valid_from >= ty.start_date AND ei.valid_from <= ty.end_date AND ei.retracted_at IS NULL WITH sum(ei.amount) as total_interest RETURN total_interest as mortgage_interest_paid, total_interest * 0.20 as interest_relief_credit, total_interest * 0.80 as disallowed_interest; // Furnished Holiday Lettings (FHL) qualification test MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) MATCH (pa:PropertyAsset {usage: 'furnished_holiday_letting'})-[:OWNED_BY]->(tp) OPTIONAL MATCH (pa)-[:HAS_METRIC]->(m:PropertyMetric) WHERE m.metric_type IN ['availability_days', 'letting_days', 'longer_term_days'] AND m.tax_year = $tax_year WITH pa, collect(CASE WHEN m.metric_type = 'availability_days' THEN m.value END)[0] as availability, collect(CASE WHEN m.metric_type = 'letting_days' THEN m.value END)[0] as letting, collect(CASE WHEN m.metric_type = 'longer_term_days' THEN m.value END)[0] as longer_term RETURN pa.address as property_address, availability >= 210 as availability_test_passed, letting >= 105 as letting_test_passed, (longer_term IS NULL OR longer_term <= 155) as longer_term_test_passed, (availability >= 210 AND letting >= 105 AND (longer_term IS NULL OR longer_term <= 155)) as fhl_qualified; // Validation query: Income/expense reconciliation MATCH (tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (ty:TaxYear {label: $tax_year}) OPTIONAL MATCH (ii:IncomeItem)-[:BELONGS_TO]->(tp) WHERE ii.valid_from >= ty.start_date AND ii.valid_from <= ty.end_date OPTIONAL MATCH (ei:ExpenseItem)-[:BELONGS_TO]->(tp) WHERE ei.valid_from >= ty.start_date AND ei.valid_from <= ty.end_date OPTIONAL MATCH (p:Payment)-[:BELONGS_TO]->(tp) WHERE p.valid_from >= ty.start_date AND p.valid_from <= ty.end_date RETURN sum(ii.gross) as total_income, sum(ei.amount) as total_expenses, sum(CASE WHEN p.direction = 'outgoing' THEN p.amount ELSE 0 END) as total_payments_out, sum(CASE WHEN p.direction = 'incoming' THEN p.amount ELSE 0 END) as total_payments_in, abs(sum(ii.gross) - sum(CASE WHEN p.direction = 'incoming' THEN p.amount ELSE 0 END)) as income_reconciliation_delta, abs(sum(ei.amount) - sum(CASE WHEN p.direction = 'outgoing' THEN p.amount ELSE 0 END)) as expense_reconciliation_delta; // Time-travel query: Get facts as of specific date CALL temporal.asOf($as_of_date) YIELD node WHERE node:IncomeItem OR node:ExpenseItem WITH node MATCH (node)-[:BELONGS_TO]->(tp:TaxpayerProfile {taxpayer_id: $taxpayer_id}) MATCH (node)-[:DERIVED_FROM]->(e:Evidence)-[:SUPPORTED_BY]->(d:Document) RETURN labels(node)[0] as node_type, node.type as item_type, CASE WHEN node:IncomeItem THEN node.gross ELSE node.amount END as amount, node.valid_from as valid_from, node.valid_to as valid_to, node.asserted_at as asserted_at, d.doc_id as source_document, e.page as source_page, e.text_hash as evidence_hash ORDER BY node.valid_from, node.asserted_at;