Member Updates

September pairings are live!

September pairings are now live in BidGrid and ready for you to start building your bid.

Maintenance Notification

The system is under maintenance, sorry for the inconvenience.

NEW Tool Launch: Batch Bid

Zap your favorite Pairing IDs on Date that you tagged in BidGrid straight into PBS with one click. Learn how it works and watch a video here.

Reserve Legality Checker 2.0 (beta)

In this new beta version, you simply input how many days off you need (anywhere from 1 to 12) and we'll generate up to 50 legal solutions across three categories: weekend-off, short work-block, and long work-block. Check it out here.

Help Us Grow — Tell a Pilot

Every new pilot helps us build the next tool faster. If Bidliner's made your life easier, a quick mention to your copilot goes a long way and we really appreciate it.

We use third-party cookies in order to personalise your experience
Read our Cookie Policy.
const state={defMin:3,defMax:6,activeLayer:1,layers:{},entries:{},focusedDate:null,selectedCode:null,inputMode:"advance",month:null,labelYear:null};function defaultLayer(){return{line:"S",min:null,max:null,flyThrough:"",waive4off:!1,allowSingle:!1,waiveFirstDFP:!1}}function effectiveLayer(e){return null!=e.min&&null!=e.max?e:{...e,min:e.min??state.defMin,max:e.max??state.defMax}}function currentLayer(){return state.layers[state.activeLayer]}state.layers[1]=defaultLayer();const defMinRange=document.getElementById("defMinRange"),defMaxRange=document.getElementById("defMaxRange"),defMinNum=document.getElementById("defMinNum"),defMaxNum=document.getElementById("defMaxNum");function syncSetup(e){let t=parseInt(defMinRange.value),n=parseInt(defMaxRange.value);"minNum"===e&&(t=parseInt(defMinNum.value)||1),"maxNum"===e&&(n=parseInt(defMaxNum.value)||1),"minRange"===e&&(t=parseInt(defMinRange.value)),"maxRange"===e&&(n=parseInt(defMaxRange.value)),t=Math.min(Math.max(t,1),10),n=Math.min(Math.max(n,1),10),defMinRange.value=t,defMinNum.value=t,defMaxRange.value=n,defMaxNum.value=n,state.defMin=t,state.defMax=n;const o=state.layers[1];null!=o.min&&(o.min=Math.min(Math.max(o.min,t),n)),null!=o.max&&(o.max=Math.min(Math.max(o.max,t),n)),null!=o.min&&null!=o.max&&o.min>o.max&&(o.min=o.max),populateMinMaxSelects(),loadLayerIntoPanel(),document.getElementById("wbsLabel").textContent=`Work Block Size (Default: ${t}–${n})`}[["minRange",defMinRange],["maxRange",defMaxRange],["minNum",defMinNum],["maxNum",defMaxNum]].forEach(([e,t])=>t.addEventListener("input",()=>syncSetup(e))),document.querySelectorAll(".collapse-btn").forEach(e=>{e.addEventListener("click",()=>{const t=document.getElementById(e.dataset.target);t.classList.toggle("collapsed"),e.textContent=t.classList.contains("collapsed")?"+":"−"})}),document.getElementById("btnStartOver").addEventListener("click",()=>{confirm("Start over? This clears your bid and resets all settings to defaults.")&&(state.entries={},state.focusedDate=null,state.selectedCode=null,state.inputMode="advance",state.defMin=3,state.defMax=6,state.layers[1]=defaultLayer(),state.activeLayer=1,state.lastBidContext=null,document.getElementById("defMinRange").value=3,document.getElementById("defMinNum").value=3,document.getElementById("defMaxRange").value=6,document.getElementById("defMaxNum").value=6,document.querySelector('input[name="inputMode"][value="advance"]').checked=!0,document.querySelectorAll(".code-btn").forEach(e=>e.classList.remove("selected")),resetSolutionPools(),loadLayerIntoPanel(),syncSetup(),renderCalendar(),updateGlance(),hideErrors())});const panels={help:document.getElementById("helpPanel"),faq:document.getElementById("faqPanel")};function togglePanel(e){const t=panels[e],n="none"===t.style.display||""===t.style.display;Object.values(panels).forEach(e=>e.style.display="none"),t.style.display=n?"block":"none"}function renderPalette(){const e=document.getElementById("codePalette");e.innerHTML="",RLC_PALETTE.forEach(t=>{const n=document.createElement("div");n.className="code-btn cc-"+t.colorGroup,setDayCodeLabel(n,t.code),n.dataset.code=t.code,n.addEventListener("click",()=>onPaletteClick(t.code)),e.appendChild(n)})}function setDayCodeLabel(e,t){"OFFMUST"===t||"OFFPREFER"===t?(e.classList.add("stacked"),e.innerHTML=`${"OFFMUST"===t?"MUST":"PREFER"}OFF`):e.textContent=t}function onPaletteClick(e){state.selectedCode=e,document.querySelectorAll(".code-btn").forEach(t=>{t.classList.toggle("selected",t.dataset.code===e)}),state.focusedDate&&applyCodeToFocused(e)}function isExplicitOffCode(e){return e&&!isDutyCode(e)&&!isProratedCode(e)}function countExplicitDaysOff(){const e=(state.gridDates||[]).filter(e=>"month"===e.zone);let t=0,n=0;e.forEach(e=>{effectiveProratedAt(e.iso)&&t++;const o=getCode(e.iso);o&&isExplicitOffCode(o)&&n++});const o=e.length-t,{daysOff:a}=prorationLookup(o);return{daysOff:a,offCount:n}}function applyCodeToFocused(e){const t=state.focusedDate;if(!t)return;const n=(state.gridDates||[]).find(e=>e.iso===t);if(n&&"month"===n.zone&&isExplicitOffCode(e)){const e=getCode(t);if(!e||!isExplicitOffCode(e)){const{daysOff:e,offCount:t}=countExplicitDaysOff();if(t>=e)return void alert(`You've reached the maximum of ${e} days off for this month. Remove a day off before adding another.`)}}state.entries[t]={code:e,solved:!1},"advance"===state.inputMode&&advanceFocus(),renderCalendar(),updateGlance()}function advanceFocus(){const e=state.gridDates||[],t=e.findIndex(e=>e.iso===state.focusedDate);if(-1!==t){for(let n=t+1;n—',t.innerHTML='';for(let n=state.defMin;n<=state.defMax;n++){const o=document.createElement("option");o.value=n,o.textContent=n,e.appendChild(o);const a=document.createElement("option");a.value=n,a.textContent=n,t.appendChild(a)}}function loadLayerIntoPanel(){const e=currentLayer();populateMinMaxSelects(),document.querySelectorAll("#lineSeg button").forEach(t=>{t.classList.toggle("active",t.dataset.val===e.line)}),document.getElementById("layerMin").value=e.min??"",document.getElementById("layerMax").value=e.max??"",document.getElementById("flyThrough").value=e.flyThrough,document.getElementById("waive4off").checked=e.waive4off,document.getElementById("allowSingle").checked=e.allowSingle,document.getElementById("waiveFirstDFP").checked=e.waiveFirstDFP}function updateRestrictiveCaution(){const e=document.getElementById("layerMin").value,t=document.getElementById("layerMax").value,n=""!==e||""!==t;document.getElementById("restrictiveBidCaution").style.display=n?"block":"none"}function onWaiverChange(){Object.keys(state.entries).forEach(e=>{const t=state.entries[e];t&&"object"==typeof t&&"OFFPREFER"===t.code&&!1===t.solved&&delete state.entries[e]}),state.gridDates.forEach(e=>{const t=state.entries[e.iso];t&&t.solved&&delete state.entries[e.iso]}),solutionPools.weekends=null,solutionPools.longoff=null,solutionPools.balanced=null,state.lastBidContext=null,renderCalendar(),updateGlance()}function isoDate(e){return e.getFullYear()+"-"+String(e.getMonth()+1).padStart(2,"0")+"-"+String(e.getDate()).padStart(2,"0")}function addDays(e,t){const n=new Date(e);return n.setDate(n.getDate()+t),n}function fmtShort(e){return e.getMonth()+1+"/"+e.getDate()}function buildGrid(){const e=contractMonthStart(state.month,state.labelYear),t=addDays(e,contractMonthLength(state.month,state.labelYear)-1),n=addDays(e,-7),o=addDays(t,7),a=addDays(n,-n.getDay()),s=6-o.getDay(),l=addDays(o,s),i=[];let d=new Date(a);for(;d<=l;){const a=isoDate(d);let s;s=do?"outside":dt?"post-buffer":"month",i.push({date:new Date(d),iso:a,zone:s,editable:"outside"!==s}),d=addDays(d,1)}return state.gridDates=i,state.monthStart=e,state.monthEnd=t,state.monthIsoSet=new Set(i.filter(e=>"month"===e.zone).map(e=>e.iso)),i}function codeMeta(e){return MASTER_CODES[e]||null}function codeCategoryClass(e){const t=codeMeta(e);return t?"cc-"+(t.colorGroup||"nonProratedDuty"):""}document.getElementById("btnHelp").addEventListener("click",()=>togglePanel("help")),document.getElementById("btnFAQ").addEventListener("click",()=>togglePanel("faq")),document.querySelectorAll("#lineSeg button").forEach(e=>{e.addEventListener("click",()=>{currentLayer().line=e.dataset.val,document.querySelectorAll("#lineSeg button").forEach(t=>t.classList.toggle("active",t===e))})}),document.getElementById("layerMin").addEventListener("change",e=>{const t=currentLayer();t.min=""!==e.target.value?parseInt(e.target.value):null,null!=t.min&&null!=t.max&&t.min>t.max&&(t.max=t.min,document.getElementById("layerMax").value=t.max),updateRestrictiveCaution()}),document.getElementById("layerMax").addEventListener("change",e=>{const t=currentLayer();t.max=""!==e.target.value?parseInt(e.target.value):null,null!=t.min&&null!=t.max&&t.max{currentLayer().flyThrough=e.target.value}),document.getElementById("waive4off").addEventListener("change",e=>{currentLayer().waive4off=e.target.checked,onWaiverChange()}),document.getElementById("allowSingle").addEventListener("change",e=>{currentLayer().allowSingle=e.target.checked,onWaiverChange()}),document.getElementById("waiveFirstDFP").addEventListener("change",e=>{currentLayer().waiveFirstDFP=e.target.checked,onWaiverChange()});const MONTH_NAMES=["January","February","March","April","May","June","July","August","September","October","November","December"];function renderCalendar(){const e=buildGrid(),t=addDays(state.monthStart,7);document.getElementById("calMonthName").textContent=MONTH_NAMES[t.getMonth()],document.getElementById("calRange").textContent=fmtShort(state.monthStart)+" – "+fmtShort(state.monthEnd);const n=document.getElementById("calendarBody");n.innerHTML="";for(let t=0;t{state.focusedDate=a.iso,renderCalendar()}),o.appendChild(s)}n.appendChild(o)}}function shiftMonth(e){let t=state.month+e,n=state.labelYear;t<1&&(t=12,n-=1),t>12&&(t=1,n+=1),state.month=t,state.labelYear=n,state.focusedDate=null,renderCalendar(),updateGlance(),hideErrors()}function initTool(){renderPalette(),loadLayerIntoPanel();const e=new Date;let t=e.getMonth()+2,n=e.getFullYear();t>12&&(t-=12,n+=1),state.month=t,state.labelYear=n,renderCalendar(),updateGlance()}document.getElementById("navPrev").addEventListener("click",()=>{shiftMonth(-1)}),document.getElementById("navNext").addEventListener("click",()=>{shiftMonth(1)}),document.querySelectorAll('input[name="inputMode"]').forEach(e=>{e.addEventListener("change",()=>{state.inputMode=e.value})}),document.getElementById("btnCheckBid").addEventListener("click",()=>{const e=document.getElementById("btnCheckBid");e.disabled=!0,e.innerHTML='Thinking…',setTimeout(()=>{runCheckBid(),e.disabled=!1,e.textContent="Check Bid"},0)});const SOLUTION_COUNT_CAP=50,solutionPools={weekends:null,longoff:null,balanced:null},SOLVE_META={weekends:{label:"Weekends Off",navId:"navWeekends",posId:"posWeekends",prevId:"btnWkPrev",nextId:"btnWkNext"},longoff:{label:"Long Blocks of Days Off",navId:"navLongOff",posId:"posLongOff",prevId:"btnLoPrev",nextId:"btnLoNext"},balanced:{label:"Balanced (Short Work Blocks)",navId:"navBalanced",posId:"posBalanced",prevId:"btnBalPrev",nextId:"btnBalNext"}};function resetSolutionPools(){solutionPools.weekends=null,solutionPools.longoff=null,solutionPools.balanced=null;const e=document.getElementById("solutionMessage");e&&(e.style.display="none",e.textContent="");const t=document.getElementById("btnSolveDone");t&&(t.style.display="none"),Object.values(SOLVE_META).forEach(e=>{const t=document.getElementById(e.navId);t&&(t.style.display="none")});const n=document.getElementById("solveRows");n&&(n.style.display="none");const o=document.getElementById("solveSpinnerRow");o&&(o.style.display="none")}function scoreWeekends(e){return e.filter(e=>{const t=new Date(e+"T12:00:00");return 0===t.getDay()||6===t.getDay()}).length}function isNonDutyDay(e,t){return!(!t||!t.has(e))||!!getCode(e)&&!effectiveDutyAt(e)}function scoreLongOff(e){const t=new Set(e),n=state.gridDates.filter(e=>"month"===e.zone),o=[];let a=0;return n.forEach(e=>{isNonDutyDay(e.iso,t)?(a>0&&o.push(a),a=0):a++}),a>0&&o.push(a),0===o.length?0:o.reduce((e,t)=>e+t*t,0)}function scoreBalanced(e){const t=new Set(e),n=state.gridDates.filter(e=>"month"===e.zone),o=[];let a=0;if(n.forEach(e=>{isNonDutyDay(e.iso,t)?(a>0&&o.push(a),a=0):a++}),a>0&&o.push(a),0===o.length)return 0;const s=o.reduce((e,t)=>e+t,0)/o.length,l=o.reduce((e,t)=>e+(t-s)**2,0)/o.length;return-(s+Math.sqrt(l))}function collectAndSortSolutions(e,t){const n=[],o=[],a=state.lastBidContext,s=effectiveLayer(a.L);for(;n.length=t,i="weekends"===e?scoreWeekends:"longoff"===e?scoreLongOff:scoreBalanced;return n.sort((e,t)=>{const n=i(t)-i(e);return 0!==n?n:scoreWeekends(t)-scoreWeekends(e)}),{solutions:n,count:n.length,capped:l}}function updateNavUI(e){const t=solutionPools[e],n=SOLVE_META[e];if(!t)return;const o=document.getElementById(n.navId),a=document.getElementById(n.posId),s=document.getElementById(n.prevId),l=document.getElementById(n.nextId);if(!o)return;o.style.display="flex";const i=t.capped?"50+":String(t.count);a.textContent=t.index>=0?`${t.index+1} / ${i}`:`0 / ${i}`,s.disabled=t.index<=0,l.disabled=t.index>=t.solutions.length-1}function applyPoolSolution(e,t){Object.keys(state.entries).forEach(e=>{const t=state.entries[e];t&&"object"==typeof t&&"OFFPREFER"===t.code&&!1===t.solved&&delete state.entries[e]}),t.forEach(e=>{state.entries[e]={code:"OFFPREFER",solved:!1}}),runSolver(),renderCalendar(),document.getElementById("btnSolveDone").style.display="inline-block",updateNavUI(e);const n=document.getElementById("solutionMessage");n&&(n.style.display="none")}function rebuildPoolIfNeeded(e){if(solutionPools[e])return;if(state.lastBidContext)state.lastBidContext.L=currentLayer();else{const e=currentLayer(),t=state.gridDates.filter(e=>"month"===e.zone);let n=0,o=0;t.forEach(e=>{effectiveProratedAt(e.iso)&&n++,effectiveDutyAt(e.iso)&&o++});const a=t.length-n,{daysOff:s,fourDay:l}=prorationLookup(a);state.lastBidContext={L:e,fourDay:l,available:a,daysOff:s,actualOff:t.length-n-o}}const{solutions:t,count:n,capped:o}=collectAndSortSolutions(e,50);solutionPools[e]={solutions:t,index:-1,count:n,capped:o},updateNavUI(e)}function stepSolution(e,t){rebuildPoolIfNeeded(e);const n=solutionPools[e],o=SOLVE_META[e],a=document.getElementById("solutionMessage");if(!n||0===n.solutions.length)return void(a&&(a.style.display="block",a.textContent=`No "${o.label}" solution found with current settings. Try a different style or adjust your waivers.`));const s=n.index+t;s<0||s>=n.solutions.length?a&&s>=n.solutions.length&&(a.style.display="block",a.textContent=`You've seen all ${n.solutions.length} "${o.label}" solution${1===n.solutions.length?"":"s"}. Try a different style or press Done.`):(n.index=s,applyPoolSolution(e,n.solutions[n.index]))}function initSolveUI(){const e=document.getElementById("solveSpinnerRow"),t=document.getElementById("solveRows");e.style.display="flex",t.style.display="none",setTimeout(()=>{["weekends","longoff","balanced"].forEach(e=>{const{solutions:t,count:n,capped:o}=collectAndSortSolutions(e,50);solutionPools[e]={solutions:t,index:-1,count:n,capped:o},updateNavUI(e)}),e.style.display="none",t.style.display="block"},0)}function getCode(e,t){const n=state.entries[e];return n?n.code:state.monthIsoSet&&state.monthIsoSet.has(e)?"RSV":null}function isDutyCode(e){if(!e)return!1;const t=MASTER_CODES[e];return!!t&&!!t.duty}function isProratedCode(e){if(!e)return!1;const t=MASTER_CODES[e];return!!t&&!!t.prorated}function classify15(e){const t=state.gridDates,n=t.findIndex(t=>t.iso===e);if(n<=0)return null;const o=t[n-1].iso,a=getCode(o);return"CQT"===a?"travel":"15"===a&&"travel"===classify15(o)?"dutyfree":null}function effectiveDutyAt(e){const t=getCode(e);if("15"===t){const t=classify15(e);if("travel"===t)return!0;if("dutyfree"===t)return!1}return isDutyCode(t)}function effectiveProratedAt(e){const t=getCode(e);if("15"===t){const t=classify15(e);if("travel"===t||"dutyfree"===t)return!1}return isProratedCode(t)}function resolveLineCode(e){switch(e){case"L":case"L-S":return"TRL";default:return"TRS"}}function runSolver(){const e=[],t=effectiveLayer(currentLayer()),n=state.gridDates;n.forEach(e=>{const t=state.entries[e.iso];t&&t.solved&&delete state.entries[e.iso]});const o=n.findIndex(e=>isoDate(state.monthEnd)===e.iso);if(-1===o)return e;let a=0;for(let e=o;e>=0&&effectiveDutyAt(n[e].iso);e--)a++;if(0===a||a>=t.min)return e;const s=t.min-a,l=""===t.flyThrough?1/0:parseInt(t.flyThrough);if(0===l)return e;const i=Math.min(s,l,n.length-o-1),d=resolveLineCode(t.line);for(let e=1;e<=i;e++){const t=n[o+e];if(!t||"post-buffer"!==t.zone)break;state.entries[t.iso]={code:d,solved:!0}}return i"outside"!==e.zone),t=[];let n=null,o=0,a=!1;return e.forEach((s,l)=>{const i=effectiveDutyAt(s.iso);i&&(0===o&&(n=l),o++,"month"===s.zone&&(a=!0)),i&&l!==e.length-1||(o>0&&a&&t.push({startIso:e[n].iso,endIso:e[l===e.length-1&&i?l:l-1].iso,length:o}),o=0,a=!1)}),t}function checkBufferGaps(){const e=[],t=e=>{const t=new Date(e+"T00:00:00");return t.getMonth()+1+"/"+t.getDate()+"/"+t.getFullYear()},n=isoDate(state.monthStart),o=isoDate(state.monthEnd),a=effectiveDutyAt(n),s=effectiveDutyAt(o),l=state.gridDates.filter(e=>"pre-buffer"===e.zone),i=l.findIndex(e=>null!==getCode(e.iso));if(a&&-1!==i){const n=l.slice(i);n.some(e=>null===getCode(e.iso))&&e.push(`You entered a code on ${t(n[0].iso)}, in the month before your bid month, but left other days blank between ${t(n[0].iso)} and ${t(l[l.length-1].iso)} (the last day of that month). Because a Work Block that crosses into your bid month is evaluated as one continuous block, please enter a code (duty or day off) for every day from ${t(n[0].iso)} through ${t(l[l.length-1].iso)}.`)}const d=state.gridDates.filter(e=>"post-buffer"===e.zone);let r=-1;for(let e=d.length-1;e>=0;e--)if(null!==getCode(d[e].iso)){r=e;break}return s&&-1!==r&&d.slice(0,r+1).some(e=>null===getCode(e.iso))&&e.push(`You entered a code on ${t(d[r].iso)}, in the month after your bid month, but left other days blank between ${t(d[0].iso)} and ${t(d[r].iso)}. Because a Work Block that crosses out of your bid month is evaluated as one continuous block, please enter a code (duty or day off) for every day from ${t(d[0].iso)} through ${t(d[r].iso)}.`),e}function countSingleDaysOff(){const e=state.gridDates.filter(e=>"outside"!==e.zone),t=isoDate(state.monthStart),n=isoDate(state.monthEnd);let o=0;const a=[];for(let s=0;s0&&effectiveDutyAt(e[s-1].iso),d=s0&&!effectiveDutyAt(e[s-1].iso)&&null!==getCode(e[s-1].iso),c=s=t&&l.iso<=n))continue;const u=i||d,f=l.iso===n;l.iso===t&&s>0&&getCode(e[s-1].iso)&&!effectiveDutyAt(e[s-1].iso)&&!u||(f||u)&&(o++,a.push(l.iso))}return{count:o,isos:a}}function longestOffBlock(){const e=state.gridDates.filter(e=>"month"===e.zone);let t=0,n=0;return e.forEach(e=>{effectiveDutyAt(e.iso)||effectiveProratedAt(e.iso)?n=0:(n++,t=Math.max(t,n))}),t}function checkFirstDayDFP(){const e=state.gridDates,t=isoDate(state.monthStart),n=e.findIndex(e=>e.iso===t);if(n<=0)return!1;const o=e[n-1].iso,a=null!==getCode(o)&&!effectiveDutyAt(o),s=n>=2&&effectiveDutyAt(e[n-2].iso);return!(!a||!s||null!==getCode(t)&&!effectiveDutyAt(t))}function checkScheduleRules(e,t,n){e=effectiveLayer(e);const o=[];findWorkBlocks().forEach(t=>{const n=new Date(t.endIso+"T00:00:00"),a=n.getMonth()+1+"/"+n.getDate()+"/"+n.getFullYear();if(t.length>e.max)o.push(`You have a 'Work Block' ending on ${a} that is ${t.length} days long which is longer than allowed by the dashboard or your user preferences.`);else if(t.length8&&!e.waive4off&&longestOffBlock()<4&&o.push("You must bid 4 consecutive days off or check the 'Waive Reserve Block of 4 Days Off' box.");const a=countSingleDaysOff(),s=e.allowSingle?1:0;if(a.count>s){const t=a.isos.map(e=>{const t=new Date(e+"T00:00:00");return t.getMonth()+1+"/"+t.getDate()+"/"+t.getFullYear()}),n=a.isos.length>1,l=t.join(", ");let i;i=e.allowSingle||1!==a.count?` Adjust your bid on ${l} so ${n?"those days off are":"that day off is"} part of a longer block of days off (or ${n?"they are":"it is"} converted to duty/RSV day${n?"s":""}).`:` This could be fixed by checking the 'Allow 1 Single Reserve Day Off' waiver, or by adjusting your bid on ${l} so that day off is part of a longer block of days off (or is converted to a duty/RSV day).`,o.push(`You are only allowed ${1===s?"1 instance":"0 instances"} of a Single Reserve Day Off.${i}`)}return!e.waiveFirstDFP&&checkFirstDayDFP()&&o.push("Based on your indicated duty and single day off at the end of last month, you need to select 'Waive First Day of Month DFP'."),o}document.getElementById("btnSolveWeekends").addEventListener("click",()=>stepSolution("weekends",1)),document.getElementById("btnSolveLongOff").addEventListener("click",()=>stepSolution("longoff",1)),document.getElementById("btnSolveBalanced").addEventListener("click",()=>stepSolution("balanced",1)),document.getElementById("btnWkPrev").addEventListener("click",()=>stepSolution("weekends",-1)),document.getElementById("btnWkNext").addEventListener("click",()=>stepSolution("weekends",1)),document.getElementById("btnLoPrev").addEventListener("click",()=>stepSolution("longoff",-1)),document.getElementById("btnLoNext").addEventListener("click",()=>stepSolution("longoff",1)),document.getElementById("btnBalPrev").addEventListener("click",()=>stepSolution("balanced",-1)),document.getElementById("btnBalNext").addEventListener("click",()=>stepSolution("balanced",1)),document.getElementById("btnSolveDone").addEventListener("click",()=>{document.getElementById("suggestionBox").style.display="none",runCheckBid()}),document.getElementById("btnClearCalendar").addEventListener("click",()=>{confirm("Clear the calendar? This removes all entered codes for this layer's view.")&&(state.entries={},state.focusedDate=null,state.selectedCode=null,document.querySelectorAll(".code-btn").forEach(e=>e.classList.remove("selected")),renderCalendar(),updateGlance(),hideErrors())});const FEASIBILITY_NODE_LIMIT=1e5,FEASIBILITY_OFF_CODE="OFF";function checkPrefixStreaks(e,t,n,o){n=effectiveLayer(n);let a=0,s=!1;for(let l=0;l<=o;l++)if(void 0===t[l]?effectiveDutyAt(e[l].iso):t[l]){if(a++,"month"===e[l].zone&&(s=!0),s&&a>n.max)return!1}else{if(s&&a>0&&a=0;o--)return!1===(void 0!==t[o]?t[o]:effectiveDutyAt(e[o].iso));return!1}return!0}function searchFeasibleCompletion(e,t,n,o,a,s={}){const l=s.bias||"default",i=s.exclude||[],d=state.gridDates.filter(e=>"outside"!==e.zone),r=[];d.forEach((e,t)=>{"month"===e.zone&&void 0===state.entries[e.iso]&&r.push(t)});const c=o-a;if(c<0||c>r.length)return{feasible:!1,reason:"impossible"};const u=new Array(d.length);let f=0,y=null;return function o(a,s){if(y||f++>FEASIBILITY_NODE_LIMIT)return;if(s>c)return;if(s+(r.length-a)!1===u[e]).map(e=>d[e].iso));if(i.some(t=>t.size===e.size&&[...t].every(t=>e.has(t))))return}return void(function(){const o=[];r.forEach(e=>{!1===u[e]&&(state.entries[d[e].iso]={code:FEASIBILITY_OFF_CODE,solved:!1},o.push(d[e].iso))});const a=0===checkScheduleRules(e,t,n).length;return o.forEach(e=>delete state.entries[e]),a}()&&(y=u.slice()))}const m=r[a],h=preferOffAt(d,u,m,l)?[!1,!0]:[!0,!1];for(const t of h){if(y)return;!1===t&&s+1>c||(u[m]=t,checkPrefixStreaks(d,u,e,m)&&o(a+1,s+(!1===t?1:0)))}u[m]=void 0}(0,0),y?{feasible:!0,isos:r.filter(e=>!1===y[e]).map(e=>d[e].iso)}:{feasible:!1,reason:f>FEASIBILITY_NODE_LIMIT?"search_limit":"infeasible"}}function findFeasibleCompletion(e,t,n,o,a){return searchFeasibleCompletion(e,t,n,o,a)}function findBiasedSolution(e,t=[]){const n=state.lastBidContext;if(!n)return{feasible:!1,reason:"no_context"};const{L:o,fourDay:a,available:s,daysOff:l,actualOff:i}=n;if("weekends"===e)return searchFeasibleCompletion(o,a,s,l,i,{bias:"weekends",exclude:t});if("longoff"===e)return searchFeasibleCompletion(o,a,s,l,i,{bias:"continuation",exclude:t});if("balanced"===e){const e=effectiveLayer(o);let n={feasible:!1,reason:"infeasible"};for(let o=Math.max(3,e.min);o<=e.max;o++)if(n=searchFeasibleCompletion(o===e.max?e:{...e,max:o},a,s,l,i,{bias:"default",exclude:t}),n.feasible)return n;return n}return{feasible:!1,reason:"unknown_type"}}function runCheckBid(){resetSolutionPools();const e=currentLayer(),t=[];t.push(...runSolver()),renderCalendar();const n=state.gridDates.filter(e=>"month"===e.zone),o=n.length;let a=0,s=0,l=0;n.forEach(e=>{const t=getCode(e.iso);effectiveProratedAt(e.iso)&&a++,effectiveDutyAt(e.iso)&&(s++,"RSV"!==t&&"TRS"!==t&&"TRL"!==t||l++)});const i=o-a,{daysOff:d,fourDay:r}=prorationLookup(i),c=i-d,u=o-a-s;if(state.lastStats={requiredRSV:c,rsvDays:l,daysOff:d,actualOff:u,proratedDays:a,available:i},t.push(...checkBufferGaps()),state.lastBidContext=null,u>d)t.push(`You need to bid ${d} days off.`);else if(u{const t=document.createElement("li");"object"==typeof e&&null!==e?(t.textContent=e.text,"warning"===e.level&&t.classList.add("warning")):t.textContent=e,n.appendChild(t)}));const s=document.getElementById("suggestionBox");if(s){s.style.display=state.lastBidContext?"block":"none";const e=document.getElementById("solutionMessage");e&&(e.style.display="none"),state.lastBidContext&&initSolveUI()}}function hideErrors(){document.getElementById("errorPanel").style.display="none"}function updateGlance(){const e=state.gridDates.filter(e=>"month"===e.zone),t=e.length;let n=0,o=0,a=0;e.forEach(e=>{const t=getCode(e.iso);effectiveProratedAt(e.iso)&&n++,effectiveDutyAt(e.iso)&&(o++,"RSV"!==t&&"TRS"!==t&&"TRL"!==t||a++)});const s=t-n,{daysOff:l}=prorationLookup(s),i=s-l,d=t-n-o;document.getElementById("gl-required").textContent=i,document.getElementById("gl-actual").textContent=a,document.getElementById("gl-off").textContent=`${d} / ${l}`,document.getElementById("gl-prorated").textContent=n}initTool(); const RLC_PALETTE=[{code:"FLY",label:"FLY",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:"5:15",paid:!0},{code:"VC",label:"VC",category:"duty",colorGroup:"proratedLeave",duty:!1,prorated:!0,pay:"4:35",paid:!0},{code:"TRL",label:"TRL",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:"4:03",paid:!0},{code:"VS",label:"VS",category:"duty",colorGroup:"proratedLeave",duty:!1,prorated:!0,pay:null,paid:!0},{code:"TRS",label:"TRS",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:"4:13",paid:!0},{code:"RT",label:"RT",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:null,paid:!0},{code:"RSV",label:"RSV",category:"rsv",colorGroup:"rsv",duty:!0,prorated:!1,pay:null,paid:!0},{code:"OFFMUST",label:"OFF Must",category:"offmust",colorGroup:"daysOff",duty:!1,prorated:!1,pay:"0:00",paid:!1},{code:"CQT",label:"CQT",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:"4:00",paid:!0},{code:"OFFPREFER",label:"OFF Prefer",category:"offprefer",colorGroup:"offprefer",duty:!1,prorated:!1,pay:"0:00",paid:!1},{code:"SA",label:"SA",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:null,paid:!0},{code:"MIL",label:"MIL",category:"duty",colorGroup:"proratedLeave",duty:!1,prorated:!0,pay:"0:00",paid:!1},{code:"R2",label:"R2",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:null,paid:!0},{code:"OE",label:"OE",category:"duty",colorGroup:"proratedTraining",duty:!1,prorated:!0,pay:null,paid:!0},{code:"TNG",label:"TNG",category:"duty",colorGroup:"proratedTraining",duty:!0,prorated:!0,pay:"4:00",paid:!0},{code:"AT",label:"AT",category:"duty",colorGroup:"proratedTraining",duty:!1,prorated:!0,pay:null,paid:!0},{code:"DFP",label:"DFP",category:"duty",colorGroup:"daysOff",duty:!1,prorated:!1,pay:"0:00",paid:!1},{code:"2Q",label:"2Q",category:"duty",colorGroup:"nonProratedDuty",duty:!0,prorated:!1,pay:null,paid:!0},{code:"15",label:"15",category:"duty",colorGroup:"daysOff",duty:!1,prorated:!0,pay:null,paid:null},{code:"VEX",label:"VEX",category:"duty",colorGroup:"daysOff",duty:!1,prorated:!1,pay:"0:00",paid:!1},{code:"OFF",label:"OFF",category:"duty",colorGroup:"daysOff",duty:!1,prorated:!1,pay:"0:00",paid:!1}],ABSENCE_CODE_ROWS=[["05","TYPE 5",!1,!0,null],["0A","AAMP",!0,!1,!0],["0D","CA UPGD",!0,!1,!0],["0G","GPS GS",!0,!1,!0],["0I","INITTR",!0,!1,!0],["0M","MEX GS",!0,!1,!0],["0O","OTHERTR",!0,!1,!0],["0P","TRAINING",!0,!1,!0],["0Q","REQ6MORE",!0,!1,!0],["0T","TRANSTR",!1,!0,!0],["0U","UPGRADTR",!1,!0,!0],["0V","OBSRIDE",!1,!0,!0],["11","117.11",!1,!0,null],["13","PAID ADJ",!1,!1,!0],["17","117.17",!1,!0,null],["19","117.19A2",!1,!0,null],["1I","INITREM",!1,!0,!0],["1R","CRRETRNG",!0,!1,!0],["1T","TRANSREM",!1,!0,!0],["1U","UPGRDREM",!1,!0,!0],["25","117.25",!1,!0,null],["27","117.27",!1,!0,null],["2I","INITTR",!1,!0,!0],["2N","INT5LESS",!0,!1,!0],["2R","RECURRNT",!0,!1,!0],["2T","FR TRNG",!0,!1,!0],["2U","UPGRADTR",!0,!1,!0],["30","30 HRS",!1,!0,null],["31","777INST",!1,!1,!0],["32","INST SCH",!1,!1,!0],["35","S80INST",!1,!1,!0],["38","300INST",!1,!1,!0],["3B","117.23B",!1,!0,null],["3C","117.23C",!1,!0,null],["5B","117.25B",!1,!0,null],["6B","V6BUYBCK",!1,!0,!1],["6G","REGFL V6",!1,!0,!0],["6M","REGFL V6",!1,!0,!0],["70","TEMPFS",!1,!1,!0],["72","TEMPCA",!1,!1,!0],["7D","7 DAYS",!1,!0,!1],["85","SECURITY",!0,!1,!0],["AA","APPEND A",!1,!0,null],["AE","AWT OE",!1,!0,!0],["AF","ADDFLY",!1,!0,null],["AI","AWT NHTR",!1,!0,!0],["AL","UN LV",!1,!0,!1],["AM","ALTMONTH",!1,!0,!1],["AN","PSP2RTN",!1,!0,!1],["AQ","AWT SCTR",!1,!0,!0],["AS","ASAP",!0,!1,!0],["AT2","AWT LCTR",!1,!0,!0],["AW","AWTGPT",!1,!0,!0],["BR","BEREAVEM",!1,!1,!0],["C7","ROILGLTY",!1,!0,null],["CD","DISPDC/A",!1,!0,null],["CH","CHG OVR",!1,!0,null],["CL","CLEANUP",!1,!0,null],["CM","COM TM",!1,!0,!1],["CO","CONFLICT",!1,!0,null],["CS","CASICKPD",!1,!0,!0],["CT","CTL TRNG",!0,!1,!0],["DB","117.15",!1,!0,null],["DC","DIRCON",!1,!0,null],["DE","DECEASED",!1,!0,!1],["DI","DOM-INTL",!0,!1,!0],["DL","RSTDTYL",!1,!0,!0],["DM","RSTDTYM2",!1,!0,!0],["DN","DISPIENC",!1,!0,null],["DO","DAY OFF",!1,!1,!0],["DP","DISPD",!1,!0,null],["DR","RSTDTY",!1,!0,!0],["DT","DISPD IE",!1,!0,null],["DU","FMDL",!1,!0,!1],["EE","EMP RQST",!1,!0,!1],["EF","ERLY FUR",!1,!0,!1],["EM","ACTOFGOD",!1,!0,!1],["EO","ER NOCR",!1,!0,!1],["ES","ESB RMV",!1,!0,!0],["F6","REGFL V6",!1,!0,!0],["FC","FARLIMIT",!1,!0,null],["FG","INTFL UP",!1,!0,!1],["FL","REGFL UP",!1,!0,!1],["FM","REGFL UP",!1,!0,!1],["FR","FURLOUGH",!1,!0,!1],["FS","REGFL SK",!1,!0,!0],["FT","FATIGUED",!1,!0,!0],["FV","REGFL VC",!1,!0,!0],["G6","REGFL V6",!1,!0,!0],["GF","REGFL UP",!1,!0,!1],["GM","INTFL UP",!1,!0,!1],["GS","TRNRSIM",!1,!0,!1],["GV","REGFL VC",!1,!0,!0],["HR","EMPORIEN",!0,!1,!0],["I6","REGFL V6",!1,!0,!0],["IE","INOPTEXP",!1,!0,null],["IF","PUIFNDED",!1,!0,!0],["IH","PARPYIOD",!1,!0,!0],["IP","CP IOD",!1,!0,!0],["IS","REGFL SK",!1,!0,!0],["IU","UNPD IOD",!1,!0,!0],["IV","REGFL VC",!1,!0,!0],["JD","JURYDUTY",!1,!1,!0],["LB","ILGLBK",!1,!0,null],["LC2","AWTGDIVC",!1,!0,!1],["LE","IOEPAID",!1,!1,!0],["LF","INTFL UP",!1,!0,!1],["LK","DODSRD",!1,!0,null],["LL","LLREMOVL",!1,!0,null],["LP","PARENTLV",!1,!0,!0],["LS","LOSAUDIT",!0,!1,!0],["LV","VOLLEAVE",!1,!0,!1],["M1","ADPLOA",!1,!0,!1],["M2","SKLONG",!1,!0,!0],["M3","UPMTY",!1,!0,!0],["M4","ULOA",!1,!0,!1],["MA","MISDASMT",!1,!0,!1],["MC","MISCON",!1,!0,null],["MD","MDSB",!1,!0,!0],["MF","IMTFL UP",!1,!0,!1],["MG","REGFL UP",!1,!0,!1],["MI","MEDIND",!1,!0,null],["ML","CRMLOA",!1,!0,!1],["MN","RSVUMLOA",!1,!0,!1],["MO","MLTYLV",!1,!0,!1],["MR","MLTYWKND",!1,!0,!1],["MS","MLSKSHRT",!1,!0,!0],["MT","MISSED",!1,!0,!1],["MV","MIL PVD",!1,!0,!0],["MX","UNCRMLOA",!1,!0,!1],["MY","MLTYLV17",!1,!0,!1],["ND","NEED T O",!1,!1,!0],["NE","117.19NO",!1,!0,!1],["NF","NOTFIT",!1,!0,!0],["NS","NO SHOW",!1,!0,!1],["OE2","OEBLOCK",!1,!0,!0],["ON","ON DUTY",!1,!0,null],["OP","OVRPROJ",!1,!0,null],["OT","TTOTTRAD",!1,!0,null],["OX","DISPD OE",!1,!0,null],["P6","V6 PE",!1,!0,!0],["PD","PD DISPD",!1,!0,null],["PI","INSTR PI",!0,!1,!0],["PL","PLOA",!1,!0,!1],["PP","PAYPROT",!1,!0,null],["PR","PROB",!0,!1,!0],["PT","LOA10-02",!1,!0,!1],["PU","PDUNLV",!1,!1,!0],["PW","PDWITHLD",!1,!0,!0],["PX","PAID TFR",!1,!1,!0],["QD","NEEDSDOC",!1,!0,null],["QE","QUALEXPR",!1,!0,!0],["QI","NOT QUAL",!1,!0,!0],["R6","XTRAVCDY",!1,!0,!0],["RA","TYPE 1",!1,!0,null],["RD","RPL DFP",!1,!0,null],["RE","RESIGNED",!1,!0,!1],["RI","RCURIOD",!1,!0,!0],["RL","RELEASED",!1,!0,!1],["RM","RMVUNPD",!1,!0,null],["RT2","RETIRED",!1,!0,!1],["S2","C19 SHOT",!1,!1,!0],["S6","SCHL PVD",!1,!0,!0],["SA2","SPCASSGN",!0,!1,!0],["SD","SEQDROP",!1,!0,null],["SF","SIM FILL",!0,!1,!0],["SI","SKIFNDED",!1,!0,!1],["SK","SICK",!1,!0,!0],["SL","SLOA",!1,!0,!0],["SN","SCHLUNPD",!1,!0,!1],["ST","SHRDTRIP",!1,!0,null],["SU","SUSPEND",!1,!0,!1],["SV","SCHOOLPD",!1,!0,!0],["T6","TTOTPVD",!1,!0,!0],["TC","COM MT",!1,!0,!1],["TE","TRNGOFF",!1,!0,!0],["TI","TRNGOFF",!1,!0,!0],["TM","MISSTRIP",!1,!0,!1],["TO","TRFF",!1,!0,!0],["TQ","TRFF OE",!1,!0,!0],["TT","TRIPTRAD",!1,!0,null],["TW","TWA MTG",!1,!1,!0],["TX","UNPD TFR",!1,!0,!1],["U8","U8",!1,!0,!0],["UA","UNPLOA",!1,!0,!1],["UD","UCMENTOR",!1,!0,!0],["UI","RECUR IU",!1,!0,!0],["UL","PDUNLVNC",!0,!1,!0],["UM","UPSMTNY",!1,!0,!0],["UR","UNABLRPT",!1,!0,!1],["US","UNPD SK",!1,!0,!0],["V1","CUR VC",!1,!0,!0],["V2","ACCRU VC",!1,!0,!0],["V6","P VACDAY",!1,!0,!0],["V8","8 IN 24",!1,!0,null],["VA","V6HURCAN",!1,!0,!0],["VB","VCBANK",!1,!0,!0],["VC2","VACATION",!1,!0,!0],["VG","REGFL VC",!1,!0,!0],["VL","VELOA",!1,!0,!1],["VM","REGFL VC",!1,!0,!0],["VO","12MVEOP",!1,!0,!1],["VP","VPLOA",!1,!0,!1],["VS2","VAC SLID",!1,!0,!0],["VT","VSTLOA",!1,!0,!1],["VU","UNPD 48",!1,!0,null],["VV","REGFL VC",!1,!0,!0],["VW","XTRA VC",!1,!0,!0],["W6","WINGSV6",!1,!0,!0],["WG","WINGS",!1,!1,!0],["WP","WITNESSP",!1,!1,!0],["WS","WH SVC",!1,!0,!1],["WU","WITNESSU",!1,!0,!1],["X6","TEX PVD",!1,!0,!0],["XR","CXDRMVL",!1,!0,null]],ABSENCE_CODES=ABSENCE_CODE_ROWS.map(([o,a,r,l,T])=>({code:o,description:a,category:"absence",duty:r,prorated:l,pay:null,paid:T})),MASTER_CODES={};ABSENCE_CODES.forEach(o=>MASTER_CODES[o.code]=o),RLC_PALETTE.forEach(o=>MASTER_CODES[o.code]=o);const CONTRACT_MONTH_DAY_START={1:"-01-01",2:"-01-31",3:"-03-02",4:"-04-01",5:"-05-02",6:"-06-02",7:"-07-02",8:"-08-01",9:"-08-31",10:"-10-01",11:"-11-01",12:"-12-02"};function isLeapYear(o){return o%4==0&&o%100!=0||o%400==0}function contractMonthLength(o,a){return{1:30,2:isLeapYear(a)?31:30,3:30,4:31,5:31,6:30,7:30,8:30,9:31,10:31,11:31,12:30}[o]}function contractMonthStart(o,a){return new Date(a+CONTRACT_MONTH_DAY_START[o]+"T00:00:00")}function prorationLookup(o){return o>=31?{daysOff:13,fourDay:!0,twoDay:!0}:o>=30?{daysOff:12,fourDay:!0,twoDay:!0}:o>=28?{daysOff:11,fourDay:!0,twoDay:!0}:o>=26?{daysOff:10,fourDay:!0,twoDay:!0}:o>=23?{daysOff:9,fourDay:!0,twoDay:!0}:o>=20?{daysOff:8,fourDay:!0,twoDay:!0}:o>=17?{daysOff:7,fourDay:!0,twoDay:!0}:o>=14?{daysOff:6,fourDay:!0,twoDay:!0}:o>=11?{daysOff:5,fourDay:!0,twoDay:!1}:o>=9?{daysOff:4,fourDay:!0,twoDay:!1}:o>=7?{daysOff:3,fourDay:!1,twoDay:!1}:o>=5?{daysOff:2,fourDay:!1,twoDay:!1}:o>=3?{daysOff:1,fourDay:!1,twoDay:!1}:{daysOff:0,fourDay:!1,twoDay:!1}}