Source: bemap-services-v2/ev/bemap-charging-time.js

/**
 * BeNomad BeMap JavaScript API - ChargingTime service (v1.5)
 *
 * Promise-based wrapper around `POST service/chargingTime/1.0` — estimates
 * how long it takes to charge an EV from one battery level to another at
 * a given charger.
 *
 * Auth: `ROLE_CHARGINGTIME`.
 *
 * Note: the BeMap server does NOT expose a per-vehicle "curve" endpoint.
 * Customers who need a charging curve to plot call `estimate()` repeatedly
 * with different `remainingBatteryLevel` values (e.g. 10, 20, 30, …, 90)
 * and derive instantaneous power from the differences.
 *
 * Typical usage:
 *
 *     var ct = new bemap.ChargingTime(ctx);
 *     var req = new bemap.ChargingTimeRequest({
 *         vehicle: 'a204c816-...',
 *         chargingPointPower: 50,
 *         chargingCurrentType: bemap.ChargingCurrentType.DC,
 *         remainingBatteryLevel: 20,
 *         chargingBatteryLevel: 80
 *     });
 *     ct.estimate(req).then(function(resp) {
 *         console.log('charging time:', resp.getChargingTime(), 's');
 *         console.log('knee at:', resp.getOptimumBatteryChargeLevel(), '%');
 *     }).catch(function(err) {
 *         if (err.getCode() === bemap.Error.CHARGING_TIME_FAILED) ...;
 *     });
 *
 * @since 1.5.0
 * @public
 * @constructor
 * @extends {bemap.ServiceV2}
 * @param {bemap.Context} context
 * @param {Object} [options]
 */
bemap.ChargingTime = function(context, options) {
    bemap.ServiceV2.call(this, context, options);
};
bemap.inherits(bemap.ChargingTime, bemap.ServiceV2);

/**
 * Estimate a charging duration.
 *
 * @public
 * @param {bemap.ChargingTimeRequest} request
 * @param {Object} [options]
 * @param {AbortSignal} [options.signal]
 * @return {Promise<bemap.ChargingTimeResponse>}
 */
bemap.ChargingTime.prototype.estimate = function(request, options) {
    if (!(request instanceof bemap.ChargingTimeRequest)) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'ChargingTime.estimate requires a bemap.ChargingTimeRequest'
        }));
    }
    // Client-side validation — fail before the network call.
    if (!request.vehicle) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'ChargingTime.estimate: request.vehicle (UUID key) is required'
        }));
    }
    if (request.chargingPointPower === null || request.chargingPointPower <= 0) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'ChargingTime.estimate: request.chargingPointPower must be > 0 (kW)'
        }));
    }
    if (!request.chargingCurrentType) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'ChargingTime.estimate: request.chargingCurrentType is required (bemap.ChargingCurrentType.*)'
        }));
    }
    if (request.chargingBatteryLevel <= request.remainingBatteryLevel) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'ChargingTime.estimate: chargingBatteryLevel must be > remainingBatteryLevel'
        }));
    }

    var opts = options || {};
    var body = request.toJson();
    if (!body.geoserver) body.geoserver = this.ctx.getGeoserver();

    return this._post('service/chargingTime/1.0', body, { signal: opts.signal })
        .then(function(json) {
            var resp = bemap.ChargingTimeResponse.fromJson(json);
            if (resp.isError()) {
                throw new bemap.Error({
                    code: bemap.Error.CHARGING_TIME_FAILED,
                    message: 'The server could not compute a charging time ' +
                             '(zero battery capacity, zero connector power, or zero max charge power)',
                    context: json
                });
            }
            return resp;
        });
};