MediaWiki:Gadget-wix-interactive.js: Difference between revisions

Content deleted Content added
No edit summary
No edit summary
Line 13:
- "grouping-funnel" 3-step IFRS 17 contract grouping walkthrough
- "csm-rollforward" Horizontal waterfall: CSM opening → movements → closing
- "building-blocks" Stacked-bar initial recognition building blocks
================================================================ */
 
Line 32 ⟶ 33:
'risk-adjustment': initRiskAdjustment,
'grouping-funnel': initGroupingFunnel,
'csm-rollforward': initCsmRollforward,
'building-blocks': initBuildingBlocks
};
 
Line 2,979 ⟶ 2,981:
window.addEventListener( 'resize', draw );
draw();
}
 
 
/* ================================================================
BUILDING BLOCKS — data-wix-module="building-blocks"
Stacked-bar visualisation of IFRS 17 initial recognition.
Two sliders control outflows and risk adjustment; the chart
shows inflows vs outflows+RA+CSM and flips between profitable
and onerous states with a day-one loss bar.
================================================================ */
 
function initBuildingBlocks( container ) {
 
/* ── Constants ────────────────────────────────────────────── */
 
var INFLOWS = 3000000;
var MAX_BAR = 260;
var C_INFLOW = '#2b6cb0';
var C_OUTFLOW = '#c05621';
var C_RA = '#9b59b6';
var C_CSM = '#27ae60';
var C_LOSS = '#e74c3c';
 
/* ── Formatting ──────────────────────────────────────────── */
 
function fmt( v ) {
return '\u20AC' + Math.round( v ).toLocaleString();
}
 
function fmtShort( v ) {
if ( Math.abs( v ) >= 1000000 ) return '\u20AC' + ( v / 1000000 ).toFixed( 2 ) + 'M';
if ( Math.abs( v ) >= 1000 ) return '\u20AC' + ( v / 1000 ).toFixed( 0 ) + 'K';
return '\u20AC' + v;
}
 
/* ── Build UI ────────────────────────────────────────────── */
 
wix.empty( container );
 
var wrapper = wix.el( 'div', { className: 'wix-eng-wrapper' } );
container.appendChild( wrapper );
 
/* Title */
wrapper.appendChild( wix.el( 'div', { className: 'wix-sim-title', textContent: 'Building Block Stacker' } ) );
 
/* Status banner */
var bannerIcon = wix.el( 'span' );
var bannerText = wix.el( 'span' );
var banner = wix.el( 'div', { className: 'wix-bb-banner wix-bb-banner--profit' }, [
bannerIcon, bannerText
] );
wrapper.appendChild( banner );
 
/* Chart area */
function makeSeg( color ) {
var valEl = wix.el( 'span', { className: 'wix-bb-seg-val' } );
var seg = wix.el( 'div', { className: 'wix-bb-seg', style: { background: color, height: '0px' } }, [ valEl ] );
return { seg: seg, valEl: valEl };
}
 
function makeGroup( labelHTML, stack, extra ) {
var label = wix.el( 'span', { className: 'wix-bb-bar-label', innerHTML: labelHTML } );
var grp = wix.el( 'div', { className: 'wix-bb-group' }, [ stack, label ] );
if ( extra ) Object.keys( extra ).forEach( function ( k ) { grp.style[k] = extra[k]; } );
return grp;
}
 
function makeOp( text, extra ) {
var op = wix.el( 'span', { className: 'wix-bb-op', textContent: text } );
if ( extra ) Object.keys( extra ).forEach( function ( k ) { op.style[k] = extra[k]; } );
return op;
}
 
/* Inflow bar */
var inflow = makeSeg( C_INFLOW );
var stackInflow = wix.el( 'div', { className: 'wix-bb-stack' }, [ inflow.seg ] );
 
/* Outflow + RA + CSM stacked bar */
var csmSeg = makeSeg( C_CSM );
var raSeg = makeSeg( C_RA );
var outflowSeg = makeSeg( C_OUTFLOW );
var stackRight = wix.el( 'div', { className: 'wix-bb-stack' }, [
csmSeg.seg, raSeg.seg, outflowSeg.seg
] );
 
/* Result bar */
var resultSeg = makeSeg( C_CSM );
var resultLabel = wix.el( 'span', { className: 'wix-bb-bar-label' } );
var stackResult = wix.el( 'div', { className: 'wix-bb-stack' }, [ resultSeg.seg ] );
 
/* Loss bar */
var lossSeg = makeSeg( C_LOSS );
var stackLoss = wix.el( 'div', { className: 'wix-bb-stack' }, [ lossSeg.seg ] );
var lossOp = makeOp( '+', { visibility: 'hidden' } );
var lossGroup = makeGroup( 'Day-one<br>loss', stackLoss, { visibility: 'hidden' } );
 
var chartRow = wix.el( 'div', { className: 'wix-bb-row' }, [
wix.el( 'span', { className: 'wix-bb-zero', textContent: '\u20AC0' } ),
makeGroup( 'Inflows<br>(Premiums)', stackInflow ),
makeOp( 'vs' ),
makeGroup( 'Outflows + RA<br>+ CSM', stackRight ),
makeOp( '=' ),
wix.el( 'div', { className: 'wix-bb-group' }, [ stackResult, resultLabel ] ),
lossOp,
lossGroup
] );
 
/* Legend */
function legendItem( color, text ) {
return wix.el( 'span', { className: 'wix-bb-legend-item' }, [
wix.el( 'span', { className: 'wix-bb-legend-swatch', style: { background: color } } ),
text
] );
}
var legend = wix.el( 'div', { className: 'wix-bb-legend' }, [
legendItem( C_INFLOW, 'PV of inflows' ),
legendItem( C_OUTFLOW, 'PV of outflows' ),
legendItem( C_RA, 'Risk adjustment' ),
legendItem( C_CSM, 'CSM' ),
legendItem( C_LOSS, 'Loss' )
] );
 
wrapper.appendChild( wix.el( 'div', { className: 'wix-bb-chart' }, [ chartRow, legend ] ) );
 
/* Slider controls */
var slOutflow = wix.el( 'input', { type: 'range', min: '1500000', max: '3800000', step: '10000', value: '2600000' } );
var svOutflow = wix.el( 'span', { className: 'wix-bb-slider-val' } );
var slRa = wix.el( 'input', { type: 'range', min: '0', max: '500000', step: '5000', value: '120000' } );
var svRa = wix.el( 'span', { className: 'wix-bb-slider-val' } );
 
wrapper.appendChild( wix.el( 'div', { className: 'wix-bb-controls' }, [
wix.el( 'div', { className: 'wix-bb-controls-title', textContent: 'Adjust the assumptions' } ),
wix.el( 'div', { className: 'wix-bb-slider-row' }, [
wix.el( 'span', { className: 'wix-bb-slider-label' }, [
wix.el( 'span', { className: 'wix-bb-slider-dot', style: { background: C_OUTFLOW } } ),
'PV of outflows'
] ),
slOutflow, svOutflow
] ),
wix.el( 'div', { className: 'wix-bb-slider-row' }, [
wix.el( 'span', { className: 'wix-bb-slider-label' }, [
wix.el( 'span', { className: 'wix-bb-slider-dot', style: { background: C_RA } } ),
'Risk adjustment'
] ),
slRa, svRa
] )
] ) );
 
/* Reset button */
var btnReset = wix.el( 'button', { className: 'wix-btn wix-btn--outline', textContent: '\u21BA Reset to Belgian example' } );
wrapper.appendChild( wix.el( 'div', { style: { marginBottom: '1.25rem' } }, [ btnReset ] ) );
 
/* Insight */
var insightEl = wix.el( 'div', { className: 'wix-bb-insight' } );
wrapper.appendChild( insightEl );
 
/* ── Update logic ────────────────────────────────────────── */
 
function update() {
var outflow = Number( slOutflow.value );
var ra = Number( slRa.value );
var netFC = outflow - INFLOWS;
var fulfilment = netFC + ra;
var csm = Math.max( 0, -fulfilment );
var loss = Math.max( 0, fulfilment );
var isOnerous = fulfilment > 0;
 
/* Slider readouts */
svOutflow.textContent = fmt( outflow );
svRa.textContent = fmt( ra );
 
/* Bar heights */
var leftTotal = INFLOWS;
var rightTotal = outflow + ra + csm;
var maxVal = Math.max( leftTotal, rightTotal, 1 );
 
inflow.seg.style.height = ( INFLOWS / maxVal * MAX_BAR ) + 'px';
inflow.valEl.textContent = fmtShort( INFLOWS );
 
outflowSeg.seg.style.height = ( outflow / maxVal * MAX_BAR ) + 'px';
outflowSeg.valEl.textContent = fmtShort( outflow );
 
raSeg.seg.style.height = ( ra / maxVal * MAX_BAR ) + 'px';
raSeg.valEl.textContent = fmtShort( ra );
 
var hCsm = ( csm / maxVal * MAX_BAR );
csmSeg.seg.style.height = hCsm + 'px';
csmSeg.valEl.textContent = csm > 0 ? fmtShort( csm ) : '';
csmSeg.seg.style.display = csm > 0 ? 'block' : 'none';
 
/* Result bar */
resultSeg.seg.style.height = '28px';
resultSeg.valEl.textContent = '\u20AC0';
if ( !isOnerous ) {
resultSeg.seg.style.background = C_CSM;
resultLabel.innerHTML = 'No day-one<br>profit';
} else {
resultSeg.seg.style.background = C_LOSS;
resultLabel.innerHTML = 'CSM<br>= \u20AC0';
}
 
/* Loss bar */
if ( isOnerous ) {
lossGroup.style.visibility = 'visible';
lossOp.style.visibility = 'visible';
lossSeg.seg.style.height = Math.max( loss / maxVal * MAX_BAR, 18 ) + 'px';
lossSeg.valEl.textContent = fmtShort( loss );
} else {
lossGroup.style.visibility = 'hidden';
lossOp.style.visibility = 'hidden';
lossSeg.seg.style.height = '0px';
}
 
/* Banner */
if ( !isOnerous ) {
banner.className = 'wix-bb-banner wix-bb-banner--profit';
bannerIcon.textContent = '\u2705';
bannerText.innerHTML = 'Profitable group \u2014 CSM of <strong>' + fmt( csm ) + '</strong> absorbs the expected gain.';
} else {
banner.className = 'wix-bb-banner wix-bb-banner--onerous';
bannerIcon.textContent = '\uD83D\uDEA8';
bannerText.innerHTML = 'Onerous group \u2014 CSM is zero. Day-one loss of <strong>' + fmt( loss ) + '</strong> hits the income statement.';
}
 
/* Insight */
if ( !isOnerous ) {
insightEl.innerHTML =
'<strong>How it works:</strong> The present value of future cash flows (outflows minus inflows) is <strong>' + fmt( netFC ) + '</strong>. ' +
'Adding the risk adjustment of <strong>' + fmt( ra ) + '</strong> gives fulfilment cash flows of <strong>' + fmt( fulfilment ) + '</strong>. ' +
'Since this is negative (inflows exceed outflows + RA), the group is profitable. ' +
'The <span class="wix-bb-hl-csm">CSM is set to ' + fmt( csm ) + '</span> to absorb this gain, so no profit appears on day one. ' +
'That unearned profit will be released gradually as coverage is provided.';
} else {
insightEl.innerHTML =
'<strong>How it works:</strong> The present value of future cash flows (outflows minus inflows) is <strong>' + fmt( netFC ) + '</strong>. ' +
'Adding the risk adjustment of <strong>' + fmt( ra ) + '</strong> gives fulfilment cash flows of <strong>' + fmt( fulfilment ) + '</strong>. ' +
'Since this is positive (outflows + RA exceed inflows), there is no profit to store. ' +
'The CSM cannot go negative, so it is <span class="wix-bb-hl-loss">floored at zero</span>. ' +
'The <span class="wix-bb-hl-loss">' + fmt( loss ) + ' shortfall is recognised as a loss</span> in the income statement immediately \u2014 the principle of prudence demands early warning.';
}
}
 
/* ── Events ──────────────────────────────────────────────── */
 
slOutflow.addEventListener( 'input', update );
slRa.addEventListener( 'input', update );
btnReset.addEventListener( 'click', function () {
slOutflow.value = '2600000';
slRa.value = '120000';
update();
} );
 
update();
}