Skip to content

Commit

Permalink
feat: add /capacity/pool and /capacity/pool/product endpoints (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
rackstar authored Nov 21, 2024
2 parents bd02dd3 + 2cf85c1 commit 5cc5933
Show file tree
Hide file tree
Showing 10 changed files with 882 additions and 191 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Computes the optimal capacity allocation in order to get the best price on cover
- [Quote Route](#quote-route)
- [Capacity Route](#capacity-route)
- [Capacity Route for a specific product](#capacity-route-for-a-specific-product)
- [Capacity Route for all products in a pool](#capacity-route-for-all-products-in-a-pool)
- [Capacity Route for a specific product in a pool](#capacity-route-for-a-specific-product-in-a-pool)

## Setup

Expand Down Expand Up @@ -49,4 +51,24 @@ best available combination of pools for the premium.
- **OpenAPI**: [v2/api/docs/#/Capacity/get_v2_capacity__productId_](https://api.nexusmutual.io/v2/api/docs/#/Capacity/get_v2_capacity__productId_)
- **Description**: Returns the current capacity for a specific product for a period of 30 days if no period query param is specified.

### Capacity Route for all products in a pool
- **URL**: `/v2/capacity/pools/{poolId}`
- **Method**: `GET`
- **OpenAPI**: [v2/api/docs/#/Capacity/get_v2_capacity_pools__poolId_](https://api.nexusmutual.io/v2/api/docs/#/Capacity/
get_v2_capacity_pools__poolId_)

- **Description**: Returns the current capacity for all products in a specific pool for a period of 30 days if no period query param is specified.
- **Parameters**:
- `poolId`: Required path parameter specifying the pool ID.
- `period`: Optional query parameter specifying the period in days (default is 30, range is 28-365).

### Capacity Route for a specific product in a pool
- **URL**: `/v2/capacity/pools/{poolId}/products/{productId}`
- **Method**: `GET`
- **OpenAPI**: [v2/api/docs/#/Capacity/get_v2_capacity_pools__poolId__products__productId_](https://api.nexusmutual.io/v2/api/docs/#/Capacity/get_v2_capacity_pools__poolId__products__productId_)

- **Description**: Returns the current capacity for a specific product in a specific pool for a period of 30 days if no period query param is specified.
- **Parameters**:
- `poolId`: Required path parameter specifying the pool ID.
- `productId`: Required path parameter specifying the product ID.
- `period`: Optional query parameter specifying the period in days (default is 30, range is 28-365).
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cover-router",
"version": "2.3.8",
"version": "2.4.0",
"description": "Cover Router",
"main": "src/index.js",
"engines": {
Expand Down
126 changes: 108 additions & 18 deletions src/lib/capacityEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,39 @@ const { selectAsset, selectProduct, selectProductPools } = require('../store/sel

const { WeiPerEther, Zero } = ethers.constants;

const SECONDS_PER_DAY = BigNumber.from(24 * 3600);
const BASIS_POINTS = 10000;

/**
* Calculates the utilization rate of the capacity.
*
* @param {BigNumber} capacityAvailableNXM - The amount of capacity available in NXM.
* @param {BigNumber} capacityUsedNXM - The amount of capacity used in NXM.
* @returns {BigNumber} The utilization rate as a BigNumber, expressed in basis points (0-10,000).
* Returns undefined if capacity in NXM is missing.
*/
function getUtilizationRate(capacityAvailableNXM, capacityUsedNXM) {
if (!capacityAvailableNXM || !capacityUsedNXM) {
return undefined;
}

const totalCapacity = capacityAvailableNXM.add(capacityUsedNXM);
if (totalCapacity.isZero()) {
return BigNumber.from(0);
}

return capacityUsedNXM.mul(BASIS_POINTS).div(totalCapacity);
}

/**
* Calculates capacity and pricing data for a specific tranche of product pools.
*
* @param {Array<Object>} productPools - Array of product pool objects.
* @param {number} firstUsableTrancheIndex - Index of the first usable tranche.
* @param {boolean} useFixedPrice - Flag indicating whether to use fixed pricing.
* @param {BigNumber} now - Current timestamp in seconds.
* @returns {Object} An object containing capacity used, capacity available, minimum price, and total premium.
*/
function calculateProductDataForTranche(productPools, firstUsableTrancheIndex, useFixedPrice, now) {
return productPools.reduce(
(accumulated, pool) => {
Expand All @@ -18,9 +51,10 @@ function calculateProductDataForTranche(productPools, firstUsableTrancheIndex, u
const total = trancheCapacities.reduce((total, capacity) => total.add(capacity), Zero);

const unused = trancheCapacities.reduce((available, capacity, index) => {
const allocationDifference = capacity.sub(allocations[index]);
return index < firstUsableTrancheIndex
? available.add(bnMin(capacity.sub(allocations[index]), Zero)) // only carry over the negative
: available.add(capacity.sub(allocations[index]));
? available.add(bnMin(allocationDifference, Zero)) // only carry over the negative
: available.add(allocationDifference);
}, Zero);

const availableCapacity = bnMax(unused, Zero);
Expand Down Expand Up @@ -71,31 +105,81 @@ function calculateProductDataForTranche(productPools, firstUsableTrancheIndex, u
);
}

function capacityEngine(store, productIds, time, period = 30) {
const { assets, assetRates } = store.getState();
const capacities = [];
const ids = productIds.length === 0 ? Object.keys(store.getState().products) : [...productIds];
/**
* Retrieves all product IDs that are associated with a specific pool.
*
* @param {Object} store - The Redux store containing application state.
* @param {number|string} poolId - The ID of the pool to filter products by.
* @returns {Array<string>} An array of product IDs associated with the specified pool.
*/
function getProductsInPool(store, poolId) {
const { products } = store.getState();
return Object.keys(products).filter(productId => {
const productPools = selectProductPools(store, productId, poolId);
return productPools?.length > 0;
});
}

/**
* Calculates tranche indices for capacity calculations based on time and product data.
*
* @param {BigNumber} time - The current timestamp in seconds.
* @param {Object} product - The product object containing product details.
* @param {number} period - The coverage period in days.
* @returns {Object} Contains indices for the first usable tranche / first usable tranche for the maximum period.
*/
function calculateTrancheInfo(time, product, period) {
const firstActiveTrancheId = calculateTrancheId(time);
const gracePeriodExpiration = time.add(SECONDS_PER_DAY.mul(period)).add(product.gracePeriod);
const firstUsableTrancheId = calculateTrancheId(gracePeriodExpiration);
const firstUsableTrancheIndex = firstUsableTrancheId - firstActiveTrancheId;
const firstUsableTrancheForMaxPeriodId = calculateTrancheId(time.add(MAX_COVER_PERIOD).add(product.gracePeriod));
const firstUsableTrancheForMaxPeriodIndex = firstUsableTrancheForMaxPeriodId - firstActiveTrancheId;

return {
firstUsableTrancheIndex,
firstUsableTrancheForMaxPeriodIndex,
};
}

/**
* Calculates the capacity and pricing information for products and pools.
*
* @param {Object} store - The Redux store containing application state.
* @param {Object} [options={}] - Optional parameters for capacity calculation.
* @param {number|null} [options.poolId=null] - The ID of the pool to filter products by.
* @param {Array<number>} [options.productIds=[]] - Array of product IDs to process.
* @param {number} [options.period=30] - The coverage period in days.
* @returns {Array<Object>} An array of capacity information objects for each product.
*/
function capacityEngine(store, { poolId = null, productIds = [], period = 30 } = {}) {
const { assets, assetRates, products } = store.getState();
const now = BigNumber.from(Date.now()).div(1000);
const capacities = [];

for (const productId of ids) {
let productIdsToProcess;
if (productIds.length > 0) {
productIdsToProcess = [...productIds];
} else if (poolId !== null) {
// If only poolId is provided, get all products in that pool
productIdsToProcess = getProductsInPool(store, poolId);
} else {
// If neither productIds nor poolId is provided, process all products
productIdsToProcess = Object.keys(products);
}

for (const productId of productIdsToProcess) {
const product = selectProduct(store, productId);

if (!product) {
continue;
}

const secondsPerDay = BigNumber.from(24 * 3600);

const firstActiveTrancheId = calculateTrancheId(time);
const gracePeriodExpiration = time.add(secondsPerDay.mul(period)).add(product.gracePeriod);
const firstUsableTrancheId = calculateTrancheId(gracePeriodExpiration);
const firstUsableTrancheIndex = firstUsableTrancheId - firstActiveTrancheId;
const firstUsableTrancheForMaxPeriodId = calculateTrancheId(time.add(MAX_COVER_PERIOD).add(product.gracePeriod));
const firstUsableTrancheForMaxPeriodIndex = firstUsableTrancheForMaxPeriodId - firstActiveTrancheId;

const productPools = selectProductPools(store, productId);
const { firstUsableTrancheIndex, firstUsableTrancheForMaxPeriodIndex } = calculateTrancheInfo(now, product, period);
const productPools = selectProductPools(store, productId, poolId);

if (product.useFixedPrice) {
// Fixed Price
const productData = calculateProductDataForTranche(productPools, firstUsableTrancheIndex, true, now);

const { capacityAvailableNXM, capacityUsedNXM, minPrice, totalPremium } = productData;
Expand All @@ -114,10 +198,12 @@ function capacityEngine(store, productIds, time, period = 30) {
productId: Number(productId),
availableCapacity: capacityInAssets,
usedCapacity: capacityUsedNXM,
utilizationRate: getUtilizationRate(capacityAvailableNXM, capacityUsedNXM),
minAnnualPrice: minPrice,
maxAnnualPrice,
});
} else {
// Non-fixed Price
let productData = {};
let maxAnnualPrice = BigNumber.from(0);

Expand Down Expand Up @@ -149,6 +235,7 @@ function capacityEngine(store, productIds, time, period = 30) {
productId: Number(productId),
availableCapacity: capacityInAssets,
usedCapacity: capacityUsedNXM,
utilizationRate: getUtilizationRate(capacityAvailableNXM, capacityUsedNXM),
minAnnualPrice: minPrice,
maxAnnualPrice,
});
Expand All @@ -158,4 +245,7 @@ function capacityEngine(store, productIds, time, period = 30) {
return capacities;
}

module.exports = capacityEngine;
module.exports = {
capacityEngine,
getUtilizationRate,
};
Loading

0 comments on commit 5cc5933

Please sign in to comment.