I am currently working on a project that requires a series (almost 86) of calculations to be run based on user input. The problem is that each calculation has a series of requirements:

- I should be able to hold a
`version`

variable to distinguish the changes in each implementation of the calculation algorithm. In this way, every time we modify an algorithm, we know which version was used in the specific calculation.
- It must be able to load specific data from other modules within the application (that is, we have 8 entities) so that each one can choose the necessary information for its operation.
- You should be able to determine if it is
*"executable"*, and by which we would write a *function* (?) which verifies that the extracted data (from the previous requirement) meets some custom criteria for each calculation that guarantees that the algorithm will execute correctly.
- Each must have a different algorithm implementation.
- Generate and store a series of execution metrics (logs), such as data fetch time, algorithm runtime, and
`sampleSize`

This refers to the amount of data loaded to execute each specific calculation.

Currently what I have done is: create an abstract class `Calculation`

with this structure:

```
abstract class Calculation {
/**
* Logging Variables.
*/
private initialDataFetchTime: Date;
private finalDataFetchTime: Date;
private initialAlgorithmTime: Date;
private finalAlgorithmTime: Date;
// Final result holding variable.
private finalResult: T;
// The coverage status for this calculation.
private coverage: boolean;
// Data to use within the algorithm.
private data: F;
// The version of the Calculation.
public abstract version: string;
// The form data from the User to be used.
public static formData: FormData;
/**
* This is the abstract function to be implemented with
* the operation to be performed with the data. Always
* called after `loadData()`.
*/
public abstract async algorithm(): Promise;
/**
* This function should implement the data fetching
* for this particular calculation. This function is always
* called before `calculation()`.
*/
public abstract async fetchData(): Promise;
/**
* This is the abstract function that checks
* if enough information is met to perform the
* calculation. This function is called always
* after `loadData()`.
*/
public abstract async coverageValidation(): Promise;
/**
* This is the public member function that is called
* to perform the data-fetching operations of the
* calculation. This is the first function to call.
*/
public async loadData(): Promise {
// Get the initial time.
this.initialDataFetchTime = new Date();
/**
* Here we run the data-fetching implementation for
* this particular calculation.
*/
this.data = await this.fetchData();
// Store the final time.
this.finalDataFetchTime = new Date();
}
/**
* This is the public member function that is called
* to perform the calculation on this field. This is
* the last function to be called.
*/
public async calculation(): Promise {
// Get the initial time.
this.initialAlgorithmTime = new Date();
/**
* Here we run the algorithmic implementation for
* this particular calculation.
*/
this.finalResult = await this.algorithm();
// Store the final time.
this.finalAlgorithmTime = new Date();
// Return the result.
return this.finalResult;
}
/**
* This is the public member function that is called
* to perform the coverage-checking of this calculation.
* This function should be called after the `loadData()`
* and before `calculation()`.
*/
public async coverageCheck(): Promise {
// Execute the check function.
this.coverage = await this.coverageValidation();
// Return result.
return this.coverage;
}
/**
* Set FormData statically to be used across calculations.¡
*/
public static setFormData(formData: FormData): FormData {
// Store report.
this.formData = formData;
// Return report.
return this.formData;
}
/**
* Get the coverage of this calculation.
*/
public getCoverage(): boolean {
return this.coverage;
}
/**
* Get the data for this calculation.
*/
public getData(): F {
return this.data;
}
/**
* Get the result for this calculation.
*/
public getResult(): T {
return this.finalResult;
}
/**
* Function to get the class name.
*/
private getClassName(): string {
return this.constructor.name;
}
/**
* Function to get the version for this calculation.
*/
private getVersion(): string { return this.version; }
/**
* Get all the Valuation Logs for this Calculation.
*/
public async getValuationLogs(): Promise {
// The array of results.
const valuationLogs: CreateValuationLogDTO() = ();
// Log the time the algorithm took to execute.
valuationLogs.push({
report: Calculation.formData,
calculation: this.getClassName(),
metric: 'Algorithm Execution Time',
version: this.getVersion(),
value:
this.initialAlgorithmTime.getTime() - this.finalAlgorithmTime.getTime(),
});
// Log the time to fetch information.
valuationLogs.push({
report: Calculation.formData,
calculation: this.getClassName(),
metric: 'Data Fetch Load Time',
version: this.getVersion(),
value:
this.initialDataFetchTime.getTime() - this.finalDataFetchTime.getTime(),
});
// Sample size is calculated and not an issue for this matter.
// Return the metrics.
return valuationLogs;
}
}
```

And then, created subsequent classes for each calculation that extend the previous class, like:

```
export class GeneralArea extends Calculation {
/**
* Versioning information.
* These variable hold the information about the progress done to this
* calculation algorithm. The `version` field is a SemVer field which
* stores the version of the current algorithm implementation.
*
* IF YOU MAKE ANY MODIFICATION TO THIS CALCULATION, PLEASE UPDATE THE
* VERSION ACCORDINGLY.
*/
public version = '1.0.0';
// Dependencies.
constructor(private readonly dataSource: DataSource) {
super();
}
// 1) Fetch Information
public async fetchData(): Promise {
// Query the DB.
const dataPoints = this.dataSource.getInformation(/** **/);
// Return the data object.
return {
mortgages: dataPoints,
};
}
// 2) Validate Coverage.
public async coverageValidation(): Promise {
// Load data.
const data: GeneralAreaData = this.getData();
// Validate to be more than 5 results.
if (data.mortgages.length < 5) {
return false;
}
// Everything correct.
return true;
}
// 3) Algorithm
public async algorithm(): Promise {
// Load data.
const data: GeneralAreaData = this.getData();
// Perform operation.
const result: number = await Math.min.apply(
Math,
data.mortgages.map(mortgage => mortgage.price),
);
// Return the result.
return result;
}
}
/**
* Interface that holds the structure of the data
* used for this implementation.
*/
export interface GeneralAreaData {
// Mortages based on some criteria.
mortages: SomeDataEntity;
}
```

The idea is to allow ourselves to carry out three basic operations:

- Load the data for each calculation.
- Validate coverage for each calculation.
- If the previous step returns a general "true", run calculations.

However, this pattern has posed some problems since `FormData`

(the information that the user loads) is stored *inactively*, which means if some calculation is already running and another user loads, I can't configure FormData because it will make the other user's calculations go crazy. However passing the `FormData`

For each function constructor it seems like a lot of work (if you think this should be the way, I'm not afraid to write code;))

Maybe it's this quarantine, however am I not seeing something here? Currently, the final run looks like this:

```
public performCalculation(formData: FormData): Promise {
// Set general form data.
Calculation.setFormData(formData); // <--- Error in subsequent requests :(
// Instance Calculations.
const generalAreaCalculation: GeneralAreaCalculation = new GeneralAreaCalculation(/** data service **/);
// 85 more instantiations...
// Load data for Calculations.
try {
await Promise.all((
generalAreaCalculation.loadData(),
// 85 more invocations...
));
} catch(dataLoadError) { /** error handling **/ }
// Check for coverage.
const coverages: boolean() = await Promise.all((
generalAreaCalculation.coverageCheck(),
// 85 more coverage checks...
));
// Reduce coverage.
const covered: boolean = coverages.reduce((previousValue, coverage) => coverage && previousValue, true);
// Check coverage.
if (!covered) { /** Throw exception **/ }
// Perform calculations!
const result: FormDataWithCalculations = new FormDataWithCalculations(formData);
try {
result.generalAreaValue = generalAreaCalculation.calculation();
// 85 more of this.
} catch (algorithmsError) { /** error handling ***/ }
/*
(( Here should go the log collecting and storing, for each of the 85 calculations ))
*/
// Return processed information.
return result;
}
```

I'm not afraid to write too much code if that means it is reusable, maintainable, and most importantly capable of being testable (oh yes, test each calculation to make sure it does what it is supposed to do in normal cases and extremes is why classes were my focus, so each would have attached a test), however I am completely overwhelmed by writing this tremendous amount of code instead of just writing 85 functions (which is what was already used ) and call each one of them.

Is there a pattern? Guide? Advice? Reference? Study material? I can't seem to shrink this code anymore, but I wanted to ask in case someone knows a better pattern for this kind of problem and, in case it's useful, TypeScript code (NodeJS with NestJS API) to understand how everything works . be connected

Thanks in advance and apologies for my horrible English!