@@ -172,6 +172,21 @@ <h3>Summary Statistics</h3>
172172 < script >
173173 // Tax configuration data structure
174174 const taxConfig = {
175+ // incomeTax: {
176+ // name: "Income Tax",
177+ // bands: [
178+ // { min: 0, max: 12570, rate: 0 },
179+ // { min: 12570, max: 50270, rate: 0.20 },
180+ // { min: 50270, max: 125140, rate: 0.40 },
181+ // { min: 125140, max: Infinity, rate: 0.45 }
182+ // ],
183+ // personalAllowance: 12570,
184+ // allowanceTaper: {
185+ // start: 100000,
186+ // end: 125140,
187+ // reductionRate: 0.5 // £1 reduction per £2 over threshold
188+ // }
189+ // },
175190 incomeTax : {
176191 name : "Income Tax" ,
177192 bands : [
@@ -180,12 +195,17 @@ <h3>Summary Statistics</h3>
180195 { min : 50270 , max : 125140 , rate : 0.40 } ,
181196 { min : 125140 , max : Infinity , rate : 0.45 }
182197 ] ,
183- personalAllowance : 12570 ,
198+ personalAllowance : 12_570 ,
184199 allowanceTaper : {
185- start : 100000 ,
186- end : 125140 ,
187- reductionRate : 0.5 // £1 reduction per £2 over threshold
188- }
200+ start : 100_000 ,
201+ end : 125_140 ,
202+ reductionRate : 0.5 // £1 PA reduced per £2 over => 0.5 per £1
203+ } ,
204+ basicRate : 0.20 ,
205+ higherRate : 0.40 ,
206+ additionalRate : 0.45 ,
207+ basicBandWidth : 37_700 , // constant width of 20% band
208+ additionalThresholdGross : 125_140 // fixed gross threshold for 45%
189209 } ,
190210 nationalInsurance : {
191211 name : "National Insurance" ,
@@ -237,35 +257,52 @@ <h3>Summary Statistics</h3>
237257 }
238258 } ;
239259
240- // Calculate income tax with personal allowance tapering
260+
241261 function calculateIncomeTax ( income ) {
242- const config = taxConfig . incomeTax ;
243- let personalAllowance = config . personalAllowance ;
244-
245- // Apply tapering
246- if ( income > config . allowanceTaper . start ) {
247- const excess = Math . min ( income - config . allowanceTaper . start ,
248- config . allowanceTaper . end - config . allowanceTaper . start ) ;
249- personalAllowance = Math . max ( 0 , personalAllowance - ( excess * config . allowanceTaper . reductionRate ) ) ;
262+
263+ const cfg = taxConfig . incomeTax ;
264+
265+ // 1) Adjust the personal allowance for taper
266+ let pa = cfg . personalAllowance ;
267+ if ( income > cfg . allowanceTaper . start ) {
268+ const excess = income - cfg . allowanceTaper . start ;
269+ const reduction = Math . min ( excess * cfg . allowanceTaper . reductionRate , cfg . personalAllowance ) ;
270+ pa = Math . max ( 0 , cfg . personalAllowance - reduction ) ;
250271 }
251-
272+
273+ // 2) Taxable income after the adjusted allowance
274+ let taxable = Math . max ( 0 , income - pa ) ;
275+
276+ // 3) Compute taxable thresholds in *taxable-income* space
277+ const basicWidth = cfg . basicBandWidth ; // 37,700
278+ const additionalThresholdTaxable = cfg . additionalThresholdGross - pa ; // shifts with PA
279+
280+ // 4) Apply bands in taxable space
252281 let tax = 0 ;
253- let taxableIncome = Math . max ( 0 , income - personalAllowance ) ;
254-
255- //
256- for ( const band of config . bands ) {
257- if ( taxableIncome <= 0 ) break ;
258- // Skip first band
259- if ( band . min === 0 ) continue ;
260-
261- const bandWidth = band . max - band . min ;
262- const taxableInBand = Math . min ( taxableIncome , bandWidth ) ;
263- tax += taxableInBand * band . rate ;
264- taxableIncome -= taxableInBand ;
282+
283+ // Basic rate (20%) on first 37,700 of taxable income
284+ const basicTaxable = Math . min ( taxable , basicWidth ) ;
285+ tax += basicTaxable * cfg . basicRate ;
286+ taxable -= basicTaxable ;
287+
288+ if ( taxable > 0 ) {
289+ // Higher-rate ceiling measured from the *start* of taxable bands
290+ // i.e., the portion available at 40% before hitting additional rate
291+ const higherWidth = Math . max ( 0 , additionalThresholdTaxable - basicWidth ) ;
292+
293+ const higherTaxable = Math . min ( taxable , higherWidth ) ;
294+ tax += higherTaxable * cfg . higherRate ;
295+ taxable -= higherTaxable ;
296+
297+ if ( taxable > 0 ) {
298+ // Remainder at additional rate (45%)
299+ tax += taxable * cfg . additionalRate ;
300+ }
265301 }
266-
302+
267303 return tax ;
268- }
304+ }
305+
269306
270307 // Calculate National Insurance
271308 function calculateNationalInsurance ( income ) {
@@ -337,7 +374,7 @@ <h3>Summary Statistics</h3>
337374 }
338375
339376 // Create Vega-Lite specification
340- function createSpec ( data ) {
377+ function createSpec ( data , summaryIncome ) {
341378 // Prepare data for labels - get the last point for each line
342379 const lastPoint = data [ data . length - 1 ] ;
343380 const labelData = [
@@ -359,6 +396,28 @@ <h3>Summary Statistics</h3>
359396 height : 400 ,
360397 data : { values : data } ,
361398 layer : [
399+ {
400+ data : { values : [ { income : summaryIncome } ] } ,
401+ mark : {
402+ type : "rule" ,
403+ strokeWidth : 2 ,
404+ // color: "#666",
405+ strokeDash : [ 3 , 3 ] ,
406+ opacity : 0.7
407+ } ,
408+ encoding : {
409+ x : {
410+ field : "income" ,
411+ type : "quantitative" ,
412+ // scale: {
413+ // domain: [0, Math.max(...data.map(d => d.income))]
414+ // }
415+ } ,
416+ tooltip : [
417+ { field : "income" , type : "quantitative" , title : "Your Income" , format : "$.0f" }
418+ ]
419+ }
420+ } ,
362421 {
363422 mark : { type : "line" , strokeWidth : 2 , color : "#ff6b6b" } ,
364423 encoding : {
@@ -452,12 +511,6 @@ <h3>Summary Statistics</h3>
452511 } ;
453512 }
454513
455- function loadConfig ( ) {
456- const config = fetch ( 'config.json' ) . then ( response => response . json ( ) ) ;
457- console . log ( config ) ;
458- return config ;
459- }
460-
461514 // Update visualisation
462515 function updateVisualisation ( ) {
463516 const components = {
@@ -468,8 +521,9 @@ <h3>Summary Statistics</h3>
468521 } ;
469522
470523 const maxIncome = parseInt ( document . getElementById ( 'maxIncome' ) . value ) || 200000 ;
524+ const summaryIncome = parseInt ( document . getElementById ( 'summaryIncomeInput' ) . value ) || 50000 ;
471525 const data = generateData ( maxIncome , components ) ;
472- const spec = createSpec ( data ) ;
526+ const spec = createSpec ( data , summaryIncome ) ;
473527
474528
475529 vegaEmbed ( '#vis' , spec , {
@@ -494,7 +548,7 @@ <h3>Summary Statistics</h3>
494548 console . log ( spec ) ;
495549
496550 // Update summary statistics
497- const summaryIncome = parseInt ( document . getElementById ( 'summaryIncomeInput' ) . value ) || 50000 ;
551+ // const summaryIncome = parseInt(document.getElementById('summaryIncomeInput').value) || 50000;
498552
499553 const marginal = calculateMarginalRate ( summaryIncome , components ) ;
500554 const total = calculateTotalTax ( summaryIncome , components ) ;
0 commit comments