<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#0284c7">
<title>İSG Risk Analizi – Bow-Tie (Papyon Analizi)</title>
<style>
:root{--bg:#f8fafc;--paper:#fff;--line:#cbd5e1;--text:#17283b;--muted:#64748b;--blue:#0284c7;--navy:#075985;--pale:#eaf5fc;--red:#b42332;--green:#167a46;--amber:#b45309;--radius:6px;--shadow:0 2px 7px #22465b0b}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:#edf3f8;color:var(--text);font:13px/1.45 "Segoe UI",Arial,sans-serif}button,input,select,textarea{font:inherit}button{cursor:pointer}button:disabled{opacity:.55;cursor:not-allowed}input,select,textarea{width:100%;min-width:0;padding:8px 10px;min-height:36px;color:var(--text);background:#fff;border:1px solid #cbd5e1;border-radius:5px;outline:none}textarea{resize:vertical;min-height:62px}input:focus,select:focus,textarea:focus{border-color:var(--blue);box-shadow:0 0 0 3px #0284c721}label{display:block;color:#334155;font-size:12px;font-weight:650;margin:0 0 5px}label small{font-weight:400;color:var(--muted)}input[type=checkbox]{width:16px;min-height:16px;height:16px;accent-color:var(--blue)}.frame{margin:8px;min-height:calc(100vh - 16px);border:2px solid #7f95a9;border-radius:7px;background:var(--bg);box-shadow:0 5px 20px #203d5414;overflow:clip}.header{position:sticky;top:0;z-index:30;background:#fff;border-top:4px solid var(--blue);border-bottom:1px solid #bfcfdb;box-shadow:0 3px 10px #23405614}.headerinner{min-height:77px;padding:9px 19px;display:flex;align-items:center;gap:13px}.logo{width:62px;height:57px;flex:none;border:1px solid #cbd5e1;border-radius:5px;background:#f8fafc;color:var(--navy);display:grid;place-items:center;padding:4px;overflow:hidden;font-size:12px;font-weight:800;cursor:pointer}.logo:hover{background:#eaf5fc}.logo img{max-width:100%;max-height:100%;width:100%;height:100%;object-fit:contain}.logo img[hidden],.logo span[hidden]{display:none!important}.header-titles{flex:1;min-width:0}.header h1{font-size:18px;color:var(--navy);margin:0;line-height:1.28}.header p{font-size:12px;margin:4px 0 0;color:#475569}.organization{margin-left:auto;text-align:right;min-width:0;max-width:47%;overflow-wrap:anywhere;font-size:16px;letter-spacing:.2px;font-weight:750;color:#1e3a57}.header [contenteditable=true]{outline:2px dashed var(--blue);outline-offset:4px;background:#f0f9ff;border-radius:3px}.wrap{padding:12px 17px 24px;width:100%}.card{min-width:0;background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:13px}.card-header{background:#eaf5fc;border-left:4px solid var(--blue);border-bottom:1px solid #b9d4e6;border-radius:5px 5px 0 0;padding:10px 13px;display:flex;align-items:center;justify-content:space-between;gap:9px;flex-wrap:wrap}.card-header h2{font-size:15px;font-weight:750;color:#084c71;display:flex;align-items:center;gap:8px;margin:0}.section-number{background:#d2eafb;color:var(--navy);border-radius:4px;padding:3px 6px;font-size:11px}.body{padding:14px 15px}.hint{font-size:11px;color:var(--muted)}.muted{color:var(--muted)}.grid{display:grid;gap:11px}.g2{grid-template-columns:repeat(2,minmax(0,1fr))}.g3{grid-template-columns:repeat(3,minmax(0,1fr))}.g4{grid-template-columns:repeat(4,minmax(0,1fr))}.g6{grid-template-columns:repeat(6,minmax(0,1fr))}.span2{grid-column:span 2}.span3{grid-column:span 3}.wide{grid-column:1/-1}.toolbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.action-row{padding:12px 17px 2px}.action-panel{display:flex;align-items:center;flex-wrap:wrap;gap:7px;padding:9px 11px;border:1px solid #b9cbd9;border-radius:6px;background:#fff;box-shadow:0 1px 4px #1b476912}.btn{border:1px solid #cbd5e1;border-radius:5px;color:#334155;background:#fff;min-height:35px;padding:8px 12px;display:inline-flex;align-items:center;justify-content:center;gap:5px;font-weight:650;white-space:nowrap}.btn:hover{background:#eff4f7}.btn-primary{background:var(--blue);border-color:var(--blue);color:#fff}.btn-primary:hover{background:#0369a1}.btn-soft{color:var(--navy);background:#e8f4ff;border-color:#b7d8fb}.btn-soft:hover{background:#d7edff}.btn-green{color:#166534;background:#eaf8ef;border-color:#b9e2c6}.btn-green:hover{background:#dcf3e4}.btn-danger{color:var(--red);border-color:#f0bdc5;background:#fff}.btn-danger:hover{background:#fff1f2}.btn-small{font-size:11px;min-height:28px;padding:5px 8px}.metrics{grid-template-columns:repeat(6,minmax(0,1fr));gap:10px}.metric{margin:0;min-width:0;border-top:3px solid var(--blue);padding:11px 12px;display:flex;justify-content:space-between;gap:7px;align-items:center}.metric small{font-size:10.5px;display:block;color:#64748b;font-weight:700}.metric b{display:block;margin-top:3px;color:#123855;font-size:24px;line-height:1.1}.metric .icon{width:29px;height:29px;border-radius:7px;background:#eaf5fc;display:grid;place-items:center;color:var(--navy);font-size:15px;font-weight:800}.callout{background:#f2f9fd;border:1px solid #d6eefa;border-radius:5px;padding:9px 11px;color:#195071;font-size:12px}.warning{background:#fff9e9;border-color:#f3dda5;color:#815517}.score-card{border:1px solid #dce6ed;border-radius:6px;background:#fcfdff;overflow:hidden}.score-card h3{font-size:13px;margin:0;padding:9px 12px;background:#f2f7fb;border-bottom:1px solid #e4eaf0;color:#1e405a}.score-inner{padding:11px}.score-result{margin-top:10px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;padding:9px;background:#f2f7fb;border-radius:5px}.score-result strong{font-size:23px}.risk-tag{display:inline-block;padding:4px 7px;border:1px solid transparent;border-radius:4px;font-size:11px;font-weight:750}.risk-unknown{background:#eef2f6;color:#475569;border-color:#d5dce4}.risk-low{background:#e5f7ec;color:#177245;border-color:#b7e5c7}.risk-medium{background:#fff5d9;color:#8d6010;border-color:#f3dda5}.risk-high{background:#fff0e1;color:#ad5019;border-color:#ffccaa}.risk-critical{background:#ffe5e8;color:#a92439;border-color:#f4b5c0}.subhead{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.subhead h3{margin:0;color:#0e476b;font-size:13px}.count{font-size:11px;border-radius:10px;background:#e0f2fe;color:var(--navy);padding:2px 8px;font-weight:750}.branch-editor{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.branch-side{min-width:0;border:1px solid #d7e2eb;border-radius:6px;background:#fbfdff;padding:11px}.branch-side.left{border-top:3px solid #1d84b5}.branch-side.right{border-top:3px solid #c77a2f}.branch-item{background:#fff;border:1px solid #d9e3ec;border-radius:6px;padding:10px;margin:10px 0}.branch-item>.item-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;border-bottom:1px solid #edf1f5;padding-bottom:8px;margin-bottom:10px}.branch-item h4{font-size:12px;color:#144863;margin:0}.barrier{border:1px solid #dce4ed;background:#f8fbff;border-radius:6px;padding:9px;margin-top:8px}.barrier h5{margin:0;font-size:12px;color:#274d66}.barrier-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:7px}.checkrow{display:flex;gap:8px;align-items:center;font-size:12px;color:#334155}.checkrow label{display:inline;margin:0;font-weight:500}.empty{padding:17px 10px;color:#64748b;border:1px dashed #b9cbd9;border-radius:6px;text-align:center;background:#fff;font-size:12px}.block-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-top:12px}.diagram-scroll{overflow-x:auto;max-width:100%;padding-bottom:3px}.diagram{min-width:1030px}.diagram-head,.diagram-grid{display:grid;grid-template-columns:1.15fr 1.25fr 1.08fr 1.25fr 1.15fr;column-gap:14px}.diagram-head{margin-bottom:10px;font-size:11px;font-weight:750;text-transform:uppercase;letter-spacing:.035em;text-align:center}.diagram-head span{border-radius:4px;padding:9px 6px;background:#eef5fb;color:#184c6b}.diagram-head span:nth-child(1){background:#e7f1f9}.diagram-head span:nth-child(3){background:#ffe8e7;color:#92333b}.diagram-head span:nth-child(5){background:#fff1e4;color:#91511a}.diagram-grid{row-gap:10px;align-items:stretch}.dcell{padding:10px 10px;min-height:95px;background:#fff;border:1px solid #cbdbe7;border-radius:6px;position:relative;overflow-wrap:anywhere;display:flex;flex-direction:column;justify-content:center;gap:5px;break-inside:avoid}.dcell strong{font-size:12px;line-height:1.36}.dcell small{color:#64748b;font-size:10px}.dcell.d-threat{border-left:4px solid #4b91bf;background:#f6fbff}.dcell.d-prevent{border-left:4px solid #22816b;background:#f5fcf8}.dcell.d-mitigate{border-left:4px solid #8d7ab6;background:#faf8ff}.dcell.d-outcome{border-left:4px solid #d18a38;background:#fffaf5}.dcell.d-threat:after,.dcell.d-prevent:after,.dcell.d-center:after,.dcell.d-mitigate:after{content:'→';position:absolute;right:-16px;top:50%;transform:translateY(-50%);color:#7794a7;font-size:17px;font-weight:800;z-index:2;pointer-events:none}.dcell.d-center{border:2px solid #df888d;background:#fff1f0;text-align:center;align-items:center;min-height:115px;padding:15px 11px}.d-center .hazard{color:#a64f28;background:#fff9ed;border:1px solid #eddfb7;padding:6px;border-radius:5px;font-size:10.5px;width:100%}.d-center strong{color:#912e38;font-size:16px}.d-center .event-chip{font-size:10px;text-transform:uppercase;color:#9b3441;letter-spacing:.08em;font-weight:800}.d-center .center-risk{font-size:10px;background:#fff;border-radius:4px;padding:5px;color:#65424a}.diagram-chip{display:block;border-radius:4px;background:#edf3f8;color:#325169;font-size:10.5px;padding:3px 6px;margin-top:2px;overflow-wrap:anywhere}.diagram-chip.ok{background:#e5f7ec;color:#146038}.diagram-chip.warn{background:#fff2d9;color:#875912}.diagram-chip.bad{background:#ffe5e8;color:#a92439}.diagram-chip.planned{background:#e8eff7;color:#475569}.diagram-chip.emptychip{background:#ffe5e8;color:#a92439;font-weight:750}.diagram-legend{display:flex;gap:7px;flex-wrap:wrap;margin-top:12px;color:#52677b;font-size:11px}.legend-item{padding:4px 7px;border:1px solid #d6e2ea;border-radius:4px;background:#fff}.quality{display:flex;gap:7px;flex-wrap:wrap}.quality span{border-radius:5px;padding:6px 8px;background:#fff7ea;border:1px solid #f0d8a8;color:#814d19;font-size:11px}.quality .allgood{color:#156534;background:#ebf8ef;border-color:#bce1c5}.tablebox{overflow-x:auto;max-width:100%}table{border-collapse:collapse;width:100%;min-width:860px;text-align:left}th,td{padding:9px 9px;border-bottom:1px solid #e5ecf2;vertical-align:top;font-size:11.5px;overflow-wrap:anywhere}th{background:#f2f7fb;text-align:left;color:#39566c;font-weight:800;font-size:11px}tbody tr:nth-child(even){background:#f8fbfd}.status-tag{display:inline-block;padding:3px 6px;border-radius:4px;font-size:10px;white-space:nowrap;font-weight:700}.status-active{color:#166534;background:#e5f7ec}.status-weak{color:#8d6010;background:#fff3d5}.status-failed{color:#aa2235;background:#ffe5e8}.status-planned{color:#475569;background:#edf2f7}.overdue{color:#b42332;font-weight:750}.foot{font-size:11px;color:#64748b;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:7px;margin:16px 0 0}.print-only{display:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}#toast{position:fixed;bottom:20px;right:20px;padding:10px 14px;background:#143d54;color:#fff;border-radius:6px;box-shadow:0 8px 30px #12283c35;opacity:0;pointer-events:none;transform:translateY(8px);transition:.2s;z-index:100;max-width:350px}#toast.show{opacity:1;transform:translateY(0)}.note{margin-top:8px;color:#64748b;font-size:11px}.print-table{width:100%;min-width:0}.print-table th,.print-table td{font-size:10px;padding:5px}.print-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}.print-kv{border:1px solid #dae4ec;border-radius:4px;padding:6px}.print-kv small{display:block;color:#64748b}.print-kv strong{font-size:11px;overflow-wrap:anywhere;white-space:pre-wrap}.print-title{font-size:13px;color:#075985;margin:12px 0 6px}.print-coverage{font-size:10px;color:#5a6674;margin:8px 0}
@media(max-width:1200px){.metrics{grid-template-columns:repeat(3,minmax(0,1fr))}.g6{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:800px){.branch-editor,.g4{grid-template-columns:1fr 1fr}.branch-editor{grid-template-columns:1fr}.g6{grid-template-columns:repeat(2,minmax(0,1fr))}.organization{font-size:13px}.diagram{min-width:980px}}@media(max-width:560px){.frame{margin:3px;min-height:calc(100vh - 6px)}.headerinner{padding:9px;flex-wrap:wrap;gap:8px}.logo{width:49px;height:49px}.header h1{font-size:15px}.header p{font-size:11px}.header-titles{flex:1 1 55%}.organization{flex:1 0 100%;max-width:100%;font-size:12px}.wrap{padding:9px}.action-row{padding:9px 9px 2px}.action-panel{width:100%;padding:8px}.action-panel .btn{flex:1 1 125px;white-space:normal}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.metric{padding:9px}.metric b{font-size:21px}.metric .icon{display:none}.g2,.g3,.g4,.g6{grid-template-columns:1fr}.span2,.span3{grid-column:auto}.body{padding:11px}.card-header{padding:9px 10px}.grid{gap:9px}.diagram{min-width:860px}}
@page{size:A4 landscape;margin:9mm}
@media print{html,body{margin:0!important;padding:0!important;background:#fff!important;color:#17283b!important;font-size:10px!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.frame{margin:0!important;min-height:0!important;border:1.5px solid #7f95a9!important;box-shadow:none!important;overflow:visible!important;border-radius:0!important;background:#fff!important}.header{position:static!important;box-shadow:none!important;border-top:4px solid #0284c7!important}.headerinner{min-height:64px;padding:7px 12px!important}.header h1{font-size:17px!important}.header p{font-size:11px!important}.organization{font-size:14px!important}.action-row,.screen-only,#toast,#logoFile{display:none!important}.wrap{padding:8px 10px!important}.print-only{display:block!important}.card{box-shadow:none!important;margin-bottom:9px!important;break-inside:auto!important}.card-header{break-after:avoid!important}.body{padding:9px!important}.diagram-scroll{overflow:visible!important}.diagram{min-width:0!important}.diagram-head,.diagram-grid{column-gap:10px!important;grid-template-columns:1.1fr 1.2fr 1.05fr 1.2fr 1.1fr!important}.diagram-grid{row-gap:7px!important}.dcell{padding:6px!important;min-height:68px!important;break-inside:avoid!important}.dcell strong{font-size:10px!important}.d-center strong{font-size:13px!important}.dcell small,.diagram-chip{font-size:9px!important}.dcell.d-threat:after,.dcell.d-prevent:after,.dcell.d-center:after,.dcell.d-mitigate:after{right:-13px!important;font-size:13px!important}.diagram-head span{padding:5px 3px!important;font-size:9px!important}.diagram-legend{font-size:9px!important}.print-table{width:100%!important;min-width:0!important;table-layout:fixed}.print-table td,.print-table th{font-size:9px!important;overflow-wrap:anywhere!important;word-break:break-word!important}.print-grid{grid-template-columns:repeat(4,1fr)!important}.quality span{font-size:9px!important;padding:4px!important}.foot{font-size:9px!important}tr{break-inside:avoid!important}#diagramSection{break-before:auto}}
/* REFERANS RISK UI – HAZOP ile uyumlu kabuk, kurumsal başlık, palet ve alt işlem paneli */
:root{--bg:#f1f6fa;--paper:#fff;--line:#c9d8e4;--text:#183146;--muted:#5d7282;--blue:#087dae;--navy:#08658e;--pale:#e9f5fd;--radius:7px;--shadow:0 2px 7px #17405a0a}
body{background:#eaf1f6;font:13px/1.43 'Segoe UI',Arial,sans-serif;color:var(--text)}
.frame{border-color:#8ca7bc;background:#f9fcfe;box-shadow:0 4px 18px #23425a15;overflow:visible}
.header{border-top-color:#0579ad;box-shadow:none;border-bottom-color:#bed1df}
.headerinner{min-height:75px;padding:8px 17px;gap:13px}
.logo{width:61px;height:57px;flex:0 0 61px;background:#f6fbff;border-color:#cbd9e4;color:#11658b}
.header h1{color:#08658e;font-size:19px;line-height:1.22}
.header p{font-size:11px;color:#4d6576;margin:3px 0 0}
.organization{max-width:35%;font-size:15px;letter-spacing:0;font-weight:800;color:#23455e}
.wrap{padding:12px 17px 7px}
.card{border-color:#c9d8e4;box-shadow:0 2px 7px #17405a0a;margin-bottom:10px}
.card-header{background:#e9f5fd;border-left:4px solid #087dae;border-bottom:1px solid #c2ddea;padding:9px 12px}
.card-header h2{font-size:14px;color:#0b587c}
.section-number{background:#d2ecfb;color:#135d88;font-size:10px}
.body{padding:11px 13px}
.metric{border-color:#c7d8e3;border-top-color:#0c83bd;padding:9px 12px}
.metric small{color:#5b7386}
.metric b{color:#164766;font-size:23px}
input,select,textarea{border-color:#c8d6e1;padding:7px 8px;min-height:34px}
label{color:#29465c;font-size:11.5px;margin-bottom:4px}
.btn{border-color:#c5d4df;padding:7px 10px;min-height:33px;font-weight:700;color:#2c4a5c}
.btn-soft{background:#e7f5ff;border-color:#b8dbf4;color:#0b5881}
.btn-green{background:#e7f8ee;border-color:#b5e0c6;color:#17633d}
.btn-small{font-size:10.5px;padding:4px 7px;min-height:27px}
.bottom-actions{padding:5px 17px 4px;display:flex}
.bottom-panel{border:1px solid #bed4e1;background:#fff;border-radius:6px;padding:8px 10px;display:flex;gap:9px;flex-wrap:wrap}
.footerline{white-space:nowrap;overflow-x:auto;font-size:11px;border-top:1px solid #d7e4ec;margin:0 17px;padding:5px 0 4px;color:#4c6074;line-height:1.35}
@media(max-width:720px){.headerinner{padding:8px 10px;flex-wrap:wrap}.logo{flex:0 0 49px;width:49px;height:49px}.header h1{font-size:15px}.header p{font-size:10px}.organization{max-width:100%;width:100%;font-size:11px;text-align:left}.wrap{padding:9px}.bottom-actions{padding:5px 9px}.bottom-panel{width:100%;gap:5px}.bottom-panel .btn{flex:1 1 110px;white-space:normal}.footerline{font-size:10px;margin:0 9px}}
/* Referans formdaki sabit rapor çerçevesi, tekrarlanan başlık, A4 yatay */
#printPages,#printPageFrame{display:none}
@page{size:A4 landscape;margin:6mm 7mm 7mm 7mm}
@media print{
html,body{margin:0!important;padding:0!important;background:#fff!important;color:#183146!important;font-size:8.5px!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
#uiShell,#toast{display:none!important}
#printPages{display:table!important;width:100%!important;border:0!important;border-collapse:separate!important;border-spacing:0!important;table-layout:fixed}
#printPages>thead{display:table-header-group!important}#printPages>tbody{display:table-row-group!important}
#printPages>thead>tr>td{padding:0 0 3px!important}#printPages>tbody>tr>td{padding:0!important}
#printPageFrame{display:block!important;position:fixed;inset:0;border:1.5px solid #809bb2;pointer-events:none;z-index:15}
.p-header{display:flex;align-items:center;gap:8px;min-height:49px;padding:3px 8px;border-top:4px solid #0c84b8;border-bottom:1px solid #abc8d9;background:#fff!important}
.p-header img{width:43px;height:40px;object-fit:contain}.p-header h1{font-size:14px;margin:0;color:#06648d}.p-header p{font-size:9.5px;margin:3px 0;color:#526879}.p-org{margin-left:auto;max-width:35%;text-align:right;font-size:12px;font-weight:800;overflow-wrap:anywhere}
#printContent{line-height:1.27!important}
.p-section{border:1px solid #b9cdda;margin-bottom:4px;break-inside:auto;page-break-inside:auto}
.p-section h3{font-size:10.5px;background:#eaf5fc!important;border-left:4px solid #067fb3;color:#075d81;margin:0;padding:4px 7px;break-after:avoid-page;page-break-after:avoid}
.p-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:3px;padding:4px}
.p-field{border:1px solid #dce8ef;padding:3px 4px;border-radius:3px;overflow-wrap:anywhere;white-space:pre-wrap;font-size:8px;min-height:24px}
.p-field b{display:block;color:#667d8e;font-size:7.5px;margin-bottom:1px}
.p-note{font-size:7.5px;color:#5b7082;padding:3px 6px;line-height:1.25}
.p-risk{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px;padding:5px}
.p-risk>div{border:1px solid #d6e3eb;padding:5px 8px;font-size:9px}
.p-risk strong{font-size:13px;color:#145c7e}
.p-diagram{padding:4px;break-inside:avoid-page;page-break-inside:avoid}
.p-diagram .diagram{width:100%!important;min-width:0!important}
.p-diagram .diagram-head,.p-diagram .diagram-grid{column-gap:5px!important;grid-template-columns:1.1fr 1.2fr 1.05fr 1.2fr 1.1fr!important}
.p-diagram .diagram-grid{row-gap:4px!important}
.p-diagram .diagram-head{margin:0 0 4px!important}
.p-diagram .diagram-head span{padding:3px 1px!important;font-size:7px!important}
.p-diagram .dcell{padding:3px!important;min-height:43px!important;border-radius:3px!important;overflow-wrap:anywhere!important;break-inside:avoid!important}
.p-diagram .dcell strong{font-size:8px!important}.p-diagram .d-center strong{font-size:9px!important}
.p-diagram .dcell small,.p-diagram .diagram-chip{font-size:7px!important;line-height:1.25!important;padding:2px!important}
.p-diagram .dcell.d-threat:after,.p-diagram .dcell.d-prevent:after,.p-diagram .dcell.d-center:after,.p-diagram .dcell.d-mitigate:after{right:-9px!important;font-size:10px!important}
.p-table{width:100%;table-layout:fixed;border-collapse:collapse}
.p-table th,.p-table td{border:1px solid #d5e4ed;padding:3px 4px;font-size:7.6px;vertical-align:top;overflow-wrap:anywhere}
.p-table th{background:#eaf4fa!important;color:#105f84}
.p-table thead{display:table-header-group}.p-table tr{break-inside:avoid-page;page-break-inside:avoid}
.p-detail{display:block;margin:2px 3px;break-inside:auto;page-break-inside:auto}
.p-detail-table{width:100%;border-collapse:separate;border-spacing:2px 0;table-layout:fixed;break-inside:auto}
.p-detail-table thead{display:table-header-group!important}
.p-detail-table th{padding:0!important;font-size:9px;text-align:left;border:0}
.p-detail-table th .p-detail-head{padding:3px 4px;background:#eaf5fa!important;line-height:1.2;font-size:8.2px;color:#145c7e;break-after:avoid-page}
.p-detail-table tr{break-inside:avoid-page;page-break-inside:avoid}
.p-detail-table td{vertical-align:top;padding:0;border:0;overflow-wrap:anywhere;line-height:1.25}
.p-detail-table td .p-field{height:100%;padding:2px 3px;font-size:7.6px;break-inside:avoid-page}
}
</style>
</head>
<body>
<div class="frame" id="uiShell">
<header class="header"><div class="headerinner">
<button type="button" id="logoTrigger" class="logo" title="Logoyu değiştirmek için 5 kez art arda tıklayın"><img id="logoImage" alt="Kurum logosu" hidden><span id="logoFallback">LOGO</span></button>
<div class="header-titles"><h1 id="brandTitle" title="Başlığı düzenlemek için 5 kez tıklayın">BOW-TIE (PAPYON) RİSK ANALİZİ</h1><p id="brandSubtitle" title="Alt başlığı düzenlemek için 5 kez tıklayın">Tehlike · Kritik Olay · Tehditler · Bariyerler · Sonuçlar · Aksiyon Takibi</p></div>
<div id="brandOrg" class="organization" title="Kurum adını düzenlemek için 5 kez tıklayın">OLGUN BAŞKANLIĞI</div>
</div><input id="logoFile" class="sr-only" type="file" accept="image/png,image/jpeg,image/webp,image/gif" aria-label="Kurum logosu seç"></header>
<main class="wrap">
<section class="grid metrics screen-only" aria-label="Özet göstergeler">
<div class="card metric"><div><small>TEHDİT / NEDEN</small><b id="statThreats">0</b></div><span class="icon">↘</span></div>
<div class="card metric"><div><small>OLASI SONUÇ</small><b id="statCons">0</b></div><span class="icon">↗</span></div>
<div class="card metric"><div><small>TOPLAM BARİYER</small><b id="statBarriers">0</b></div><span class="icon">▥</span></div>
<div class="card metric"><div><small>DOĞRULANMIŞ ETKİN</small><b id="statVerified">0</b></div><span class="icon">✓</span></div>
<div class="card metric"><div><small>KORUMASIZ DAL</small><b id="statGaps">0</b></div><span class="icon">!</span></div>
<div class="card metric"><div><small>GECİKEN AKSİYON</small><b id="statOverdue">0</b></div><span class="icon">⌛</span></div>
</section>
<section class="card screen-only" aria-labelledby="s1"><div class="card-header"><h2 id="s1"><span class="section-number">01</span> Analiz / tesis bilgileri</h2><span class="hint">Analiz kayıtları yenilemede sıfırlanır; verilerinizi Excel Dışarı Aktar ile yedekleyin.</span></div>
<div class="body grid g4">
<div><label for="m_ref">Analiz / kayıt no.</label><input id="m_ref" data-meta="ref" placeholder="Örn. BT-2026-001"></div>
<div><label for="m_company">İş yeri / kuruluş</label><input id="m_company" data-meta="company" placeholder="İş yeri adı"></div>
<div><label for="m_facility">Tesis / ünite / proses</label><input id="m_facility" data-meta="facility" placeholder="Örn. Kimyasal işleme ünitesi"></div>
<div><label for="m_equipment">Ekipman / faaliyet</label><input id="m_equipment" data-meta="equipment" placeholder="Hat, tank veya süreç"></div>
<div><label for="m_date">Analiz tarihi</label><input id="m_date" data-meta="date" type="date"></div>
<div><label for="m_preparer">Hazırlayan / ekip</label><input id="m_preparer" data-meta="preparer"></div>
<div><label for="m_approver">Gözden geçiren / onaylayan</label><input id="m_approver" data-meta="approver"></div>
<div><label for="m_revision">Revizyon / sürüm</label><input id="m_revision" data-meta="revision" placeholder="Örn. R00"></div>
<div class="wide"><label for="m_scope">Kapsam, sınırlar ve varsayımlar</label><textarea id="m_scope" data-meta="scope" placeholder="Analizin kapsadığı faaliyet, operasyon koşulları ve hariç tutulan durumlar"></textarea></div>
</div></section>
<section class="card screen-only" aria-labelledby="s2"><div class="card-header"><h2 id="s2"><span class="section-number">02</span> Tehlike ve kritik olay (Top Event)</h2></div><div class="body grid g2">
<div><label for="hazard">Tehlike / tehlikeli enerji veya madde</label><textarea id="hazard" data-meta="hazard" placeholder="Örn. Basınç altındaki toksik kimyasal"></textarea></div>
<div><label for="topEvent">Kritik olay / kontrol kaybı</label><textarea id="topEvent" data-meta="topEvent" placeholder="Örn. Boru hattı bütünlüğünün kaybı ve kontrolsüz salım"></textarea></div>
<div><label for="criterion">Kritik olayın gerçekleşme ölçütü</label><textarea id="criterion" data-meta="criterion" placeholder="Top Event'i somut olarak hangi koşul tanımlar?"></textarea></div>
<div><label for="recovery">Acil durum ve iyileşme yaklaşımı</label><textarea id="recovery" data-meta="recovery" placeholder="Tahliye, izolasyon, olay sonrası toparlanma; mevcut prosedürlerle ilişki"></textarea></div>
<div class="wide callout">Bow-Tie mantığı: <b>tehdit / neden → önleyici bariyer → kritik olay → sonuç azaltıcı bariyer → sonuç.</b> Önleyici bariyerler kritik olayın oluşmasını engellemeye; azaltıcı bariyerler kritik olay sonrası etkileri sınırlamaya yöneliktir. Bir bariyerin yalnızca listelenmesi, onun etkin olduğunu kanıtlamaz.</div>
</div></section>
<section class="card screen-only" aria-labelledby="s3"><div class="card-header"><h2 id="s3"><span class="section-number">03</span> Öncesi ve sonrası risk değerlendirmesi</h2><span class="hint">Örnek 5×5 ölçek; kurumun onaylı risk kriterlerine göre uyarlayın.</span></div><div class="body">
<div class="grid g2">
<div class="score-card"><h3>Bariyerler öncesi / başlangıç riski</h3><div class="score-inner"><div class="grid g2">
<div><label for="beforeP">Olasılık</label><select id="beforeP" data-score="beforeP"><option value="">Seçiniz</option><option value="1">1 · Çok düşük</option><option value="2">2 · Düşük</option><option value="3">3 · Orta</option><option value="4">4 · Yüksek</option><option value="5">5 · Çok yüksek</option></select></div>
<div><label for="beforeS">Şiddet</label><select id="beforeS" data-score="beforeS"><option value="">Seçiniz</option><option value="1">1 · Önemsiz</option><option value="2">2 · Hafif</option><option value="3">3 · Orta</option><option value="4">4 · Ciddi</option><option value="5">5 · Çok ciddi</option></select></div>
</div><div id="beforeResult" class="score-result"><span>Risk puanı</span><strong>—</strong></div></div></div>
<div class="score-card"><h3>Bariyerler sonrası / artık risk</h3><div class="score-inner"><div class="grid g2">
<div><label for="afterP">Olasılık</label><select id="afterP" data-score="afterP"><option value="">Seçiniz</option><option value="1">1 · Çok düşük</option><option value="2">2 · Düşük</option><option value="3">3 · Orta</option><option value="4">4 · Yüksek</option><option value="5">5 · Çok yüksek</option></select></div>
<div><label for="afterS">Şiddet</label><select id="afterS" data-score="afterS"><option value="">Seçiniz</option><option value="1">1 · Önemsiz</option><option value="2">2 · Hafif</option><option value="3">3 · Orta</option><option value="4">4 · Ciddi</option><option value="5">5 · Çok ciddi</option></select></div>
</div><div id="afterResult" class="score-result"><span>Risk puanı</span><strong>—</strong></div></div></div>
</div><p class="note">Gösterge sınıfları (örnek): 1–5 düşük, 6–10 orta, 11–15 yüksek, 16–25 çok yüksek. Bu sınıflama kurum politikasının veya mevzuattaki yöntemin yerine geçmez; artık risk otomatik olarak düşürülmez, değerlendirici tarafından ayrıca girilir.</p>
</div></section>
<section class="card screen-only" aria-labelledby="s4"><div class="card-header"><h2 id="s4"><span class="section-number">04</span> Tehditler, sonuçlar ve bariyer kütüğü</h2><span class="hint">Her dal için istediğiniz sayıda bariyer ekleyin.</span></div><div class="body">
<div class="branch-editor">
<section class="branch-side left" aria-label="Önleyici taraf"><div class="subhead"><h3>Sol kanat · Tehdit / neden + önleyici bariyer</h3><span id="threatCount" class="count">0</span></div><div id="threatEditor"></div><div class="block-footer"><button type="button" class="btn btn-soft btn-small" data-action="add-item" data-side="threats">+ Tehdit ekle</button></div></section>
<section class="branch-side right" aria-label="Azaltıcı taraf"><div class="subhead"><h3>Sağ kanat · Sonuç + azaltıcı bariyer</h3><span id="consCount" class="count">0</span></div><div id="consequenceEditor"></div><div class="block-footer"><button type="button" class="btn btn-soft btn-small" data-action="add-item" data-side="consequences">+ Sonuç ekle</button></div></section>
</div><p class="note">Bariyer alanları: durum, saha doğrulaması, sorumlu, son kontrol tarihi, zayıflatıcı etken, koruyucu önlem, düzeltici faaliyet ve hedef tarih. Sıra numaraları her iki kanat boyunca tek sıra hâlinde ilerler. Excel'deki Dal ID tekrarı, aynı dala bağlı birden fazla bariyerin ilişkisini göstermek için kullanılır; sıra numarası değildir.</p>
</div></section>
<section id="printSummary" class="card print-only" aria-label="Yazdırma analizi özeti"></section>
<section class="card" id="diagramSection" aria-labelledby="s5"><div class="card-header"><h2 id="s5"><span class="section-number">05</span> Dinamik Bow-Tie / Papyon diyagramı</h2><span class="hint screen-only">Alanları düzenledikçe otomatik güncellenir · Dar ekranda diyagram kendi içinde kaydırılır.</span></div><div class="body">
<div class="diagram-scroll"><div class="diagram" id="diagramContent"></div></div>
<div class="diagram-legend"><span class="legend-item">↘ Sol kanat: kritik olayın oluşmasını <b>önleme</b></span><span class="legend-item">↗ Sağ kanat: olayın etkisini <b>azaltma</b></span><span class="legend-item">✓ Etkin + doğrulanmış</span><span class="legend-item">! Etkisiz / eksik / doğrulanmamış bariyer uyarısı</span></div>
</div></section>
<section class="card screen-only" aria-labelledby="s6"><div class="card-header"><h2 id="s6"><span class="section-number">06</span> Bariyer güvence durumu ve kontrol boşlukları</h2></div><div class="body"><div id="quality" class="quality"></div><p class="note">Bu otomatik kontroller yalnızca veri alanlarının doldurulması ve bariyer doğrulama durumları üzerinden uyarı üretir. Fiziksel bağımsızlık, yeterlilik veya tasarım uygunluğu için uzman saha incelemesi gereklidir.</p></div></section>
<section class="card screen-only" aria-labelledby="s7"><div class="card-header"><h2 id="s7"><span class="section-number">07</span> Bariyer doğrulama / düzeltici faaliyet takibi</h2><span class="hint">Bütün bariyerler listelenir; geciken açık faaliyetler işaretlenir.</span></div><div class="body"><div class="tablebox" id="actionTable"></div></div></section>
<section class="card print-only" id="printAppend" aria-label="Yazdırma bariyer ve aksiyon kayıtları"></section>
<footer class="foot"><span>RISK UI · Bow-Tie (Papyon) yöntemi · Tek dosyalık çevrimdışı uygulama</span><span>Karar desteği amaçlıdır; teknik doğrulama ve yetkili onay gerektirir.</span></footer>
</main>
<div class="bottom-actions"><div class="bottom-panel" aria-label="Dosya ve yazdırma işlemleri">
<button type="button" class="btn btn-soft" id="importBtn">⬆ Excel İçeri Aktar</button>
<button type="button" class="btn btn-green" id="exportBtn">⬇ Excel Dışarı Aktar</button>
<button type="button" class="btn" id="printBtn">▣ PDF / Yazdır</button>
<input type="file" class="sr-only" id="fileIn" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" aria-label="Bow-Tie Excel çalışma kitabını içeri aktar">
</div></div>
<footer class="footerline">Temel özelliği: Tehlike → Tehdit / neden → Önleyici bariyer → Kritik olay → Azaltıcı bariyer → Sonuç Kullanım: Endüstriyel proses ve yüksek sonuçlu riskler Yardımcı risk: Olasılık (1–5) × Şiddet (1–5)</footer>
</div>
<table id="printPages" role="presentation"><thead><tr><td><div id="printHeader"></div></td></tr></thead><tbody><tr><td><div id="printContent"></div></td></tr></tbody></table><div id="printPageFrame"></div>
<div id="toast" role="status" aria-live="polite"></div>
<script>
'use strict';
const $=id=>document.getElementById(id);
const BRAND_KEY='risk-ui-bow-tie-brand-v1';
const BRAND_DEFAULT={title:'BOW-TIE (PAPYON) RİSK ANALİZİ',subtitle:'Tehlike · Kritik Olay · Tehditler · Bariyerler · Sonuçlar · Aksiyon Takibi',org:'OLGUN BAŞKANLIĞI',logo:''};
const META_KEYS=['ref','company','facility','equipment','date','preparer','approver','revision','scope','hazard','topEvent','criterion','recovery'];
const SCORE_KEYS=['beforeP','beforeS','afterP','afterS'];
const stateFactory=()=>({schema:'risk-ui-bow-tie-v1',meta:Object.fromEntries(META_KEYS.map(k=>[k,''])),scores:Object.fromEntries(SCORE_KEYS.map(k=>[k,''])),threats:[],consequences:[]});
let state=stateFactory();
let brand={...BRAND_DEFAULT};
const today=()=>{const d=new Date();return [d.getFullYear(),String(d.getMonth()+1).padStart(2,'0'),String(d.getDate()).padStart(2,'0')].join('-')};
const uid=()=> 'i'+(globalThis.crypto?.randomUUID?.().replace(/-/g,'')||Math.random().toString(36).slice(2,11));
// Görüntüdeki sıra numaraları Excel Dal/Bariyer ID alanlarından bağımsızdır.
// Her iki kanatta 1,2,3... olarak tek sıra kullanılır; ilişki kimlikleri tekrar edebilir.
function globalBranchNumber(side,index){return side==='threats'?index+1:state.threats.length+index+1}
function globalBarrierNumber(side,branchIndex,barrierIndex){
let n=1;
if(side==='consequences')n+=state.threats.reduce((sum,b)=>sum+b.barriers.length,0);
for(let k=0;k<branchIndex;k++)n+=state[side][k].barriers.length;
return n+barrierIndex;
}
const safe=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const val=x=>String(x??'');
const cleanName=x=>val(x).trim()||'Belirtilmedi';
const printVal=x=>safe(val(x).trim()||'—').replace(/\n/g,'<br>');
const statuses={planned:'Planlandı',active:'Etkin',weak:'Kısmen etkin',failed:'Devre dışı'};
let toastTimer;
function toast(message){const el=$('toast');el.textContent=message;el.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>el.classList.remove('show'),2900)}
function fileDownload(name,body,mime){const blob=new Blob([body],{type:mime});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=name;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1600)}
function slug(){return val(state.meta.ref).trim().replace(/[^a-z0-9_-]/gi,'_').slice(0,35)||'Bow_Tie'}
function newBarrier(){return {id:uid(),name:'',status:'planned',verified:false,owner:'',checkedAt:'',degrader:'',safeguard:'',action:'',dueDate:''}}
function newItem(){return {id:uid(),title:'',detail:'',barriers:[]}}
function allBranches(){return [...state.threats.map((b,i)=>({...b,side:'threats',index:i})),...state.consequences.map((b,i)=>({...b,side:'consequences',index:i}))]}
function allBarriers(){return allBranches().flatMap(b=>b.barriers.map(barrier=>({...barrier,side:b.side,branch:b.title,branchIndex:b.index}))) }
function isVerified(b){return b.status==='active'&&b.verified===true}
function overdue(b){return !!(b.dueDate&&b.dueDate<today()&&(!isVerified(b)||val(b.action).trim()))}
function gapBranches(){return allBranches().filter(b=>!b.barriers.some(isVerified))}
function risk(p,s){let a=Number(p),b=Number(s);if(!p||!s||!Number.isInteger(a)||!Number.isInteger(b)||a<1||a>5||b<1||b>5)return {score:null,desc:'Değerlendirilmedi',klass:'risk-unknown'};let n=a*b;return {score:n,desc:n<=5?'Düşük':n<=10?'Orta':n<=15?'Yüksek':'Çok yüksek',klass:n<=5?'risk-low':n<=10?'risk-medium':n<=15?'risk-high':'risk-critical'}}
function refreshScores(){for(const side of ['before','after']){const r=risk(state.scores[side+'P'],state.scores[side+'S']);$(side+'Result').innerHTML=`<span>Risk puanı</span><strong>${r.score??'—'}</strong><span class="risk-tag ${r.klass}">${r.desc}</span>`}}
function refreshStats(){const b=allBarriers();const data={statThreats:state.threats.length,statCons:state.consequences.length,statBarriers:b.length,statVerified:b.filter(isVerified).length,statGaps:gapBranches().length,statOverdue:b.filter(overdue).length};Object.entries(data).forEach(([k,v])=>$(k).textContent=v);refreshScores();renderDiagram();renderQuality();renderActions()}
function inputField(label,field,value,attrs=''){return `<label>${label}<input type="text" data-field="${field}" value="${safe(value)}" ${attrs}></label>`}
function selectStatus(b){return `<label>Durum<select data-bfield="status">${Object.entries(statuses).map(([v,n])=>`<option value="${v}" ${b.status===v?'selected':''}>${n}</option>`).join('')}</select></label>`}
function renderBarrier(b,side,i,j){return `<div class="barrier" data-side="${side}" data-i="${i}" data-j="${j}">
<div class="barrier-head"><h5>${side==='threats'?'Önleyici':'Azaltıcı'} bariyer #${globalBarrierNumber(side,i,j)}</h5><button class="btn btn-danger btn-small" type="button" data-action="remove-barrier" data-side="${side}" data-i="${i}" data-j="${j}" aria-label="Bariyeri sil">× Sil</button></div>
<div class="grid g2"><div class="wide"><label>Bariyer tanımı<input data-bfield="name" value="${safe(b.name)}" placeholder="Örn. Basınç alarmı + otomatik izolasyon"></label></div>
<div>${selectStatus(b)}</div><div><label>Sorumlu / birim<input data-bfield="owner" value="${safe(b.owner)}" placeholder="Bariyer sorumlusu"></label></div>
<div><label>Son saha kontrolü<input type="date" data-bfield="checkedAt" value="${safe(b.checkedAt)}"></label></div>
<div class="checkrow"><input data-bfield="verified" id="v_${side}_${i}_${j}" type="checkbox" ${b.verified?'checked':''}><label for="v_${side}_${i}_${j}">Sahada doğrulandı</label></div>
<div><label>Zayıflatıcı etken / bozulma mekanizması<textarea data-bfield="degrader" placeholder="Yanlış bakım, ortak neden, güç kesintisi…">${safe(b.degrader)}</textarea></label></div>
<div><label>Zayıflamaya karşı koruyucu önlem<textarea data-bfield="safeguard" placeholder="Test, periyodik bakım, yedeklilik…">${safe(b.safeguard)}</textarea></label></div>
<div class="wide"><label>Aksiyon / gerekli iyileştirme<textarea data-bfield="action" placeholder="Eksikliği giderme, test etme veya iyileştirme">${safe(b.action)}</textarea></label></div>
<div><label>Hedef tarih<input type="date" data-bfield="dueDate" value="${safe(b.dueDate)}"></label></div>
</div></div>`}
function renderBranchItem(item,side,i){const noun=side==='threats'?'Tehdit / neden':'Olası sonuç';return `<article class="branch-item" data-side="${side}" data-i="${i}">
<div class="item-head"><h4>${noun} #${globalBranchNumber(side,i)}</h4><button type="button" class="btn btn-danger btn-small" data-action="remove-item" data-side="${side}" data-i="${i}">× Dalı sil</button></div>
<div class="grid"><label>${noun} başlığı<input data-field="title" value="${safe(item.title)}" placeholder="${side==='threats'?'Örn. Korozyon nedeniyle boru delinmesi':'Örn. Personelin toksik maddeye maruziyeti'}"></label>
<label>Açıklama / senaryo<textarea data-field="detail" placeholder="Kapsam ve gelişim koşulları">${safe(item.detail)}</textarea></label></div>
<div class="subhead" style="margin-top:12px"><h4 style="margin:0;font-size:12px">${side==='threats'?'Önleyici':'Azaltıcı'} bariyerler</h4><span class="count">${item.barriers.length}</span></div>
${item.barriers.length?item.barriers.map((b,j)=>renderBarrier(b,side,i,j)).join(''):'<div class="empty">Henüz bariyer eklenmedi.</div>'}
<div class="block-footer"><button type="button" class="btn btn-soft btn-small" data-action="add-barrier" data-side="${side}" data-i="${i}">+ Bariyer ekle</button></div>
</article>`}
function renderEditors(){ $('threatCount').textContent=state.threats.length;$('consCount').textContent=state.consequences.length;
$('threatEditor').innerHTML=state.threats.length?state.threats.map((x,i)=>renderBranchItem(x,'threats',i)).join(''):'<div class="empty">Tehdit / neden dalı ekleyerek başlayın.</div>';
$('consequenceEditor').innerHTML=state.consequences.length?state.consequences.map((x,i)=>renderBranchItem(x,'consequences',i)).join(''):'<div class="empty">Olası sonuç dalı ekleyerek başlayın.</div>';
document.querySelectorAll('.branch-editor input, .branch-editor textarea, .branch-editor select').forEach(el=>el.setAttribute('autocomplete','off'));
}
function barrierChip(b){const cls=isVerified(b)?'ok':b.status==='failed'?'bad':b.status==='weak'?'warn':'planned';return `<span class="diagram-chip ${cls}">${isVerified(b)?'✓':b.status==='failed'?'✕':b.status==='weak'?'!':'○'} ${safe(cleanName(b.name))} <small>(${safe(statuses[b.status]||'Bilinmiyor')}${b.verified?' · doğrulandı':''})</small></span>`}
function diagramCell(side,item,i){let threat=side==='threats',barriers=item?.barriers||[],name=item?.title||`${threat?'Tehdit':'Sonuç'} tanımlanmadı`,detail=item?.detail||'';
return `<div class="dcell ${threat?'d-threat':'d-outcome'}" style="grid-column:${threat?1:5};grid-row:${i+1}"><strong>${safe(name)}</strong>${detail?`<small>${safe(detail).slice(0,260)}</small>`:''}</div><div class="dcell ${threat?'d-prevent':'d-mitigate'}" style="grid-column:${threat?2:4};grid-row:${i+1}">${barriers.length?barriers.map(barrierChip).join(''):'<span class="diagram-chip emptychip">! Bariyer tanımlanmadı</span>'}</div>`}
function diagramMarkup(){const count=Math.max(state.threats.length,state.consequences.length,1);const br=risk(state.scores.beforeP,state.scores.beforeS),ar=risk(state.scores.afterP,state.scores.afterS);
let rows='';for(let i=0;i<count;i++){
if(state.threats[i])rows+=diagramCell('threats',state.threats[i],i);else rows+=`<div class="dcell d-threat" style="grid-column:1;grid-row:${i+1};opacity:.35"><small>—</small></div><div class="dcell d-prevent" style="grid-column:2;grid-row:${i+1};opacity:.35"><small>—</small></div>`;
if(state.consequences[i])rows+=diagramCell('consequences',state.consequences[i],i);else rows+=`<div class="dcell d-mitigate" style="grid-column:4;grid-row:${i+1};opacity:.35"><small>—</small></div><div class="dcell d-outcome" style="grid-column:5;grid-row:${i+1};opacity:.35"><small>—</small></div>`;
}
return `<div class="diagram-head"><span>1 · Tehditler / nedenler</span><span>2 · Önleyici bariyerler</span><span>3 · Kritik olay</span><span>4 · Azaltıcı bariyerler</span><span>5 · Sonuçlar</span></div>
<div class="diagram-grid">${rows}<div class="dcell d-center" style="grid-column:3;grid-row:1 / span ${count}"><span class="hazard"><b>TEHLİKE</b><br>${safe(state.meta.hazard||'Tehlike tanımlanmadı')}</span><span class="event-chip">TOP EVENT · KONTROL KAYBI</span><strong>${safe(state.meta.topEvent||'Kritik olay tanımlanmadı')}</strong>${state.meta.criterion?`<small>${safe(state.meta.criterion).slice(0,230)}</small>`:''}<div class="center-risk">İlk risk: <b>${br.score??'—'}</b> · Artık risk: <b>${ar.score??'—'}</b></div></div></div>`;
}
function renderDiagram(){ $('diagramContent').innerHTML=diagramMarkup() }
function renderQuality(){const warn=[];if(!state.meta.hazard.trim())warn.push('Tehlike açıklaması eksik');if(!state.meta.topEvent.trim())warn.push('Kritik olay tanımı eksik');if(!state.threats.length)warn.push('Tehdit / neden eklenmedi');if(!state.consequences.length)warn.push('Olası sonuç eklenmedi');const t=state.threats.filter(x=>!x.barriers.some(isVerified)).length,c=state.consequences.filter(x=>!x.barriers.some(isVerified)).length;if(t)warn.push(`${t} tehdit dalında doğrulanmış etkin önleyici bariyer yok`);if(c)warn.push(`${c} sonuç dalında doğrulanmış etkin azaltıcı bariyer yok`);if(!state.scores.afterP||!state.scores.afterS)warn.push('Artık risk henüz değerlendirilmedi');$('quality').innerHTML=warn.length?warn.map(w=>`<span>! ${safe(w)}</span>`).join(''):'<span class="allgood">✓ Temel veri alanları ve bariyer doğrulama kayıtları tamam görünüyor; bağımsız teknik saha doğrulaması yine gereklidir.</span>'}
function renderActions(){const b=allBarriers();if(!b.length){$('actionTable').innerHTML='<div class="empty">İzlenecek bariyer bulunmuyor. Dallara bariyer ekleyin.</div>';return}
$('actionTable').innerHTML=`<table><thead><tr><th>Sıra No.</th><th>Kanat / dal</th><th>Bariyer</th><th>Durum / doğrulama</th><th>Sorumlu</th><th>Aksiyon</th><th>Hedef tarih</th></tr></thead><tbody>${b.map((v,i)=>`<tr><td>${i+1}</td><td>${v.side==='threats'?'Önleyici':'Azaltıcı'}<br><span class="muted">${safe(cleanName(v.branch))}</span></td><td>${safe(cleanName(v.name))}</td><td><span class="status-tag status-${safe(v.status)}">${safe(statuses[v.status]||'Bilinmiyor')}</span><br>${isVerified(v)?'✓ Saha doğrulandı':'! Etkinlik doğrulanmadı'}</td><td>${safe(v.owner||'—')}</td><td>${safe(v.action||'—')}</td><td>${v.dueDate?safe(v.dueDate):'—'}${overdue(v)?'<br><span class="overdue">HEDEF TARİH GEÇTİ</span>':''}</td></tr>`).join('')}</tbody></table>`}
function renderAll(){for(const el of document.querySelectorAll('[data-meta]'))el.value=val(state.meta[el.dataset.meta]);for(const el of document.querySelectorAll('[data-score]'))el.value=val(state.scores[el.dataset.score]);renderEditors();refreshStats()}
function normalizeImport(obj){if(!obj||typeof obj!=='object'||obj.schema!=='risk-ui-bow-tie-v1')throw new Error('Risk UI Bow-Tie v1 biçiminde olmayan dosya.');const out=stateFactory();for(const k of META_KEYS)out.meta[k]=typeof obj.meta?.[k]==='string'?obj.meta[k].slice(0,5000):'';for(const k of SCORE_KEYS){const v=val(obj.scores?.[k]);out.scores[k]=/^[1-5]$/.test(v)?v:''}
for(const s of ['threats','consequences']){if(!Array.isArray(obj[s])||obj[s].length>120)throw new Error('Dal bilgisi geçersiz veya aşırı uzun.');out[s]=obj[s].map(raw=>{if(!raw||!Array.isArray(raw.barriers)||raw.barriers.length>40)throw new Error('Bariyer yapısı geçersiz.');return {id:uid(),title:val(raw.title).slice(0,1000),detail:val(raw.detail).slice(0,2500),barriers:raw.barriers.map(a=>({id:uid(),name:val(a.name).slice(0,1000),status:statuses[a.status]?a.status:'planned',verified:a.verified===true,owner:val(a.owner).slice(0,500),checkedAt:/^\d{4}-\d{2}-\d{2}$/.test(a.checkedAt)?a.checkedAt:'',degrader:val(a.degrader).slice(0,2000),safeguard:val(a.safeguard).slice(0,2000),action:val(a.action).slice(0,2000),dueDate:/^\d{4}-\d{2}-\d{2}$/.test(a.dueDate)?a.dueDate:''}))}})}
return out}
function confirmReplace(){return !state.threats.length&&!state.consequences.length&&!META_KEYS.some(k=>state.meta[k].trim())||window.confirm('Mevcut form içeriği Excel dosyasındaki kayıtlarla değiştirilecek. Önce Excel yedeği aldınız mı?')}
function initBindings(){document.addEventListener('input',event=>{const e=event.target;if(e.dataset.meta){state.meta[e.dataset.meta]=e.value;refreshStats();return}if(e.dataset.score){state.scores[e.dataset.score]=e.value;refreshStats();return}
const parent=e.closest('.branch-item');if(!parent)return;const side=parent.dataset.side,i=Number(parent.dataset.i),b=state[side]?.[i];if(!b)return;if(e.dataset.field)b[e.dataset.field]=e.value;const bParent=e.closest('.barrier');if(bParent&&e.dataset.bfield){const bar=b.barriers[Number(bParent.dataset.j)];if(bar)bar[e.dataset.bfield]=e.type==='checkbox'?e.checked:e.value}refreshStats()});
document.addEventListener('change',event=>{const e=event.target;if(e.matches('select[data-score],select[data-bfield],input[type=checkbox][data-bfield],input[type=date][data-bfield],input[type=date][data-meta]'))e.dispatchEvent(new Event('input',{bubbles:true}))});
document.addEventListener('click',event=>{const btn=event.target.closest('button[data-action]');if(!btn)return;const {action,side}=btn.dataset;if(!['threats','consequences'].includes(side))return;const i=Number(btn.dataset.i),j=Number(btn.dataset.j);if(action==='add-item')state[side].push(newItem());else if(action==='remove-item'){if(!window.confirm('Seçilen dal ve ilişkili bütün bariyerler silinsin mi?'))return;state[side].splice(i,1)}else if(action==='add-barrier'){if(state[side][i])state[side][i].barriers.push(newBarrier())}else if(action==='remove-barrier'){if(!window.confirm('Bu bariyer silinsin mi?'))return;state[side][i]?.barriers.splice(j,1)}renderEditors();refreshStats()});
$('importBtn').addEventListener('click',()=>$('fileIn').click());
$('fileIn').addEventListener('change',event=>importExcel(event.target.files?.[0]));
$('exportBtn').addEventListener('click',exportExcel);
$('printBtn').addEventListener('click',printNow);
window.addEventListener('beforeprint',preparePrint);
}
const asText=val;
function xmlEsc(v){return asText(v).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])).replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g,'')}
function colLetter(num){let res='';for(let n=num;n>0;n=Math.floor((n-1)/26))res=String.fromCharCode(65+(n-1)%26)+res;return res}
function sheetXml(rows,{width=18,autoFilter=true}={}){
const maxCols=Math.max(1,...rows.map(r=>r.length));const cols=Array.from({length:maxCols},(_,i)=>{let w=width;if(i===0)w=17;if(i===5||i===6||i===8||i===9||i===26)w=42;if(i===3||i===4||i===10)w=24;return `<col min="${i+1}" max="${i+1}" width="${w}" customWidth="1"/>`}).join('');
const sheetRows=rows.map((row,i)=>`<row r="${i+1}">${row.map((value,j)=>{const c=colLetter(j+1)+(i+1),st=i===0?' s="1"':'';if(typeof value==='number'&&Number.isFinite(value))return `<c r="${c}"${st}><v>${value}</v></c>`;return `<c r="${c}" t="inlineStr"${st}><is><t xml:space="preserve">${xmlEsc(value)}</t></is></c>`}).join('')}</row>`).join('');
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetViews><sheetView workbookViewId="0"><pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews><sheetFormatPr defaultRowHeight="16"/><cols>${cols}</cols><sheetData>${sheetRows}</sheetData>${autoFilter?`<autoFilter ref="A1:${colLetter(maxCols)}${Math.max(1,rows.length)}"/>`:''}</worksheet>`
}
const CRC_TABLE=(()=>{const result=[];for(let i=0;i<256;i++){let x=i;for(let j=0;j<8;j++)x=(x&1)?(0xedb88320^(x>>>1)):(x>>>1);result[i]=x>>>0}return result})();
function crc32(data){let crc=0xffffffff;for(const byte of data)crc=CRC_TABLE[(crc^byte)&255]^(crc>>>8);return (crc^0xffffffff)>>>0}
function zipCreate(entries){
const enc=new TextEncoder(),pieces=[],centers=[];let offset=0;
const u16=(view,pos,val)=>view.setUint16(pos,val,true),u32=(view,pos,val)=>view.setUint32(pos,val>>>0,true);
function append(piece){pieces.push(piece);offset+=piece.length}
for(const [filename,text] of entries){const name=enc.encode(filename),data=enc.encode(text),crc=crc32(data),local=new Uint8Array(30+name.length),dv=new DataView(local.buffer),begin=offset;
u32(dv,0,0x04034b50);u16(dv,4,20);u16(dv,6,0x0800);u16(dv,8,0);u16(dv,10,0);u16(dv,12,0);u32(dv,14,crc);u32(dv,18,data.length);u32(dv,22,data.length);u16(dv,26,name.length);u16(dv,28,0);local.set(name,30);append(local);append(data);
const cent=new Uint8Array(46+name.length),cv=new DataView(cent.buffer);u32(cv,0,0x02014b50);u16(cv,4,20);u16(cv,6,20);u16(cv,8,0x0800);u16(cv,10,0);u16(cv,12,0);u16(cv,14,0);u32(cv,16,crc);u32(cv,20,data.length);u32(cv,24,data.length);u16(cv,28,name.length);u16(cv,30,0);u16(cv,32,0);u16(cv,34,0);u16(cv,36,0);u32(cv,38,0);u32(cv,42,begin);cent.set(name,46);centers.push(cent)
}
const start=offset;for(const c of centers)append(c);const end=new Uint8Array(22),e=new DataView(end.buffer);u32(e,0,0x06054b50);u16(e,4,0);u16(e,6,0);u16(e,8,centers.length);u16(e,10,centers.length);u32(e,12,offset-start);u32(e,16,start);u16(e,20,0);append(end);return new Blob(pieces,{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})
}
function workbookFiles(rows,summary){const types=`<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/></Types>`;
const rels=`<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`;
const workbook=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Bow-Tie Bariyerleri" sheetId="1" r:id="rId1"/><sheet name="Analiz Bilgileri" sheetId="2" r:id="rId2"/></sheets></workbook>`;
const wbRels=`<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet2.xml"/><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>`;
const styles=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="2"><font><sz val="10"/><name val="Calibri"/></font><font><b/><sz val="10"/><color rgb="FFFFFFFF"/><name val="Calibri"/></font></fonts><fills count="3"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill><fill><patternFill patternType="solid"><fgColor rgb="FF0369A1"/><bgColor indexed="64"/></patternFill></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
return [['[Content_Types].xml',types],['_rels/.rels',rels],['xl/workbook.xml',workbook],['xl/_rels/workbook.xml.rels',wbRels],['xl/styles.xml',styles],['xl/worksheets/sheet1.xml',sheetXml(rows)],['xl/worksheets/sheet2.xml',sheetXml(summary,{width:36,autoFilter:false})]]}
async function unzipEntries(arrayBuffer){const bytes=new Uint8Array(arrayBuffer),view=new DataView(arrayBuffer);let end=-1;for(let i=bytes.length-22;i>=Math.max(0,bytes.length-65557);i--)if(view.getUint32(i,true)===0x06054b50){end=i;break}if(end<0)throw new Error('Geçerli Excel ZIP yapısı bulunamadı.');const count=view.getUint16(end+10,true),base=view.getUint32(end+16,true),decoder=new TextDecoder(),entries={};let cursor=base;
for(let i=0;i<count;i++){if(view.getUint32(cursor,true)!==0x02014b50)throw new Error('Excel dosyası merkezi dizini okunamadı.');const method=view.getUint16(cursor+10,true),compressed=view.getUint32(cursor+20,true),nameLength=view.getUint16(cursor+28,true),extra=view.getUint16(cursor+30,true),comment=view.getUint16(cursor+32,true),localOffset=view.getUint32(cursor+42,true),name=decoder.decode(bytes.subarray(cursor+46,cursor+46+nameLength));if(view.getUint32(localOffset,true)!==0x04034b50)throw new Error('Excel dosyası veri bölümü bozuk.');const start=localOffset+30+view.getUint16(localOffset+26,true)+view.getUint16(localOffset+28,true);const content=bytes.slice(start,start+compressed);if(method===0)entries[name]=decoder.decode(content);else if(method===8){if(typeof DecompressionStream!=='function')throw new Error('Bu tarayıcı sıkıştırılmış Excel içeri aktarmayı desteklemiyor. Güncel bir Chrome veya Edge tarayıcısı deneyin.');try{const stream=new Blob([content]).stream().pipeThrough(new DecompressionStream('deflate-raw'));entries[name]=decoder.decode(await new Response(stream).arrayBuffer())}catch(e){throw new Error('Excel sıkıştırması açılamadı: '+e.message)}}else throw new Error('Excel dosyasında desteklenmeyen sıkıştırma türü.');cursor+=46+nameLength+extra+comment;
}return entries}
function parseXml(source){const doc=new DOMParser().parseFromString(source,'application/xml');if(doc.querySelector('parsererror'))throw new Error('Excel XML içeriği okunamadı.');return doc}
function parseXlsxSheet(source,shared=[]){if(!source)return [];const doc=parseXml(source),rows=[];for(const row of doc.getElementsByTagNameNS('*','row')){const cells=[];for(const c of row.getElementsByTagNameNS('*','c')){const ref=c.getAttribute('r')||'';let index=0;for(const letter of (ref.match(/^[A-Z]+/)||[''])[0])index=index*26+letter.charCodeAt(0)-64;index=Math.max(0,index-1);const type=c.getAttribute('t');let value='';if(type==='inlineStr')value=Array.from(c.getElementsByTagNameNS('*','t')).map(n=>n.textContent).join('');else{value=c.getElementsByTagNameNS('*','v')[0]?.textContent||'';if(type==='s')value=shared[Number(value)]||''}cells[index]=value}rows.push(cells)}return rows}
function normalizeDate(value){const str=asText(value);if(/^\d{4}-\d{2}-\d{2}$/.test(str))return str;if(/^\d{2}\.\d{2}\.\d{4}$/.test(str)){const a=str.split('.');return a[2]+'-'+a[1]+'-'+a[0]}const n=Number(value);if(Number.isFinite(n)&&n>=1&&n<100000)return new Date(Date.UTC(1899,11,30)+Math.floor(n)*86400000).toISOString().slice(0,10);return ''}
/* Bow-Tie Excel: iç içe dal-bariyer ilişkilerini bozmayan iki sayfalı XLSX */
const LEGACY_EXCEL_HEADERS=['Kanat','Dal ID','Dal Başlığı','Dal Açıklaması','Bariyer ID','Bariyer Adı','Bariyer Durumu','Sahada Doğrulandı','Sorumlu / Birim','Son Kontrol Tarihi','Zayıflatıcı Etken','Koruyucu Önlem','Aksiyon / İyileştirme','Hedef Tarih'];
const EXCEL_HEADERS=['Sıra No',...LEGACY_EXCEL_HEADERS];
const META_LABELS={ref:'Analiz / Kayıt No.',company:'İş Yeri / Kuruluş',facility:'Tesis / Ünite / Proses',equipment:'Ekipman / Faaliyet',date:'Analiz Tarihi',preparer:'Hazırlayan / Ekip',approver:'Gözden Geçiren / Onaylayan',revision:'Revizyon / Sürüm',scope:'Kapsam / Varsayımlar',hazard:'Tehlike',topEvent:'Kritik Olay (Top Event)',criterion:'Kritik Olay Ölçütü',recovery:'Acil Durum / İyileşme'};
const SCORE_LABELS={beforeP:'Başlangıç Olasılık',beforeS:'Başlangıç Şiddet',afterP:'Artık Olasılık',afterS:'Artık Şiddet'};
function excelRows(){const rows=[EXCEL_HEADERS];let serial=0;for(const [side,arr,label] of [['threats',state.threats,'Tehdit'],['consequences',state.consequences,'Sonuç']])for(const branch of arr){const items=branch.barriers.length?branch.barriers:[null];for(const b of items)rows.push([++serial,label,branch.id,branch.title,branch.detail,b?.id||'',b?.name||'',b?statuses[b.status]||'Planlandı':'',b?(b.verified?'Evet':'Hayır'):'',b?.owner||'',b?.checkedAt||'',b?.degrader||'',b?.safeguard||'',b?.action||'',b?.dueDate||'']);}return rows;}
function reportRows(){return [['Alan','Değer'],['Şablon Sürümü','risk-ui-bow-tie-xlsx-v1'],['Yöntem','Bow-Tie (Papyon)'],...META_KEYS.map(k=>[META_LABELS[k],state.meta[k]]),...SCORE_KEYS.map(k=>[SCORE_LABELS[k],state.scores[k]]),['Tehdit Sayısı',state.threats.length],['Sonuç Sayısı',state.consequences.length],['Bariyer Sayısı',allBarriers().length],['Doğrulanmış Etkin Bariyer',allBarriers().filter(isVerified).length],['Not','Bu analiz uzman ve saha doğrulaması gerektirir.']];}
function downloadBlob(blob,name){const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),5000)}
function exportExcel(){try{downloadBlob(zipCreate(workbookFiles(excelRows(),reportRows())),`${slug()}_Bow_Tie_Risk_Analizi.xlsx`);toast('Tehdit, sonuç ve bariyerler Excel olarak dışarı aktarıldı.')}catch(e){console.error(e);alert('Excel dışarı aktarma başarısız: '+e.message)}}
function excelText(x,max=5000){return val(x).slice(0,max)}
async function readBowExcel(file){
if(!/\.xlsx$/i.test(file.name))throw Error('Yalnızca .xlsx dosyaları desteklenir.');
if(file.size>15*1024*1024)throw Error('Excel dosyası 15 MB sınırını aşıyor.');
const entries=await unzipEntries(await file.arrayBuffer());const strings=[];
if(entries['xl/sharedStrings.xml']){const d=parseXml(entries['xl/sharedStrings.xml']);for(const item of d.getElementsByTagNameNS('*','si'))strings.push(Array.from(item.getElementsByTagNameNS('*','t')).map(n=>n.textContent).join(''))}
const rows=parseXlsxSheet(entries['xl/worksheets/sheet1.xml'],strings),infos=parseXlsxSheet(entries['xl/worksheets/sheet2.xml'],strings);
if(!rows.length)throw Error('Excel risk kaydı sayfası bulunamadı.');
const cols=new Map(rows[0].map((v,i)=>[val(v).trim(),i]));if(LEGACY_EXCEL_HEADERS.some(k=>!cols.has(k)))throw Error('Excel, Bow-Tie şablonundaki sütunları içermiyor.');
const info=new Map(infos.slice(1).map(r=>[val(r[0]),val(r[1])]));
if(info.get('Şablon Sürümü')!=='risk-ui-bow-tie-xlsx-v1')throw Error('Excel dosyası Bow-Tie Risk UI biçiminde değil.');
const result=stateFactory();for(const k of META_KEYS)result.meta[k]=excelText(info.get(META_LABELS[k])||'');
for(const k of SCORE_KEYS){const v=val(info.get(SCORE_LABELS[k])||'');result.scores[k]=/^[1-5]$/.test(v)?v:''}
const dataRows=rows.slice(1).filter(row=>row.some(x=>val(x).trim()));if(dataRows.length>10000)throw Error('En fazla 10.000 Excel satırı desteklenir.');
const seen=new Map(),barrierSeen=new Map();let rowNo=1;
for(const row of dataRows){rowNo++;const get=name=>excelText(row[cols.get(name)]??'');
const sideValue=get('Kanat').trim().toLocaleLowerCase('tr-TR');const side=['tehdit','threats'].includes(sideValue)?'threats':['sonuç','sonuc','consequences'].includes(sideValue)?'consequences':null;
if(!side)throw Error(rowNo+'. satırda Kanat değeri Tehdit veya Sonuç olmalıdır.');
const rawId=get('Dal ID').trim(),nameKey=get('Dal Başlığı').trim(),detailKey=get('Dal Açıklaması').trim();
// Dal ID tekrarı, bir dalın birden fazla bariyeri olduğunda gereklidir.
// Dal ID boşsa aynı kanatta aynı başlık/açıklama tek dal olarak alınır.
const branchKey=side+'|'+(rawId?'id:'+rawId:(nameKey||detailKey?'başlık:'+nameKey+'|'+detailKey:'satir-'+rowNo));
let branch=seen.get(branchKey);
if(!branch){if(result[side].length>=120)throw Error('Her kanat en fazla 120 dal içerebilir.');branch={...newItem(),title:get('Dal Başlığı').slice(0,1000),detail:get('Dal Açıklaması').slice(0,2500)};result[side].push(branch);seen.set(branchKey,branch)}
else if(branch.title!==get('Dal Başlığı').slice(0,1000)||branch.detail!==get('Dal Açıklaması').slice(0,2500))throw Error(rowNo+'. satırda aynı Dal ID için farklı dal tanımı mevcut.');
const name=get('Bariyer Adı'),bId=get('Bariyer ID');const meaningful=[bId,name,get('Bariyer Durumu'),get('Sorumlu / Birim'),get('Aksiyon / İyileştirme'),get('Zayıflatıcı Etken'),get('Koruyucu Önlem'),get('Son Kontrol Tarihi'),get('Hedef Tarih')].some(v=>v.trim());
if(meaningful){const st=get('Bariyer Durumu');let status=Object.keys(statuses).find(k=>statuses[k]===st)|| (statuses[st]?st:'planned');
const checkedAt=get('Son Kontrol Tarihi'),dueDate=get('Hedef Tarih');
if(checkedAt&&!normalizeDate(checkedAt))throw Error(rowNo+'. satırda Son Kontrol Tarihi geçersiz.');
if(dueDate&&!normalizeDate(dueDate))throw Error(rowNo+'. satırda Hedef Tarih geçersiz.');
const barrier={...newBarrier(),name:name.slice(0,1000),status,verified:['evet','true','1'].includes(get('Sahada Doğrulandı').trim().toLocaleLowerCase('tr-TR')),owner:get('Sorumlu / Birim').slice(0,500),checkedAt:normalizeDate(checkedAt),degrader:get('Zayıflatıcı Etken').slice(0,2000),safeguard:get('Koruyucu Önlem').slice(0,2000),action:get('Aksiyon / İyileştirme').slice(0,2000),dueDate:normalizeDate(dueDate)};
// Kaynağın Bariyer ID alanı eşsiz bir ilişkisel kimliktir, sıra numarası değildir.
// Aynı kimliğin birebir yinelenen satırları tek bariyer alınır; çelişkili kopyalar reddedilir.
if(bId.trim()){
const key=branchKey+'|'+bId.trim();
const signature=JSON.stringify(Object.fromEntries(Object.entries(barrier).filter(([k])=>k!=='id')));
if(barrierSeen.has(key)){
if(barrierSeen.get(key)!==signature)throw Error(rowNo+'. satırda aynı Bariyer ID için çelişen kayıt bulundu.');
continue;
}
barrierSeen.set(key,signature);
}
if(branch.barriers.length>=40)throw Error('Her dal en fazla 40 bariyer içerebilir.');
branch.barriers.push(barrier);
}
}
return result;
}
async function importExcel(file){if(!file)return;try{const imported=await readBowExcel(file);if(!confirmReplace())return;state=imported;renderAll();toast('Excel içeri aktarıldı; yinelenen satırlar ayıklandı ve sıra numaraları 1’den başlatıldı.')}catch(err){console.error(err);alert('Excel içeri aktarma başarısız: '+err.message)}finally{$('fileIn').value=''}}
function csvClean(text){let s=val(text).replace(/\r?\n/g,' ');if(/^[\s]*[=+@\-]/.test(s))s="'"+s;return '"'+s.replace(/"/g,'""')+'"'}
function downloadCSV(){let rows=[['Kanat','Dal','Bariyer','Durum','Sahada doğrulandı','Sorumlu','Son kontrol','Zayıflatıcı etken','Koruyucu önlem','Aksiyon','Hedef tarihi','Gecikme']];for(const b of allBarriers())rows.push([b.side==='threats'?'Önleyici':'Azaltıcı',b.branch,b.name,statuses[b.status]||'',b.verified?'Evet':'Hayır',b.owner,b.checkedAt,b.degrader,b.safeguard,b.action,b.dueDate,overdue(b)?'Evet':'Hayır']);fileDownload(`${slug()}_Bariyer_Aksiyon.csv`,'\ufeff'+rows.map(r=>r.map(csvClean).join(';')).join('\r\n'),'text/csv;charset=utf-8');toast('Excel uyumlu CSV hazırlandı.')}
function pField(label,v){return `<div class="p-field"><b>${safe(label)}</b>${printVal(v)}</div>`}
function printLogo(){const img=$('logoImage');if(img&&!img.hidden&&img.complete&&img.naturalWidth)return img.currentSrc||img.src;return brand.logo&&/^data:image\//.test(brand.logo)?brand.logo:''}
function preparePrint(){
const logo=printLogo();$('printHeader').innerHTML=`<div class="p-header">${logo?`<img src="${safe(logo)}" alt="Kurum logosu">`:'<span style="color:#70879c;font-weight:800">LOGO</span>'}<div><h1>${safe(brand.title)}</h1><p>${safe(brand.subtitle)}</p></div><span class="p-org">${safe(brand.org)}</span></div>`;
const meta=`<div class="p-section"><h3>01 · Analiz / Tesis Bilgileri ve Kritik Olay</h3><div class="p-grid">${META_KEYS.map(k=>pField(META_LABELS[k],state.meta[k])).join('')}</div></div>`;
const br=risk(state.scores.beforeP,state.scores.beforeS),ar=risk(state.scores.afterP,state.scores.afterS);
const scores=`<div class="p-section"><h3>02 · Risk Değerlendirmesi ve Bariyer Güvencesi</h3><div class="p-risk"><div><b>BAŞLANGIÇ RİSKİ</b><div><strong>${br.score??'—'}</strong> · ${safe(br.desc)} (O:${state.scores.beforeP||'—'} × Ş:${state.scores.beforeS||'—'})</div></div><div><b>ARTIK RİSK</b><div><strong>${ar.score??'—'}</strong> · ${safe(ar.desc)} (O:${state.scores.afterP||'—'} × Ş:${state.scores.afterS||'—'})</div></div></div><div class="p-note">Tehdit: ${state.threats.length} · Sonuç: ${state.consequences.length} · Bariyer: ${allBarriers().length} · Doğrulanmış etkin: ${allBarriers().filter(isVerified).length} · Korumasız dal: ${gapBranches().length} · Geciken faaliyet: ${allBarriers().filter(overdue).length}</div></div>`;
const diagram=`<div class="p-section"><h3>03 · Dinamik Bow-Tie / Papyon Diyagramı</h3><div class="p-diagram">${diagramMarkup()}</div><div class="p-note">Mavi: tehdit / neden · Yeşil: önleyici bariyer · Kırmızı: kritik olay · Mor: azaltıcı bariyer · Turuncu: sonuç. Bariyer etkinliği bağımsız saha doğrulaması gerektirir.</div></div>`;
const branchRows=allBranches().flatMap(branch=>branch.barriers.length?branch.barriers.map(b=>({branch,barrier:b})):[{branch,barrier:null}]);
const rows=branchRows.map(({branch,barrier:b},i)=>`<tr><td>${i+1}</td><td>${branch.side==='threats'?'Tehdit / önleme':'Sonuç / azaltma'}</td><td>${safe(branch.title||'—')}</td><td>${safe(b?.name||'—')}</td><td>${b?safe(statuses[b.status]||'Planlandı')+(isVerified(b)?' · Doğrulandı':' · Doğrulanmadı'):'—'}</td><td>${safe(b?.owner||'—')}</td><td>${safe(b?.action||'—')}</td><td>${safe(b?.dueDate||'—')}</td></tr>`).join('');
const summary=`<div class="p-section"><h3>04 · Bariyer ve Aksiyon Kayıtları (${branchRows.length})</h3>${branchRows.length?`<table class="p-table"><colgroup><col style="width:4%"><col style="width:12%"><col style="width:16%"><col style="width:16%"><col style="width:17%"><col style="width:10%"><col style="width:17%"><col style="width:8%"></colgroup><thead><tr><th>No.</th><th>Kanat</th><th>Dal / Senaryo</th><th>Bariyer</th><th>Durum / Doğrulama</th><th>Sorumlu</th><th>Aksiyon</th><th>Hedef</th></tr></thead><tbody>${rows}</tbody></table>`:'<div class="p-note">Henüz dal kaydı bulunmuyor.</div>'}</div>`;
const details=allBranches().map((b,i)=>{const pairs=[['Kanat',b.side==='threats'?'Önleyici':'Azaltıcı'],['Dal / Senaryo',b.title],['Ayrıntı',b.detail],['Bariyer Sayısı',b.barriers.length]];const bRows=b.barriers.flatMap((v,j)=>{const f=[['Bariyer Adı',v.name],['Durum / Doğrulama',(statuses[v.status]||'Planlandı')+' / '+(isVerified(v)?'Etkin / doğrulandı':'Doğrulanmadı')],['Sorumlu',v.owner],['Son Saha Kontrolü',v.checkedAt],['Zayıflatıcı Etken',v.degrader],['Koruyucu Önlem',v.safeguard],['Düzeltici Faaliyet / Aksiyon',v.action],['Hedef Tarih',v.dueDate]];return Array.from({length:Math.ceil(f.length/4)},(_,row)=>`<tr>${f.slice(row*4,row*4+4).map(([k,value])=>`<td>${pField(k,value)}</td>`).join('')}</tr>`)});return `<div class="p-detail"><table class="p-detail-table"><thead><tr><th colspan="4"><div class="p-detail-head">${i+1}. ${safe(b.side==='threats'?'TEHDİT / ÖNLEYİCİ':'SONUÇ / AZALTICI')} · ${safe(b.title||'İsimsiz dal')}</div></th></tr></thead><tbody><tr>${pairs.map(([k,v])=>`<td>${pField(k,v)}</td>`).join('')}</tr>${bRows.join('')}</tbody></table></div>`}).join('');
$('printContent').innerHTML=meta+scores+diagram+summary+`<div class="p-section"><h3>05 · Dal / Bariyer Detayları</h3>${details||'<div class="p-note">Henüz kaydedilmiş dal yok.</div>'}</div>`;
}
async function printNow(){preparePrint();try{await document.fonts.ready}catch(e){}await new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));window.print()}
function xmlEscape(s){return val(s).replace(/[&<>"']/g,ch=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch]))}
function wrapLines(s,max=24,limit=4){const text=val(s).replace(/\s+/g,' ').trim();if(!text)return ['—'];const words=text.split(' '),out=[];let part='';for(const w of words){if((part+' '+w).trim().length>max&&part){out.push(part);part=w}else part=(part+' '+w).trim()}if(part)out.push(part);if(out.length>limit){out.length=limit;out[limit-1]+='…'}return out}
function downloadSVG(){const n=Math.max(1,state.threats.length,state.consequences.length),w=1660,rowH=160,head=100,footer=35,h=head+n*rowH+footer;const cols=[20,340,660,1000,1320],widths=[295,295,315,295,315];let p=[`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><rect width="100%" height="100%" fill="#fff"/><style>text{font-family:Segoe UI,Arial,sans-serif} .title{font-size:18px;font-weight:bold;fill:#075985}.label{font-size:15px;font-weight:700;fill:#234f66}.txt{font-size:15px;fill:#17283b}.small{font-size:12px;fill:#475569}</style>`];const labels=['TEHDİTLER / NEDENLER','ÖNLEYİCİ BARİYERLER','KRİTİK OLAY','AZALTICI BARİYERLER','SONUÇLAR'];p.push(`<text x="20" y="29" class="title">${xmlEscape(brand.title)}</text><text x="20" y="52" class="small">${xmlEscape(state.meta.ref||'Bow-Tie Risk UI')} · Önleme → Kritik olay → Etki azaltma</text>`);labels.forEach((l,i)=>{p.push(`<rect x="${cols[i]}" y="67" width="${widths[i]}" height="29" rx="4" fill="${i===2?'#ffe8e7':'#eaf5fc'}"/><text x="${cols[i]+10}" y="87" class="label">${l}</text>`)});
function box(i,row,fill,lines,accent){let x=cols[i],y=head+row*rowH+6,bh=rowH-13;p.push(`<rect x="${x}" y="${y}" width="${widths[i]}" height="${bh}" rx="5" fill="${fill}" stroke="${accent}" stroke-width="1.6"/>`);let usable=lines.slice(0,7);usable.forEach((line,k)=>p.push(`<text x="${x+11}" y="${y+26+k*17}" class="${k===0?'txt':'small'}">${xmlEscape(line)}</text>`))}
for(let i=0;i<n;i++){let t=state.threats[i],c=state.consequences[i];if(t){box(0,i,'#f6fbff',wrapLines(t.title,29,2).concat(wrapLines(t.detail,39,4)),'#7fb6d4');box(1,i,'#f5fcf8',t.barriers.length?t.barriers.flatMap(b=>wrapLines((isVerified(b)?'✓':'!')+' '+(b.name||'İsimsiz bariyer'),36,2)).slice(0,7):['! Bariyer tanımlanmadı'],'#92c5ac')}if(c){box(3,i,'#faf8ff',c.barriers.length?c.barriers.flatMap(b=>wrapLines((isVerified(b)?'✓':'!')+' '+(b.name||'İsimsiz bariyer'),36,2)).slice(0,7):['! Bariyer tanımlanmadı'],'#beb0da');box(4,i,'#fffaf5',wrapLines(c.title,29,2).concat(wrapLines(c.detail,39,4)),'#e4b37e')}
const y=head+i*rowH+rowH/2;for(let ci=0;ci<4;ci++)p.push(`<path d="M ${cols[ci]+widths[ci]} ${y} H ${cols[ci+1]-9}" stroke="#7d9caf" stroke-width="2" fill="none"/><polygon points="${cols[ci+1]-9},${y-4} ${cols[ci+1]-1},${y} ${cols[ci+1]-9},${y+4}" fill="#7d9caf"/>`)}
let centerY=head+n*rowH/2,centerLines=['TEHLİKE',...wrapLines(state.meta.hazard,35,3),'TOP EVENT',...wrapLines(state.meta.topEvent,35,3)];let cy=centerY-(centerLines.length*23)/2;p.push(`<rect x="${cols[2]}" y="${head+6}" width="${widths[2]}" height="${n*rowH-13}" rx="7" fill="#fff1f0" stroke="#d98a93" stroke-width="2"/>`);centerLines.forEach((line,k)=>p.push(`<text x="${cols[2]+widths[2]/2}" y="${cy+k*23}" class="${line==='TEHLİKE'||line==='TOP EVENT'?'label':'txt'}" text-anchor="middle">${xmlEscape(line)}</text>`));p.push(`<text x="20" y="${h-10}" class="small">Bilgi amaçlı diyagram · Bariyer etkinlikleri saha incelemesi ile doğrulanmalıdır.</text></svg>`);fileDownload(`${slug()}_Bow_Tie_Diyagram.svg`,p.join(''),'image/svg+xml;charset=utf-8');toast('Diyagram SVG dosyası hazırlandı.')}
function sampleData(){const s=stateFactory();Object.assign(s.meta,{ref:'ORNEK-BT-001',company:'TEMSiLi TESiS',facility:'Kimyasal proses ünitesi',equipment:'Basınçlı transfer hattı',date:today(),preparer:'Örnek analiz ekibi',revision:'R00',scope:'Temsili endüstriyel risk senaryosu; gerçek saha verileri değildir.',hazard:'Basınç altındaki tehlikeli kimyasal',topEvent:'Akışkanın kontrolsüz salımı',criterion:'Proses sınırının kaybı sonucunda ortam dışına akışkan çıkışı',recovery:'Alarm, kontrollü tahliye, izolasyon ve olay sonrası güvenli duruma alma.'});Object.assign(s.scores,{beforeP:'4',beforeS:'5',afterP:'2',afterS:'5'});
function b(name,status='active',verified=true,action='',dueDate=''){return {...newBarrier(),name,status,verified,action,dueDate,owner:'Proses / bakım',checkedAt:verified?today():'',degrader:'Bakım ve test eksikliği',safeguard:'Periyodik kontrol / fonksiyon testi'}}
s.threats=[{...newItem(),title:'Hat korozyonu ve et kalınlığı kaybı',detail:'Uzun süreli işletmede malzeme bütünlüğünün azalması',barriers:[b('Periyodik kalınlık ölçümü'),b('Korozyon izleme programı','weak',false,'İzleme aralığını yeniden belirle',today())]},{...newItem(),title:'Aşırı basınç',detail:'Proses set değeri dışına çıkılması',barriers:[b('Yüksek basınç alarmı'),b('Bağımsız koruma sistemi','planned',false,'Devreye alma ve fonksiyon testi')]}];
s.consequences=[{...newItem(),title:'Çalışanların kimyasal maruziyeti',detail:'Salım yakınında personel etkilenmesi',barriers:[b('Gaz algılama ve alarm'),b('Acil tahliye prosedürü','active',false,'Tatbikat ve doğrulama') ]},{...newItem(),title:'Çevresel yayılım ve ekipman hasarı',detail:'Salımın saha dışına veya hassas ekipmana ulaşması',barriers:[b('Acil izolasyon vanası'),b('Sızıntı toplama / sınırlama','planned',false,'Saha uygunluk incelemesi')]}];return s}
function setupFiveClicks(el,cb){let n=0,prev=0;el.addEventListener('click',()=>{if(el.isContentEditable)return;let now=Date.now();n=now-prev<1100?n+1:1;prev=now;if(n>=5){n=0;cb()}})}
function saveBrand(){try{localStorage.setItem(BRAND_KEY,JSON.stringify(brand))}catch(e){toast('Görsel veya başlık tarayıcıda saklanamadı.')}}
function setLogo(){const img=$('logoImage'),fallback=$('logoFallback');img.onerror=()=>{img.hidden=true;fallback.hidden=false};img.onload=()=>{img.hidden=false;fallback.hidden=true};img.hidden=true;fallback.hidden=false;img.src=brand.logo||'logo.jpg'}
function editBrand(el,key){const before=el.textContent;el.contentEditable='true';el.setAttribute('role','textbox');el.focus();const r=document.createRange(),s=window.getSelection();r.selectNodeContents(el);s.removeAllRanges();s.addRange(r);let cancelled=false;function finish(){el.removeEventListener('blur',finish);el.removeEventListener('keydown',keyListener);el.contentEditable='false';const v=el.textContent.trim().slice(0,160);brand[key]=!cancelled&&v?v:before;el.textContent=brand[key];saveBrand();if(!cancelled)toast('Üst bilgi kaydedildi.')}function keyListener(e){if(e.key==='Enter'){e.preventDefault();el.blur()}else if(e.key==='Escape'){cancelled=true;el.blur()}}el.addEventListener('blur',finish);el.addEventListener('keydown',keyListener)}
function initBrand(){try{const stored=JSON.parse(localStorage.getItem(BRAND_KEY)||'{}');for(const k of Object.keys(BRAND_DEFAULT))if(typeof stored[k]==='string')brand[k]=stored[k]}catch(e){}$('brandTitle').textContent=brand.title;$('brandSubtitle').textContent=brand.subtitle;$('brandOrg').textContent=brand.org;setLogo();for(const [id,key] of [['brandTitle','title'],['brandSubtitle','subtitle'],['brandOrg','org']])setupFiveClicks($(id),()=>editBrand($(id),key));setupFiveClicks($('logoTrigger'),()=>$('logoFile').click());$('logoFile').addEventListener('change',e=>{const file=e.target.files?.[0];e.target.value='';if(!file)return;if(!/^image\/(jpeg|png|webp|gif)$/.test(file.type)||file.size>1_200_000){alert('PNG/JPEG/WEBP/GIF, en çok 1,2 MB görsel seçin.');return}const reader=new FileReader();reader.onload=()=>{brand.logo=String(reader.result||'');saveBrand();setLogo();toast('Kurum logosu güncellendi.')};reader.readAsDataURL(file)})}
// Sayfa her açıldığında / yenilendiğinde ve BFCache geçmişinden geri dönüldüğünde
// analiz verileri ve tarayıcı tarafından geri getirilen alan değerleri sıfırlanır.
// Kurumsal başlık ve logo ise kullanıcı tercihi olarak ayrı saklanmaya devam eder.
function resetAnalysisOnPageEntry(){
state=stateFactory();
$('fileIn').value='';
$('printHeader').replaceChildren();$('printContent').replaceChildren();
renderAll();
document.querySelectorAll('#uiShell input:not([type=file]), #uiShell select, #uiShell textarea')
.forEach(el=>el.setAttribute('autocomplete','off'));
}
initBrand();initBindings();resetAnalysisOnPageEntry();
window.addEventListener('pageshow',()=>{
resetAnalysisOnPageEntry();
// Bazı tarayıcılar form değerlerini pageshow sırasında veya hemen ardından geri yükler.
requestAnimationFrame(resetAnalysisOnPageEntry);
});
</script>
</body>
</html>