1000-2000T Inland Barge Calculator
:root {
–navalt: #0A2540; –navalt-light: #1F4E78; –accent: #00B050; –negative: #C00000;
–bg: #F7F9FC; –panel: #FFFFFF; –border: #E1E8F0; –text: #1A1A1A; –muted: #5A6678;
}
* { box-sizing: border-box; }
.ibc-app {
font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, Helvetica, Arial, sans-serif;
background: var(–bg); color: var(–text); font-size: 14px; line-height: 1.5;
margin: 0; padding: 0; min-height: 600px;
}
.ibc-header { background: var(–navalt); color: #fff; padding: 18px 28px; border-radius: 6px 6px 0 0; }
.ibc-header h2 { margin: 0; font-size: 20px; font-weight: 600; }
.ibc-header .sub { font-size: 12px; opacity: 0.85; margin-top: 3px; }
.ibc-modetoggle { display: flex; gap: 8px; margin: 16px 28px 0; }
.ibc-modetoggle button {
flex: 1; padding: 10px; border: 1px solid var(–border); background: #fff;
border-radius: 4px; cursor: pointer; font-weight: 600; font-size: 13px;
color: var(–muted);
}
.ibc-modetoggle button.active { background: var(–navalt); color: #fff; border-color: var(–navalt); }
.ibc-main { display: grid; grid-template-columns: 340px 1fr; gap: 20px; padding: 16px 28px 28px; }
@media (max-width: 768px) { .ibc-main { grid-template-columns: 1fr; } }
.ibc-panel { background: var(–panel); border: 1px solid var(–border); border-radius: 6px; padding: 18px; margin-bottom: 14px; }
.ibc-panel h3 { margin: 0 0 10px; font-size: 13px; font-weight: 600; color: var(–navalt);
text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 2px solid var(–navalt-light); padding-bottom: 6px; }
.ibc-panel h4 { margin: 10px 0 4px; font-size: 11px; font-weight: 600; color: var(–muted);
text-transform: uppercase; letter-spacing: 0.3px; }
.ibc-row { display: grid; grid-template-columns: 1fr 90px; gap: 8px; align-items: center; margin-bottom: 4px; }
.ibc-row label { font-size: 12.5px; }
.ibc-row input {
width: 100%; padding: 4px 6px; border: 1px solid var(–border); border-radius: 3px;
font-size: 12.5px; text-align: right; color: var(–navalt); font-weight: 600; background: #FAFBFD;
}
.ibc-row input:focus { outline: none; border-color: var(–navalt-light); background: #fff; }
.ibc-advanced { display: none; }
.ibc-advanced.show { display: block; }
table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
th, td { padding: 5px 7px; text-align: right; border-bottom: 1px solid var(–border); }
th:first-child, td:first-child { text-align: left; }
th { font-weight: 600; color: var(–muted); font-size: 10.5px;
text-transform: uppercase; letter-spacing: 0.3px; background: #F7F9FC; }
tr.total td { font-weight: 700; background: #FFF8E0; border-top: 2px solid var(–navalt); }
tr.subtotal td { font-weight: 600; background: #F0F4F9; }
.ibc-num { font-variant-numeric: tabular-nums; }
.ibc-good { color: var(–accent); font-weight: 600; }
.ibc-bad { color: var(–negative); font-weight: 600; }
.ibc-headlines { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 14px; }
@media (max-width: 600px) { .ibc-headlines { grid-template-columns: repeat(2, 1fr); } }
.ibc-hl { background: var(–navalt); color: #fff; padding: 12px; border-radius: 5px; }
.ibc-hl .lbl { font-size: 10.5px; opacity: 0.8; text-transform: uppercase; letter-spacing: 0.4px; }
.ibc-hl .v { display: flex; justify-content: space-between; margin-top: 5px; font-variant-numeric: tabular-nums; }
.ibc-hl .v div span { display: block; font-size: 9.5px; opacity: 0.65; }
.ibc-hl .v div b { font-size: 16px; font-weight: 600; }
.ibc-hl .v .ds { opacity: 0.7; }
.ibc-hl .v .bs b { color: #7FE3A3; }
.ibc-chart { max-width: 100%; height: 300px !important; }
.ibc-note { font-size: 11px; color: var(–muted); font-style: italic; margin-top: 6px; }
.ibc-reset {
background: var(–navalt); color: #fff; border: none; padding: 7px 14px;
border-radius: 3px; cursor: pointer; font-size: 12px; margin-top: 8px; width: 100%;
}
.ibc-tabs { display: flex; gap: 4px; margin-bottom: 12px; border-bottom: 2px solid var(–border); }
.ibc-tabs button {
background: none; border: none; padding: 8px 14px; cursor: pointer;
font-weight: 600; color: var(–muted); border-bottom: 3px solid transparent; margin-bottom: -2px;
font-size: 12.5px;
}
.ibc-tabs button.active { color: var(–navalt); border-bottom-color: var(–navalt); }
.ibc-tabpane { display: none; }
.ibc-tabpane.active { display: block; }
.ibc-rotation-grid {
display: grid; grid-template-columns: 70px repeat(6, 1fr); gap: 1px; background: var(–border);
font-size: 10px; margin: 8px 0;
}
.ibc-rotation-grid div { background: #fff; padding: 4px; text-align: center; }
.ibc-rotation-grid .hdr { background: var(–navalt); color: #fff; font-weight: 600; font-size: 9.5px; }
.ibc-rotation-grid .lbl { background: #F0F4F9; font-weight: 600; text-align: left; padding-left: 6px; }
Inputs
Revenue & Operating
Cargo capacity (t)
Utilisation
Cargo rate (₹/t-km)
Energy Prices
Diesel price (₹/L)
Grid tariff (₹/kWh)
Financial
Discount rate
Energy escalation
Project horizon (yrs)
Vessel & Route
Voyage distance (km)
Swap stops
Operating days/yr
Fleet size (barges)
Power
BS power cruise (kW)
BS power manoeuv (kW)
Diesel scale factor
Diesel SFC (g/kWh)
CAPEX
Hull (Cr INR)
Diesel powertrain (Cr)
Electric powertrain (Cr)
Battery cost (₹/kWh)
Replacement (₹/kWh)
OPEX
Crew cost/day (₹)
Crew × shifts
Diesel maint %
BS maint %
Insurance %CAPEX
Battery
DoD
Degradation buffer
Pack ratio (:barges)
Replacement (yrs)
Other
Scrap % of hull
Solar plant (kW)
All formulas recalculate on every keystroke. Defaults match the published 2000 T model.
TCO Breakdown (PV, Cr INR)
Cumulative TCO (Nominal)
Voyage Profile
Battery Pack Sizing & Cycles
Pack Rotation (first 24 hrs)
Round-robin assignment, 4-hour fast charge at swap stations B/C/D.
Cargo Rate Sensitivity
Diesel Price Sensitivity
Lifecycle Emissions & Externalities
Net CO₂ savings = (diesel emissions) − (grid emissions). Improves substantially as the grid decarbonises.
https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js
(function() {
const ids = [‘cargoCap’,’util’,’cargoRate’,’dieselPrice’,’gridRate’,’disc’,’energyEsc’,’horizon’,
‘dist’,’stops’,’opdays’,’fleet’,’pCruise’,’pManov’,’dieselScale’,’sfc’,’hull’,’dCapex’,’bsCapex’,’batCost’,’batReplCost’,
‘crewDay’,’crewSets’,’dMaint’,’bsMaint’,’insRate’,’dod’,’degr’,’packRatio’,’batCycle’,’scrap’,’solar’];
const defaults = {};
ids.forEach(id => defaults[id] = document.getElementById(id).value);
window.ibcReset = function() {
ids.forEach(id => document.getElementById(id).value = defaults[id]);
recalc();
};
function getVals() {
const v = {};
ids.forEach(id => v[id] = parseFloat(document.getElementById(id).value) || 0);
return v;
}
function fmt(n, dec=1) {
if (n === null || n === undefined || isNaN(n)) return ‘—’;
if (Math.abs(n) >= 1000) return n.toLocaleString(‘en-IN’, {maximumFractionDigits:0});
return n.toFixed(dec);
}
function fmtPct(n) { return (n*100).toFixed(1) + ‘%’; }
function npv(rate, flows) {
let s = 0;
for (let t = 0; t < flows.length; t++) s += flows[t] / Math.pow(1+rate, t+1);
return s;
}
function irr(flows) {
let lo=-0.99, hi=10;
for (let i=0; i<100; i++) {
const mid = (lo+hi)/2;
let n = 0;
for (let t=0; t<flows.length; t++) n += flows[t] / Math.pow(1+mid, t);
if (Math.abs(n) 0) lo = mid; else hi = mid;
}
return (lo+hi)/2;
}
function payback(flows) {
let cum = 0;
for (let t=0; t= 0) return t;
}
return null;
}
function compute(v) {
const H = Math.round(v.horizon);
const tripDist = v.dist / (v.stops + 1);
const tripsPerVoyage = v.stops + 1;
const cruiseSpeed = 7; const manovSpeed = 5; const manovDist = 1;
const tripTimeMin = (tripDist – manovDist)/cruiseSpeed/1.852*60 + manovDist/manovSpeed/1.852*60;
const swapTime = 30, cargoTime = 1304;
const voyageTimeMin = tripsPerVoyage*tripTimeMin + v.stops*swapTime + cargoTime;
const voyageDays = voyageTimeMin / 1440;
const noVoyages = Math.round(v.opdays / voyageDays);
const cargoPerVoyage = v.cargoCap * v.util;
const cargoPerYear = cargoPerVoyage * noVoyages;
const revPerVoyage = cargoPerVoyage * v.dist * v.cargoRate;
const revY1 = revPerVoyage * noVoyages / 1e7;
const propEnergyPerLeg = v.pCruise * (tripTimeMin-6)/60 + v.pManov * 6/60;
const propEnergyPerVoyage = propEnergyPerLeg * tripsPerVoyage;
const auxLoad = 23;
const auxEnergyPerVoyage = auxLoad * (tripsPerVoyage*tripTimeMin + v.stops*swapTime)/60;
const auxCharging = auxLoad * cargoTime/60;
const totalEnergyPerVoyage = propEnergyPerVoyage + auxEnergyPerVoyage + auxCharging;
const solarPerVoyage = v.solar * 3.5 * voyageDays;
const gridPerVoyage = totalEnergyPerVoyage – solarPerVoyage;
const gridY1 = gridPerVoyage * v.gridRate * noVoyages / 1e7;
const dieselPCruise = v.pCruise * v.dieselScale;
const dieselPManov = v.pManov * v.dieselScale;
const fuelPerHr = dieselPCruise * v.sfc / 850;
const fuelManovPerHr = dieselPManov * 300 / 850;
const fuelIdle = dieselPManov * v.sfc * 0.2 / 850;
const fuelCargo = auxLoad * 300 / 850;
const fuelPerTrip = fuelPerHr*(tripTimeMin-6)/60 + fuelManovPerHr*6/60 + fuelIdle*swapTime/60;
const fuelPerLastTrip = fuelPerHr*(tripTimeMin-6)/60 + fuelManovPerHr*6/60 + fuelCargo*cargoTime/60;
const fuelPerVoyage = fuelPerTrip * (tripsPerVoyage-1) + fuelPerLastTrip;
const fuelPerYear = fuelPerVoyage * noVoyages;
const dieselY1 = fuelPerYear * v.dieselPrice / 1e7;
const propBatSize = propEnergyPerLeg * (1+v.degr) / v.dod;
const auxBatSize = (auxLoad * tripTimeMin/60) * (1+v.degr) / v.dod;
const batSize = propBatSize + auxBatSize;
const onboardCapex = batSize * v.batCost / 1e7;
const extraCapex = (v.packRatio – 1) * batSize * v.batCost / 1e7;
const dieselCapex = v.hull + v.dCapex;
const bsCapex = v.hull + v.bsCapex + onboardCapex + extraCapex;
const crewY = v.crewDay * v.crewSets * v.opdays / 1e7;
const dieselMaintY1 = dieselY1 * v.dMaint;
const bsMaintY1 = gridY1 * v.bsMaint;
const dieselIns = dieselCapex * v.insRate;
const bsIns = bsCapex * v.insRate;
const replCostPerEvent = batSize * v.packRatio * v.batReplCost / 1e7;
function build(type) {
const capex = type === ‘diesel’ ? dieselCapex : bsCapex;
const energyY1 = type === ‘diesel’ ? dieselY1 : gridY1;
const maintY1 = type === ‘diesel’ ? dieselMaintY1 : bsMaintY1;
const ins = type === ‘diesel’ ? dieselIns : bsIns;
const flows = [-capex];
const yearly = [];
const cumNominal = [capex];
let c = capex;
for (let y = 1; y <= H; y++) {
const esc = Math.pow(1+v.energyEsc, y-1);
const rev = revY1 * esc;
const energy = energyY1 * esc;
const maint = maintY1 * esc;
const crew = crewY * esc;
const insurance = ins;
let batRepl = 0;
if (type === 'bs' && y % v.batCycle === 0 && y npv(v.disc, arr);
const revPV = pv(yearly.map(r => r.rev));
const energyPV = pv(yearly.map(r => r.energy));
const maintPV = pv(yearly.map(r => r.maint));
const crewPV = pv(yearly.map(r => r.crew));
const insPV = pv(yearly.map(r => r.insurance));
const batPV = pv(yearly.map(r => r.batRepl));
const scrapPV = pv(yearly.map(r => r.scrap));
const opex = energyPV + maintPV + crewPV + insPV + batPV;
const tco = opex + capex – scrapPV;
return {
capex, revPV, energyPV, maintPV, crewPV, insPV, batPV, scrapPV, opex, tco,
npv: revPV – tco, irr: irr(flows), payback: payback(flows),
flows, yearly, cumNominal
};
}
return { v, tripTimeMin, tripDist, tripsPerVoyage, voyageDays, noVoyages, cargoPerYear,
propEnergyPerVoyage, gridPerVoyage, fuelPerVoyage, fuelPerYear, propBatSize, auxBatSize, batSize,
onboardCapex, extraCapex, revY1, gridY1, dieselY1, replCostPerEvent,
diesel: build(‘diesel’), bs: build(‘bs’) };
}
let chart;
function recalc() {
const v = getVals();
const r = compute(v);
const d = r.diesel, b = r.bs;
document.getElementById(‘ibc-headlines’).innerHTML = `
TCO (PV, Cr)
Diesel${fmt(d.tco)}
Battery Swap${fmt(b.tco)}
NPV (Cr)
Diesel${fmt(d.npv)}
Battery Swap${fmt(b.npv)}
IRR
Diesel${fmtPct(d.irr)}
Battery Swap${fmtPct(b.irr)}
Payback (yrs)
Diesel${d.payback ?? ‘—’}
Battery Swap${b.payback ?? ‘—’}
`;
const row = (lbl, dv, bv, cls=”) => {
const diff = dv – bv;
const sign = diff > 0.01 ? ‘ibc-good’ : (diff < -0.01 ? 'ibc-bad' : '');
return `
| ${lbl} | ${fmt(dv)} | ${fmt(bv)} | ${diff>=0?’+’:”}${fmt(diff)} |
`;
};
document.getElementById(‘ibc-opex’).innerHTML = `
| Component | Diesel | Battery Swap | Savings |
${row(‘CAPEX’, d.capex, b.capex)}
${row(‘Energy (PV)’, d.energyPV, b.energyPV)}
${row(‘Maintenance (PV)’, d.maintPV, b.maintPV)}
${row(‘Crew (PV)’, d.crewPV, b.crewPV)}
${row(‘Insurance (PV)’, d.insPV, b.insPV)}
${row(‘Battery replacement (PV)’, d.batPV, b.batPV)}
${row(‘Scrap recovery (PV)’, -d.scrapPV, -b.scrapPV)}
${row(‘TCO’, d.tco, b.tco, ‘total’)}
${row(‘Revenue (PV)’, d.revPV, b.revPV, ‘subtotal’)}
${row(‘NPV’, d.npv, b.npv, ‘total’)}
`;
document.getElementById(‘ibc-voyage’).innerHTML = `
| Voyage distance | ${fmt(v.dist)} km |
| Number of stops | ${v.stops} |
| Trip distance (each leg) | ${fmt(r.tripDist)} km |
| Trips per one-way voyage | ${r.tripsPerVoyage} |
| Trip running time | ${fmt(r.tripTimeMin/60,2)} hrs |
| Voyage time (one-way) | ${fmt(r.voyageDays,2)} days |
| Voyages per year | ${r.noVoyages} |
| Cargo per voyage | ${fmt(v.cargoCap*v.util)} t |
| Cargo per year | ${fmt(r.cargoPerYear)} t |
`;
document.getElementById(‘ibc-battery’).innerHTML = `
| Prop energy / voyage | ${fmt(r.propEnergyPerVoyage)} kWh |
| Net grid energy / voyage | ${fmt(r.gridPerVoyage)} kWh |
| Battery pack size | ${fmt(r.batSize)} kWh |
| Onboard battery CAPEX | ₹${fmt(r.onboardCapex)} Cr |
| Extra packs CAPEX (${v.packRatio.toFixed(1)}× ratio) | ₹${fmt(r.extraCapex)} Cr |
| Replacement cost / event | ₹${fmt(r.replCostPerEvent)} Cr |
| Replacement events | Years ${Array.from({length: Math.floor((v.horizon-1)/v.batCycle)}, (_,i) => (i+1)*v.batCycle).join(‘, ‘)} |
`;
let rotHtml = ‘
Hour
‘;
for (let bb = 1; bb <= 6; bb++) rotHtml += `
Barge ${bb}
`;
for (let h = 0; h < 24; h++) {
rotHtml += `
${h.toString().padStart(2,’0′)}:00
`;
for (let bb = 0; bb < 6; bb++) {
const launch = bb * 4;
if (h < launch) { rotHtml += '
—
‘; continue; }
const hrFromLaunch = h – launch;
const tt = r.tripTimeMin/60;
const cycleHr = tt + 0.5;
const legNum = Math.floor(hrFromLaunch / cycleHr);
const inLeg = hrFromLaunch – legNum * cycleHr;
if (legNum >= r.tripsPerVoyage) { rotHtml += ‘
cargo
‘; }
else if (inLeg < tt) { rotHtml += `
B${((bb*r.tripsPerVoyage + legNum) % 9) + 1}
`; }
else { rotHtml += ‘
swap
‘; }
}
}
rotHtml += ‘
‘;
document.getElementById(‘ibc-rotation’).innerHTML = rotHtml;
const cargoRates = [1.06, 1.41, 1.75, 2.0, 2.25, 2.58, 3.0];
const cargoNotes = [‘IWT cost’,’Rail benchmark’,’Cost-plus’,’BASE’,’Stretch’,’Road benchmark’,’Premium’];
let sc = ‘
| Cargo rate | Diesel NPV | BS NPV | Gap | Note |
‘;
cargoRates.forEach((cr, i) => {
const factor = cr / v.cargoRate;
const dN = d.revPV * factor – d.tco;
const bN = b.revPV * factor – b.tco;
const isBase = Math.abs(cr – v.cargoRate) < 0.01;
sc += `| ₹${cr.toFixed(2)} | <td class="ibc-num ${dN${fmt(dN)}${fmt(bN)} | +${fmt(bN-dN)} | ${cargoNotes[i]} |
`;
});
sc += ‘‘;
document.getElementById(‘ibc-sens-cargo’).innerHTML = sc;
const dieselPrices = [80, 100, 120, 140];
const discRates = [0.05, 0.07, 0.09, 0.11];
let sd = ‘
| Diesel ₹/L | ‘;
discRates.forEach(dr => sd += `${(dr*100).toFixed(0)}% | `);
sd += ‘
‘;
dieselPrices.forEach(dp => {
sd += `| ₹${dp} | `;
discRates.forEach(dr => {
const vTest = Object.assign({}, v, {dieselPrice: dp, disc: dr});
const rTest = compute(vTest);
const dN = rTest.diesel.npv;
sd += `<td class="ibc-num ${dN${fmt(dN)}`;
});
sd += ‘
‘;
});
sd += ‘‘;
document.getElementById(‘ibc-sens-diesel’).innerHTML = sd;
const co2Diesel = r.fuelPerYear * 2.64 / 1000;
const co2Grid = r.gridPerVoyage * r.noVoyages * 0.71 / 1000;
const co2Grid2035 = r.gridPerVoyage * r.noVoyages * 0.35 / 1000;
const netSaved = co2Diesel – co2Grid;
const netSaved2035 = co2Diesel – co2Grid2035;
document.getElementById(‘ibc-co2-table’).innerHTML = `
| Metric | Per year | Lifetime (28y) |
| Diesel fuel consumed (L) | ${fmt(r.fuelPerYear)} | ${fmt(r.fuelPerYear*28)} |
| Diesel CO₂ (tonnes) | ${fmt(co2Diesel,0)} | ${fmt(co2Diesel*28,0)} |
| BS grid CO₂ @ today (0.71 kg/kWh) | ${fmt(co2Grid,0)} | ${fmt(co2Grid*28,0)} |
| Net CO₂ saved (today’s grid) | ${fmt(netSaved,0)} | ${fmt(netSaved*28,0)} |
| BS grid CO₂ @ 2035 (0.35 kg/kWh) | ${fmt(co2Grid2035,0)} | ${fmt(co2Grid2035*28,0)} |
| Net CO₂ saved (2035 grid) | ${fmt(netSaved2035,0)} | ${fmt(netSaved2035*28,0)} |
| Diesel import substitution (₹ Cr) | ${fmt(r.fuelPerYear*v.dieselPrice/1e7)} | ${fmt(r.fuelPerYear*28*v.dieselPrice/1e7)} |
`;
const labels = []; for (let y=0; y’₹’+v+’ Cr’ } } } }
});
}
ids.forEach(id => document.getElementById(id).addEventListener(‘input’, recalc));
document.getElementById(‘ibc-mode-lite’).addEventListener(‘click’, () => {
document.getElementById(‘ibc-mode-lite’).classList.add(‘active’);
document.getElementById(‘ibc-mode-full’).classList.remove(‘active’);
document.getElementById(‘ibc-adv’).classList.remove(‘show’);
});
document.getElementById(‘ibc-mode-full’).addEventListener(‘click’, () => {
document.getElementById(‘ibc-mode-full’).classList.add(‘active’);
document.getElementById(‘ibc-mode-lite’).classList.remove(‘active’);
document.getElementById(‘ibc-adv’).classList.add(‘show’);
});
document.querySelectorAll(‘.ibc-tabs button’).forEach(btn => {
btn.addEventListener(‘click’, () => {
document.querySelectorAll(‘.ibc-tabs button’).forEach(b => b.classList.remove(‘active’));
document.querySelectorAll(‘.ibc-tabpane’).forEach(p => p.classList.remove(‘active’));
btn.classList.add(‘active’);
document.getElementById(btn.dataset.tab).classList.add(‘active’);
});
});
recalc();
})();