2026-03-06 12:19:55 -06:00
const TIERS = [
2026-03-06 14:12:00 -06:00
{ min : 0 , max : 4 , label : 'Tier 0-1 — Elite Standing' , color : '#28a745' } ,
{ min : 5 , max : 9 , label : 'Tier 1 — Realignment' , color : '#856404' } ,
{ min : 10 , max : 14 , label : 'Tier 2 — Administrative Lockdown' , color : '#d9534f' } ,
{ min : 15 , max : 19 , label : 'Tier 3 — Verification' , color : '#d9534f' } ,
{ min : 20 , max : 24 , label : 'Tier 4 — Risk Mitigation' , color : '#c0392b' } ,
{ min : 25 , max : 29 , label : 'Tier 5 — Final Decision' , color : '#c0392b' } ,
{ min : 30 , max : 999 , label : 'Tier 6 — Separation' , color : '#721c24' } ,
2026-03-06 12:19:55 -06:00
] ;
2026-03-06 14:38:43 -06:00
function getTier ( points ) { return TIERS . find ( t => points >= t . min && points <= t . max ) || TIERS [ 0 ] ; }
2026-03-06 12:19:55 -06:00
function formatDate ( d ) {
2026-03-06 14:12:00 -06:00
if ( ! d ) return '—' ;
const dt = new Date ( d + 'T12:00:00' ) ;
2026-03-06 14:38:43 -06:00
return dt . toLocaleDateString ( 'en-US' , { weekday : 'long' , year : 'numeric' , month : 'long' , day : 'numeric' , timeZone : 'America/Chicago' } ) ;
2026-03-06 12:19:55 -06:00
}
2026-03-06 15:00:27 -06:00
function formatDateTime ( d , t ) { const date = formatDate ( d ) ; return t ? ` ${ date } at ${ t } ` : date ; }
2026-03-06 12:19:55 -06:00
function row ( label , value ) {
2026-03-06 14:12:00 -06:00
return `
< tr >
< td style = "font-weight:600; color:#555; width:200px; padding:8px 12px; border-bottom:1px solid #eee; white-space:nowrap;" > $ { label } < / t d >
< td style = "padding:8px 12px; border-bottom:1px solid #eee; color:#222;" > $ { value || '—' } < / t d >
< / t r > ` ;
2026-03-06 12:19:55 -06:00
}
function buildHtml ( v , score ) {
2026-03-06 15:00:27 -06:00
const priorPts = score . active _points || 0 ; // snapshot at time of logging
const priorTier = getTier ( priorPts ) ;
const newTotal = priorPts + v . points ; // math always based on stored snapshot
const newTier = getTier ( newTotal ) ;
const tierChange = priorTier . label !== newTier . label ;
2026-03-06 14:12:00 -06:00
2026-03-06 14:38:43 -06:00
const generatedAt = new Date ( ) . toLocaleString ( 'en-US' , { timeZone : 'America/Chicago' , dateStyle : 'full' , timeStyle : 'short' } ) ;
2026-03-06 14:12:00 -06:00
return ` <!DOCTYPE html>
2026-03-06 12:19:55 -06:00
< html lang = "en" >
< head >
< meta charset = "UTF-8" / >
< style >
2026-03-06 14:12:00 -06:00
* { box - sizing : border - box ; margin : 0 ; padding : 0 ; }
body { font - family : 'Segoe UI' , Arial , sans - serif ; font - size : 13 px ; color : # 222 ; background : # fff ; }
2026-03-06 14:38:43 -06:00
. header { background : linear - gradient ( 135 deg , # 000000 , # 151622 ) ; color : white ; padding : 22 px 32 px ; display : flex ; align - items : center ; justify - content : space - between ; }
2026-03-06 14:12:00 -06:00
. header - left { display : flex ; align - items : center ; }
2026-03-06 14:38:43 -06:00
. logo { height : 28 px ; margin - right : 12 px ; }
2026-03-06 14:12:00 -06:00
. header h1 { font - size : 20 px ; letter - spacing : 0.5 px ; }
. header p { font - size : 11 px ; opacity : 0.85 ; margin - top : 3 px ; }
. doc - id { text - align : right ; font - size : 11 px ; opacity : 0.8 ; }
. section { margin : 20 px 0 ; }
2026-03-06 14:38:43 -06:00
. section - title { font - size : 14 px ; font - weight : 700 ; color : white ; background : # 000000 ; padding : 8 px 14 px ; border - radius : 4 px 4 px 0 0 ; margin - bottom : 0 ; }
2026-03-06 14:12:00 -06:00
table { width : 100 % ; border - collapse : collapse ; border : 1 px solid # ddd ; border - top : none ; }
2026-03-06 14:38:43 -06:00
. score - box { display : flex ; gap : 20 px ; flex - wrap : wrap ; background : # f8f9fa ; border : 1 px solid # ddd ; border - radius : 6 px ; padding : 16 px 20 px ; margin : 20 px 0 ; }
2026-03-06 14:12:00 -06:00
. score - cell { flex : 1 ; min - width : 120 px ; text - align : center ; }
2026-03-06 14:38:43 -06:00
. score - num { font - size : 28 px ; font - weight : 800 ; }
. score - lbl { font - size : 11 px ; color : # 666 ; margin - top : 2 px ; }
. tier - badge { display : inline - block ; padding : 5 px 14 px ; border - radius : 14 px ; font - size : 12 px ; font - weight : 700 ; border : 2 px solid currentColor ; }
. tier - change { background : # fff3cd ; border : 2 px solid # ffc107 ; border - radius : 6 px ; padding : 12 px 16 px ; margin : 16 px 0 ; font - size : 12 px ; color : # 856404 ; }
. points - display { background : # fff3cd ; border : 2 px solid # ffc107 ; border - radius : 6 px ; padding : 16 px ; margin : 16 px 0 ; text - align : center ; }
2026-03-06 14:12:00 -06:00
. points - display . pts { font - size : 36 px ; font - weight : 800 ; color : # d4af37 ; }
. points - display . lbl { font - size : 12 px ; color : # 666 ; }
2026-03-06 14:38:43 -06:00
. sig - section { margin - top : 50 px ; page - break - inside : avoid ; }
. sig - grid { display : grid ; grid - template - columns : 1 fr 1 fr ; gap : 48 px ; margin - top : 32 px ; }
. sig - block { border - top : 1.5 px solid # 333 ; padding - top : 10 px ; min - height : 80 px ; }
2026-03-06 14:12:00 -06:00
. sig - label { font - size : 11 px ; color : # 555 ; font - weight : 600 ; }
2026-03-06 14:38:43 -06:00
. sig - date - block { border - top : 1.5 px solid # 333 ; padding - top : 10 px ; min - height : 60 px ; margin - top : 36 px ; }
. footer - bar { margin - top : 40 px ; padding : 10 px 0 ; border - top : 2 px solid # 000000 ; font - size : 10 px ; color : # 888 ; text - align : center ; }
. confidential { background : # f8d7da ; border : 1 px solid # f5c6cb ; border - radius : 4 px ; padding : 6 px 12 px ; font - size : 11 px ; color : # 721 c24 ; font - weight : 600 ; text - align : center ; margin - bottom : 16 px ; }
. notice { background : # e7f3ff ; border - left : 4 px solid # 2196 F3 ; padding : 10 px 14 px ; margin : 16 px 0 ; font - size : 12 px ; }
. policy - context { background : # f8f9fa ; border - left : 3 px solid # 667 eea ; padding : 12 px 16 px ; margin : 12 px 0 ; font - size : 12 px ; color : # 444 ; border - radius : 4 px ; }
2026-03-06 12:19:55 -06:00
< / s t y l e >
< / h e a d >
< body >
< div class = "header" >
2026-03-06 14:12:00 -06:00
< div class = "header-left" >
< img src = "/static/mpm-logo.png" class = "logo" / >
< div >
< h1 > CPAS Individual Violation Record < / h 1 >
< p > Message Point Media — Comprehensive Professional Accountability System < / p >
2026-03-06 12:19:55 -06:00
< / d i v >
2026-03-06 14:12:00 -06:00
< / d i v >
2026-03-06 15:00:27 -06:00
< div class = "doc-id" > Document ID : CPAS - $ { v . id . toString ( ) . padStart ( 5 , '0' ) } < br / > Generated : $ { generatedAt } < / d i v >
2026-03-06 12:19:55 -06:00
< / d i v >
< div style = "padding: 0 4px;" >
2026-03-06 14:38:43 -06:00
< div class = "confidential" style = "margin-top:16px;" > ⚠ CONFIDENTIAL — For authorized HR and management use only < / d i v >
2026-03-06 12:19:55 -06:00
< div class = "section" >
2026-03-06 14:12:00 -06:00
< div class = "section-title" > Employee Information < / d i v >
< table >
$ { row ( 'Employee Name' , ` <strong> ${ v . employee _name } </strong> ` ) }
$ { row ( 'Department' , v . department ) }
$ { row ( 'Supervisor' , v . supervisor ) }
$ { row ( 'Witness / Documenting Officer' , v . witness _name ) }
< / t a b l e >
2026-03-06 12:19:55 -06:00
< / d i v >
< div class = "section" >
2026-03-06 14:12:00 -06:00
< div class = "section-title" > Violation Details < / d i v >
< table >
$ { row ( 'Violation Type' , ` <strong> ${ v . violation _name } </strong> ` ) }
$ { row ( 'Category' , v . category ) }
$ { row ( 'Policy Reference' , 'Chapter 4, Section 5 — Comprehensive Professional Accountability System (CPAS)' ) }
$ { row ( 'Incident Date / Time' , formatDateTime ( v . incident _date , v . incident _time ) ) }
$ { v . location ? row ( 'Location / Context' , v . location ) : '' }
$ { row ( 'Submitted By' , v . submitted _by || 'System' ) }
< / t a b l e >
2026-03-06 14:38:43 -06:00
$ { v . details ? ` <div class="policy-context"><strong>Incident Details:</strong><br /> ${ v . details } </div> ` : '' }
2026-03-06 12:19:55 -06:00
< / d i v >
< div class = "section" >
2026-03-06 14:12:00 -06:00
< div class = "section-title" > CPAS Point Assessment < / d i v >
2026-03-06 14:38:43 -06:00
< div class = "points-display" > < div class = "pts" > $ { v . points } < / d i v > < d i v c l a s s = " l b l " > P o i n t s A s s e s s e d — T h i s V i o l a t i o n < / d i v > < / d i v >
2026-03-06 14:12:00 -06:00
< div class = "score-box" >
< div class = "score-cell" >
2026-03-06 15:00:27 -06:00
< div class = "score-num" style = "color:${priorTier.color};" > $ { priorPts } < / d i v >
2026-03-06 14:12:00 -06:00
< div class = "score-lbl" > Active Points ( Prior ) < / d i v >
2026-03-06 15:00:27 -06:00
< div style = "margin-top:6px;" > < span class = "tier-badge" style = "color:${priorTier.color};" > $ { priorTier . label } < / s p a n > < / d i v >
2026-03-06 12:19:55 -06:00
< / d i v >
2026-03-06 14:12:00 -06:00
< div class = "score-cell" style = "font-size:28px; font-weight:300; color:#ccc; line-height:1.8;" > + < / d i v >
< div class = "score-cell" >
< div class = "score-num" style = "color:#d4af37;" > $ { v . points } < / d i v >
< div class = "score-lbl" > Points — This Violation < / d i v >
2026-03-06 12:19:55 -06:00
< / d i v >
2026-03-06 14:12:00 -06:00
< div class = "score-cell" style = "font-size:28px; font-weight:300; color:#ccc; line-height:1.8;" >= < / d i v >
< div class = "score-cell" >
< div class = "score-num" style = "color:${newTier.color};" > $ { newTotal } < / d i v >
< div class = "score-lbl" > New Active Total < / d i v >
2026-03-06 14:38:43 -06:00
< div style = "margin-top:6px;" > < span class = "tier-badge" style = "color:${newTier.color};" > $ { newTier . label } < / s p a n > < / d i v >
2026-03-06 14:12:00 -06:00
< / d i v >
< / d i v >
2026-03-06 15:00:27 -06:00
$ { tierChange ? ` <div class="tier-change"><strong>⚠ Tier Escalation:</strong> This violation advances the employee from <strong> ${ priorTier . label } </strong> to <strong> ${ newTier . label } </strong>.</div> ` : '' }
2026-03-06 12:19:55 -06:00
< / d i v >
< div class = "section" >
2026-03-06 14:12:00 -06:00
< div class = "section-title" > CPAS Tier Reference < / d i v >
< table >
2026-03-06 14:38:43 -06:00
< tr style = "background:#f8f9fa;" > < th style = "padding:7px 12px; text-align:left; font-size:12px;" > Points < / t h > < t h s t y l e = " p a d d i n g : 7 p x 1 2 p x ; t e x t - a l i g n : l e f t ; f o n t - s i z e : 1 2 p x ; " > T i e r < / t h > < / t r >
$ { TIERS . map ( t => ` <tr style=" ${ newTotal >= t . min && newTotal <= t . max ? 'background:#fff3cd; font-weight:700;' : '' } "><td style="padding:6px 12px; border-bottom:1px solid #eee; font-size:12px;"> ${ t . min === 30 ? '30+' : t . min + '– ' + t . max } </td><td style="padding:6px 12px; border-bottom:1px solid #eee; font-size:12px; color: ${ t . color } ;"> ${ t . label } </td></tr> ` ) . join ( '' ) }
2026-03-06 14:12:00 -06:00
< / t a b l e >
2026-03-06 12:19:55 -06:00
< / d i v >
2026-03-06 14:38:43 -06:00
< div class = "notice" > < strong > Employee Notice : < / s t r o n g > C P A S p o i n t s r e m a i n a c t i v e f o r a r o l l i n g 9 0 - d a y p e r i o d f r o m t h e d a t e o f e a c h i n c i d e n t . A c c u m u l a t i o n o f p o i n t s m a y r e s u l t i n t i e r e s c a l a t i o n a n d a s s o c i a t e d c o n s e q u e n c e s a s o u t l i n e d i n t h e E m p l o y e e H a n d b o o k . < / d i v >
2026-03-06 12:19:55 -06:00
< div class = "sig-section" >
2026-03-06 14:12:00 -06:00
< div class = "section-title" style = "background:#000000;" > Acknowledgement & Signatures < / d i v >
2026-03-06 14:38:43 -06:00
< div style = "padding: 20px 0;" >
< p style = "font-size:12px; color:#555; margin-bottom:32px; line-height:1.6;" > By signing below , the employee acknowledges receipt of this violation record . Acknowledgement does not imply agreement . The employee may submit a written response within 5 business days . < / p >
2026-03-06 14:12:00 -06:00
< div class = "sig-grid" >
< div >
< div class = "sig-block" > < div class = "sig-label" > Employee Signature < / d i v > < / d i v >
< div class = "sig-date-block" > < div class = "sig-label" > Date < / d i v > < / d i v >
< / d i v >
< div >
< div class = "sig-block" > < div class = "sig-label" > Supervisor / Documenting Officer Signature < / d i v > < / d i v >
< div class = "sig-date-block" > < div class = "sig-label" > Date < / d i v > < / d i v >
< / d i v >
2026-03-06 12:19:55 -06:00
< / d i v >
2026-03-06 14:12:00 -06:00
< / d i v >
2026-03-06 12:19:55 -06:00
< / d i v >
2026-03-06 14:38:43 -06:00
< div class = "footer-bar" > CPAS Violation Record — Document ID : CPAS - $ { v . id . toString ( ) . padStart ( 5 , '0' ) } & nbsp ; | & nbsp ; $ { v . employee _name } & nbsp ; | & nbsp ; Incident : $ { v . incident _date } & nbsp ; | & nbsp ; Message Point Media Internal Use Only < / d i v >
2026-03-06 12:19:55 -06:00
2026-03-06 14:12:00 -06:00
< / d i v >
2026-03-06 12:19:55 -06:00
< / b o d y >
< / h t m l > ` ;
}
module . exports = buildHtml ;