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.
August pairings are live!
August pairings are now live in BidGrid and ready for you to start building your bid.
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
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}}