MediaWiki:Gadget-wix-interactive.js: Difference between revisions
Content deleted Content added
No edit summary |
No edit summary |
||
Line 10:
- "pool-simulator" Risk pooling comparison (Bordeaux vs Normandy)
- "insurer-engines" Two-engine profit simulator (underwriting + investment)
- "risk-adjustment" Brittany storm confidence-level risk adjustment chart
================================================================ */
Line 26 ⟶ 27:
'prob-weighted': initProbWeighted,
'discount-rate': initDiscountRate,
'balance-sheet': initBalanceSheet,
'risk-adjustment': initRiskAdjustment
};
Line 2,111 ⟶ 2,113:
var padTop = 30;
var padBot = 10;
var bracketZone = 150;
var colGap =
var colW = Math.min( 180, ( W
var chartH = H - padTop - padBot;
var totalW = colW * 2 + colGap + bracketZone;
var padL = Math.max( 10, ( W - totalW ) / 2 );
var xA = padL;
var xL = padL + colW + colGap;
Line 2,255 ⟶ 2,258:
draw();
renderDetail();
}
/* ================================================================
RISK ADJUSTMENT — data-wix-module="risk-adjustment"
Brittany storm: lognormal claim distribution with interactive
confidence slider. Shows best estimate, percentile threshold,
and the risk adjustment gap on a PDF chart.
================================================================ */
function initRiskAdjustment( container ) {
/* ── Constants ────────────────────────────────────────────── */
var MU = 1.7118;
var SIG = 0.40;
var MEAN = 6.0;
var XMIN = 1, XMAX = 18, STEPS = 400;
/* ── Math helpers ────────────────────────────────────────── */
function lognormPDF( x ) {
if ( x <= 0 ) return 0;
var lx = Math.log( x );
return Math.exp( -0.5 * Math.pow( ( lx - MU ) / SIG, 2 ) ) /
( x * SIG * Math.sqrt( 2 * Math.PI ) );
}
function ratApprox( t ) {
var c = [ 2.515517, 0.802853, 0.010328 ];
var d = [ 1.432788, 0.189269, 0.001308 ];
return t - ( c[0] + c[1]*t + c[2]*t*t ) /
( 1 + d[0]*t + d[1]*t*t + d[2]*t*t*t );
}
function normInv( p ) {
if ( p <= 0 ) return -Infinity;
if ( p >= 1 ) return Infinity;
if ( p < 0.5 ) return -ratApprox( Math.sqrt( -2 * Math.log( p ) ) );
if ( p > 0.5 ) return ratApprox( Math.sqrt( -2 * Math.log( 1 - p ) ) );
return 0;
}
function lognormCDFInv( p ) {
return Math.exp( MU + SIG * normInv( p ) );
}
/* ── Chart colors (from WIX tokens) ──────────────────────── */
var styles = getComputedStyle( container );
var colorAccent = styles.getPropertyValue( '--wix-accent' ).trim() || '#1a5276';
var colorWarn = styles.getPropertyValue( '--wix-wrong' ).trim() || '#922b21';
var colorGrid = styles.getPropertyValue( '--wix-border-subtle' ).trim() || '#eaecf0';
var colorAxis = styles.getPropertyValue( '--wix-text-muted' ).trim() || '#54595d';
/* ── Build UI ────────────────────────────────────────────── */
wix.empty( container );
var slider = wix.el( 'input', { type: 'range', min: '50', max: '95', value: '75', step: '1' } );
var sliderVal = wix.el( 'span', { className: 'wix-ra-slider-val', textContent: '75%' } );
var elBE = wix.el( 'div', { className: 'wix-ra-stat-val', textContent: '\u20AC6.0m' } );
var elPct = wix.el( 'div', { className: 'wix-ra-stat-val', textContent: '\u20AC7.2m' } );
var elRA = wix.el( 'div', { className: 'wix-ra-stat-val wix-ra-stat-val--accent', textContent: '\u20AC1.2m' } );
var canvas = wix.el( 'canvas' );
var explain = wix.el( 'p', { className: 'wix-ra-explain' } );
var wrapper = wix.el( 'div', { className: 'wix-eng-wrapper' } );
container.appendChild( wrapper );
/* Slider row */
wrapper.appendChild( wix.el( 'div', { className: 'wix-ra-slider-row' }, [
wix.el( 'span', { className: 'wix-ra-slider-label', textContent: 'Confidence level' } ),
slider,
sliderVal
] ) );
/* Stats row */
wrapper.appendChild( wix.el( 'div', { className: 'wix-ra-stats' }, [
wix.el( 'div', { className: 'wix-ra-stat' }, [
wix.el( 'div', { className: 'wix-ra-stat-label', textContent: 'Best estimate' } ),
elBE
] ),
wix.el( 'div', { className: 'wix-ra-stat' }, [
wix.el( 'div', { className: 'wix-ra-stat-label', textContent: 'Covered up to' } ),
elPct
] ),
wix.el( 'div', { className: 'wix-ra-stat' }, [
wix.el( 'div', { className: 'wix-ra-stat-label', textContent: 'Risk adjustment' } ),
elRA
] )
] ) );
/* Chart */
wrapper.appendChild( wix.el( 'div', { className: 'wix-ra-chart' }, [ canvas ] ) );
/* Legend */
function swatch( color ) {
return wix.el( 'span', { className: 'wix-ra-legend-swatch', style: { background: color } } );
}
wrapper.appendChild( wix.el( 'div', { className: 'wix-ra-legend' }, [
wix.el( 'span', { className: 'wix-ra-legend-item' }, [
swatch( colorAccent + '40' ), 'Claim distribution'
] ),
wix.el( 'span', { className: 'wix-ra-legend-item' }, [
swatch( colorAccent ), 'Best estimate (mean)'
] ),
wix.el( 'span', { className: 'wix-ra-legend-item' }, [
swatch( colorWarn ), 'Confidence threshold'
] )
] ) );
/* Explanation */
wrapper.appendChild( explain );
/* ── Drawing ─────────────────────────────────────────────── */
function draw() {
var ctx = canvas.getContext( '2d' );
var dpr = window.devicePixelRatio || 1;
var rect = canvas.getBoundingClientRect();
if ( rect.width === 0 || rect.height === 0 ) return;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale( dpr, dpr );
var W = rect.width, H = rect.height;
var pad = { t: 16, r: 16, b: 36, l: 44 };
var cw = W - pad.l - pad.r;
var ch = H - pad.t - pad.b;
ctx.clearRect( 0, 0, W, H );
/* Compute PDF curve */
var xs = [], ys = [], ymax = 0;
for ( var i = 0; i <= STEPS; i++ ) {
var x = XMIN + ( XMAX - XMIN ) * i / STEPS;
var y = lognormPDF( x );
xs.push( x ); ys.push( y );
if ( y > ymax ) ymax = y;
}
ymax *= 1.1;
function tx( x ) { return pad.l + ( x - XMIN ) / ( XMAX - XMIN ) * cw; }
function ty( y ) { return pad.t + ch - ( y / ymax ) * ch; }
var conf = parseInt( slider.value, 10 );
var pctX = lognormCDFInv( conf / 100 );
/* Axes */
ctx.strokeStyle = colorGrid;
ctx.lineWidth = 0.5;
ctx.beginPath(); ctx.moveTo( pad.l, pad.t + ch ); ctx.lineTo( pad.l + cw, pad.t + ch ); ctx.stroke();
ctx.beginPath(); ctx.moveTo( pad.l, pad.t ); ctx.lineTo( pad.l, pad.t + ch ); ctx.stroke();
/* X-axis labels */
ctx.font = '11px sans-serif';
ctx.fillStyle = colorAxis;
ctx.textAlign = 'center';
for ( var v = 2; v <= 16; v += 2 ) {
var xp = tx( v );
ctx.fillText( '\u20AC' + v + 'm', xp, pad.t + ch + 20 );
ctx.strokeStyle = colorGrid;
ctx.beginPath(); ctx.moveTo( xp, pad.t + ch ); ctx.lineTo( xp, pad.t + ch + 4 ); ctx.stroke();
}
/* Shaded area under curve up to percentile */
ctx.beginPath();
ctx.moveTo( tx( xs[0] ), ty( 0 ) );
for ( i = 0; i <= STEPS; i++ ) {
if ( xs[i] <= pctX ) ctx.lineTo( tx( xs[i] ), ty( ys[i] ) );
}
var clipIdx = -1;
for ( i = 0; i < xs.length; i++ ) {
if ( xs[i] > pctX ) { clipIdx = i; break; }
}
if ( clipIdx > 0 ) {
var frac = ( pctX - xs[clipIdx-1] ) / ( xs[clipIdx] - xs[clipIdx-1] );
var yClip = ys[clipIdx-1] + frac * ( ys[clipIdx] - ys[clipIdx-1] );
ctx.lineTo( tx( pctX ), ty( yClip ) );
}
ctx.lineTo( tx( pctX ), ty( 0 ) );
ctx.closePath();
ctx.fillStyle = colorAccent + '30';
ctx.fill();
/* Full PDF curve */
ctx.beginPath();
ctx.moveTo( tx( xs[0] ), ty( ys[0] ) );
for ( i = 1; i <= STEPS; i++ ) ctx.lineTo( tx( xs[i] ), ty( ys[i] ) );
ctx.strokeStyle = colorAccent;
ctx.lineWidth = 2;
ctx.stroke();
/* Best-estimate (mean) dashed line */
ctx.setLineDash( [ 5, 4 ] );
ctx.lineWidth = 1.5;
ctx.strokeStyle = colorAccent;
ctx.beginPath();
ctx.moveTo( tx( MEAN ), ty( 0 ) );
ctx.lineTo( tx( MEAN ), ty( lognormPDF( MEAN ) ) );
ctx.stroke();
/* Percentile dashed line */
ctx.strokeStyle = colorWarn;
ctx.beginPath();
ctx.moveTo( tx( pctX ), ty( 0 ) );
ctx.lineTo( tx( pctX ), ty( lognormPDF( pctX ) ) );
ctx.stroke();
ctx.setLineDash( [] );
/* Risk-adjustment bracket */
var raStart = tx( MEAN ), raEnd = tx( pctX );
var arrowY = ty( 0 ) - 18;
if ( raEnd - raStart > 20 ) {
ctx.strokeStyle = colorWarn;
ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo( raStart, arrowY ); ctx.lineTo( raEnd, arrowY ); ctx.stroke();
ctx.beginPath(); ctx.moveTo( raStart, arrowY - 4 ); ctx.lineTo( raStart, arrowY + 4 ); ctx.stroke();
ctx.beginPath(); ctx.moveTo( raEnd, arrowY - 4 ); ctx.lineTo( raEnd, arrowY + 4 ); ctx.stroke();
ctx.fillStyle = colorWarn;
ctx.font = '500 12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText( 'RA: \u20AC' + ( pctX - MEAN ).toFixed( 1 ) + 'm',
( raStart + raEnd ) / 2, arrowY - 6 );
}
/* Update stat cards */
var ra = pctX - MEAN;
sliderVal.textContent = conf + '%';
elPct.textContent = '\u20AC' + pctX.toFixed( 1 ) + 'm';
elRA.textContent = '\u20AC' + ra.toFixed( 1 ) + 'm';
/* Update explanation */
explain.textContent = conf === 50
? 'At the 50% level the threshold equals the best estimate, so the risk adjustment is zero \u2014 the insurer holds no buffer for uncertainty at all.'
: 'At the ' + conf + '% confidence level, the insurer holds enough to cover outcomes up to \u20AC' + pctX.toFixed( 1 ) + 'm. The risk adjustment of \u20AC' + ra.toFixed( 1 ) + 'm is the gap between that threshold and the \u20AC6.0m best estimate \u2014 the price AXA pays for bearing the uncertainty on 3,000 Brittany homes.';
}
/* ── Events ──────────────────────────────────────────────── */
slider.addEventListener( 'input', draw );
window.addEventListener( 'resize', draw );
draw();
}
| |||