Envizage SDK
About the SDK
The Envizage SDK is a set of Javascript methods that can be used to facilitate interaction with the Envizage API. The SDK can be used in both client-side and server-side (Node.js) Javascript and Typescript code. Type declarations are included.
Http client
The SDK requires an HTTP client implementation. The user can implement their own client or use a custom client implementation included in the SDK.
The @envizage/model NPM package is a peer dependency for the SDK. It includes interfaces and mappings for entities used in the API. Make sure that a version equal to or newer than the minimum specified in the SDK project is installed in your project. You can install the @envizage/model package using the following command:
npm install ---save @envizage/model
or using Yarn:
yarn add @envizage/model
Install Envizage's SDK package using NPM:
npm install ---save @envizage/services
or using Yarn:
yarn add @envizage/services
Usage
HTTP client
An HTTP client implementation is required as a dependency for all other services included in the SDK. The HTTP client that will be injected has to implement the IHttpClient interface:
export interface IHttpClient {
get: <T>(url: string, params?: Map<string, string>, headerMap?: string[][]) => Promise<T>,
post: <T, U>(url: string, body: T, params?: Map<string, string>, headerMap?: string[][]) => Promise<U>,
put: <T, U>(url: string, body: T, params?: Map<string, string>, headerMap?: string[][]) => Promise<U>,
remove: <T>(url: string, params?: Map<string, string>, headerMap?: string[][]) => Promise<T>
}
A user of the SDK can either implement that interface or import a ready fetch-based implementation that comes with the SDK:
import { HttpClient } from "@envizage/services";
A wrapper is also available to intercept requests before/after the HTTP client interacts with the API that can be used to add headers to HTTP requests. The following example intercepts HTTP requests and adds the basic headers required in every request to the API:
import { withInterceptor, HttpClient } from '@envizage/services';
const simpleClient = withInterceptor((url, params, headerMap, body) => {
const clonedHeaders = headerMap ? (JSON.parse(JSON.stringify(headerMap)) as [string, string][]) : [];
clonedHeaders.push(['Accept', 'application/json']);
clonedHeaders.push(['Content-type', 'application/json']);
return {
url, params, headerMap: clonedHeaders, body
}
})(HttpClient);
User authentication
Envizage uses the OpenIDConnect protocol to authenticate users. Users can be authenticated using the Authorization Code Flow and the Resource Owner Password Credentials (ROPC) Flow. Both flows together with the Client Credentials Flow (which can be used to authenticate a client application) are documented in the API Reference. We assume that a valid user access token has been obtained using any of the supported OpenIDConnect flows. Examples of how to obtain a token can be found for a front-end application or a backend application.
Service initialization
Each service is a class that needs to be instantiated to access the API. A new object can simply be created and used in any Javascript app. Note that all services require authorization headers added to the HTTP requests so the HTTP client has to accomodate that requirement.
import { HttpClient, LivingExpenseService } from '@envizage/services';
const apiUrl = 'https://api.envizage.me';
//We assume that a user token has been obtained and stored in the session storage of the browser.
const token = sessionStorage.getItem('access_token');
const unauthorizedClient = withInterceptor((url, params, headerMap, body) => {
const clonedHeaders = headerMap ? (JSON.parse(JSON.stringify(headerMap)) as [string, string][]) : [];
clonedHeaders.push(['Accept', 'application/json']);
clonedHeaders.push(['Content-type', 'application/json']);
return {
url, params, headerMap: clonedHeaders, body
}
})(HttpClient);
const authorizedClient = withInterceptor((url, params, headerMap, body) => {
const clonedHeaders = headerMap ? (JSON.parse(JSON.stringify(headerMap)) as [string, string][]) : [];
clonedHeaders.push(['Authorization', 'Bearer ' + token]);
return {
url, params, headerMap: clonedHeaders, body
}
})(unauthorizedClient);
const livingExpenseService = new LivingExpenseService(authorizedClient, apiUrl);
const livingExpenseResponse = await livingExpenseService.query({
page: 0,
size: 2000,
sort: PageableSort.ASC
});
Setup a simple scenario
Following the previous steps, the user is able to create and setup a new scenario by utilizing the services that Envizage SDK provides.
- Angular
- React
First things first, the user should provide the HTTP client and the authenticated HTTP client in app's module providers array. For authentication purposes, use the token that has been fetched and stored in local storage from previous steps. Create a new component named scenario-builder and use Envizage SDK's services in order to create and setup a new scenario like the following example:
- app.module.ts
- scenario-builder.component.ts
providers: [...{
provide: HTTP_CLIENT,
useFactory: () => withInterceptor((url, params, headerMap, body) => {
if (!headerMap) {
headerMap = [];
}
if (headerMap.filter(h => h[0].toLowerCase() === 'content-type').length === 0) {
headerMap.push(['content-type', 'application/json'])
}
return { url, params, headerMap, body };
})(HttpClient)},
{
provide: AUTHENTICATED_CLIENT,
deps: [OAuthService],
useFactory: (oAuthService: OAuthService) => withInterceptor((url, params, headerMap, body ) => {
if (!headerMap) {
headerMap = [];
}
headerMap.push(['accept', 'application/json, text/plain, */*']);
if (url.indexOf('/scenarios') >= 0) {
headerMap.push(['X-Requested-Version', '5.0'])
}
const authToken = oAuthService.getAccessToken();
headerMap.push(['Authorization', 'Bearer ' + authToken]);
return {
url,
params,
headerMap,
body
};
})(HttpClient)}
...},
{
provide: ScenarioService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new ScenarioService(client, baseUrl)
},
{
provide: PortfolioAssetService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new PortfolioAssetService(client, baseUrl)
},
{
provide: EarnedIncomeForPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new EarnedIncomeForPersonService(client, baseUrl)
},
{
provide: LivingExpenseForPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new LivingExpenseForPersonService(client, baseUrl)
},
{
provide: PensionAnnuityIncomeForPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new PensionAnnuityIncomeForPersonService(client, baseUrl)
},
{
provide: PortfolioAssetForPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new PortfolioAssetForPersonService(client, baseUrl)
},
{
provide: PortfolioContributionExpenseService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new PortfolioContributionExpenseService(client, baseUrl)
},
{
provide: PortfolioFinancialAssetForPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new PortfolioFinancialAssetForPersonService(client, baseUrl)
},
{
provide: PrimaryPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new PrimaryPersonService(client, baseUrl)
},
{
provide: ResultsService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new ResultsService(client, baseUrl)
},
{
provide: ScenarioService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new ScenarioService(client, baseUrl)
},
{
provide: TypedGoalForPersonService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new TypedGoalForPersonService(client, baseUrl)
},
{
provide: TypedGoalService,
deps: [AUTHENTICATED_CLIENT, BASE_URL],
useFactory: (client, baseUrl) => new TypedGoalService(client, baseUrl)
},
...}]
import {
EarnedIncomeForPersonService,
LivingExpenseForPersonService,
PensionAnnuityIncomeForPersonService,
PortfolioAssetForPersonService,
PortfolioContributionExpenseService,
PortfolioFinancialAssetForPersonService,
PrimaryPersonService,
ResultsService,
ScenarioService,
TypedGoalForPersonService,
TypedGoalService,
UserConfigurationService,
getPrimaryPerson
} from '@envizage/services';
import {
BaseGrowthRate,
Currency,
DateRangeType,
EducationLevel,
Frequency,
Gender,
GoalType,
HealthStatus,
IBaseIncome,
IEarnedIncome,
IFinancialAsset,
IFinancialPortfolio,
ILivingExpense,
IPensionAnnuityIncome,
IPortfolioContributionExpense,
IPrimary,
IScenario,
ITypedGoal,
JobType,
MaritalStatus,
SimulationResultMode
} from '@envizage/model';
@Component({
selector: 'app-scenario-builder',
templateUrl: './scenario-builder.component.html',
styleUrls: ['./scenario-builder.component.scss']
})
export class ScenarioBuilder implements OnInit{
let scenarioId = '';
let personId = '';
const primary: IPrimary = {
name: 'George',
jobType: JobType.Unemployed,
educationLevel: EducationLevel.NoEducation,
expectedRetirementAge: 67,
yearOfBirth: 1980,
gender: Gender.Unspecified,
maritalStatus: MaritalStatus.Unspecified,
healthStatus: HealthStatus.Average,
}
const earnedIncome: IEarnedIncome = {
name: 'Earned income',
amount: 20000,
currency: Currency.Usd,
startsOn: DateRangeType.UserDefined,
startDate: "2023-01-01T00:00:00Z",
endsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
maximumAbsoluteSpreadAllowed: 0,
spreadOverGrowthRate: 0
}
const livingExpense: ILivingExpense = {
name: 'Living expense',
amount: 15000,
frequency: Frequency.Unspecified,
currency: Currency.Usd,
growthRate: BaseGrowthRate.Cpi,
startsOn: DateRangeType.UserDefined,
startDate: "2023-01-01T00:00:00Z",
endsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
nonDiscretionaryPercentage: 0,
survivorAdjustmentPercentage: 0.02
}
const pot: IFinancialPortfolio = {
name: 'Pension pot',
value: 30000,
profile: 'low',
currency: Currency.Usd,
wrapper: 'sipp',
fees: 0.02
}
const portfolioContributionExpense: IPortfolioContributionExpense = {
portfolioId: '',
name: 'Portfolio contribution expense',
growthRate: BaseGrowthRate.Calculated,
frequency: Frequency.Annually,
amount: 2000,
currency: Currency.Usd,
startDate: "2023-01-01T00:00:00Z",
startsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
endsOn: DateRangeType.UserDefined,
}
const pensionAnnuityIncome: IPensionAnnuityIncome = {
portfolioId: '',
name: 'Annuity income',
amount: 0,
currency: Currency.Usd,
startDate: "2023-01-01T00:00:00Z",
startsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
frequency: Frequency.Annually,
endsOn: DateRangeType.UserDefined,
growthRate: BaseGrowthRate.Cpi,
maximumAbsoluteSpreadAllowed: 0.02,
spreadOverGrowthRate: 0.02,
toSurvivorPercentage: 0.01,
percentage: 0.1
}
const buyAhouseGoal = {
name: 'Buy a house',
type: GoalType.BuyAHouse,
priority: 5,
frequency: Frequency.OneOff,
minimumAmount: 80000,
desiredAmount: 80000,
currency: Currency.Usd,
startDate: "2025-01-01T00:00:00Z",
endDate: "2025-01-01T00:00:00Z",
enabled: true,
growthRate: BaseGrowthRate.Calculated,
maximumAbsoluteSpreadAllowed: 0.04,
spreadOverGrowthRate: 0.02,
fundingSources: [{
class: 'SaveFundingSource',
name: 'Save in years',
saveInYears: 6000
}],
properties: {
primary: true
}
}
constructor(
private scenarioService: ScenarioService,
private typedGoalService: TypedGoalService,
private earnedIncomeForPersonService: EarnedIncomeForPersonService,
private primaryPersonService: PrimaryPersonService,
private livingExpenseForPersonService: LivingExpenseForPersonService,
private portfolioContributionExpenseService: PortfolioContributionExpenseService,
private pensionAnnuityIncomeForPersonService: PensionAnnuityIncomeForPersonService,
private typedGoalForPersonService: TypedGoalForPersonService,
private portfolioAssetForPersonService: PortfolioAssetForPersonService,
private resultService: ResultsService,
) {}
ngOnInit() {
this.createAndSetupScenario()
}
public createAndSetupScenario(): Observable<IScenario> {
defer(() => this.scenarioService.create({name: 'New scenario'})).pipe(
tap((newScenario: IScenario) => scenarioId = newScenario?.id),
tap((newScenario: IScenario) => personId = getPrimaryPerson(newScenario)?.id),
switchMap(() => forkJoin([
defer(() => this.primaryPersonService.update(scenarioId, primary)),
defer(() => this.earnedIncomeForPersonService.createForPerson(scenarioId, personId, earnedIncome)),
defer(() => this.livingExpenseForPersonService.createForPerson(scenarioId, personId, livingExpense)),
defer(() => this.portfolioAssetForPersonService.createForPerson(scenarioId, personId, pot))
])),
switchMap(([primary, earnedIncome, livingExpense, portfolio]) => defer(() => this.portfolioContributionExpenseService.create(scenarioId, {...portfolioContributionExpense, portfolioId: portfolio.id}))),
switchMap((portfolioExpense: IPortfolioContributionExpense) => defer(() => this.pensionAnnuityIncomeForPersonService.createForPerson(scenarioId, personId, {...pensionAnnuityIncome, portfolioId: portfolioExpense.portfolioId}))),
switchMap((portfolioExpense: IPortfolioContributionExpense) => defer(() => this.pensionAnnuityIncomeForPersonService.createForPerson(scenarioId, personId, {...pensionAnnuityIncome, portfolioId: portfolioExpense.portfolioId}))),
switchMap((pensionAnnuityIncome: IPensionAnnuityIncome) => defer(() => this.typedGoalForPersonService.createForPerson(scenarioId, personId, buyAhouseGoal))),
switchMap((buyAHouseGoal: ITypedGoal) => this.scenarioService.get(scenarioId))
).subscribe((createdScenario: IScenario) => console.log(createdScenario))
}
}
First we define the models that are needed to passed as parameters in the services. After that we use the SDK's services in order to create and setup a simple scenario. Finally, we utilize the replace(), execute() and get() methods to run a simulation and get the results. In this example we use the RxJS library. RxJS operators are utilized in order create an observable (defer), call the services sequentially (switchMap) or in parallel(forkJoin), implement businness logic between tha calls (tap) etc. As you may noticed, createAndSetupScenario() method is called upon component initialization, however the user is able to call and use Envizage SDK whenever and wherever is needed in the app, for instance when an event or action is triggered.
Create a file named services.utils.js. This is where you should implement an HTTP client, initialize and export the services you would like to use. After completing this step, you will be able to use Envizage's SDK services wherever you like in the app. Replace 'backend_url' with your backend's url and 'access_token' with the token you have fetched in authentication step.
- services.utils.js
- scenarioBuilder.js
import {
HttpClient,
ScenarioService,
withInterceptor,
EarnedIncomeForPersonService,
PrimaryPersonService,
LivingExpenseForPersonService,
PortfolioContributionExpenseService,
PensionAnnuityIncomeForPersonService,
TypedGoalForPersonService,
PortfolioAssetForPersonService,
ResultsService
} from '@envizage/services';
const baseUrl = 'backend_url';
const JsonClient = withInterceptor((baseUrl, params, headerMap, body) => {
const clonedHeaders = headerMap ? (JSON.parse(JSON.stringify(headerMap))) : [];
clonedHeaders.push(['Accept', 'application/json']);
clonedHeaders.push(['Content-type', 'application/json']);
return {
baseUrl, params, headerMap: clonedHeaders, body
}
})(HttpClient);
const AuthenticatedJsonClient = withInterceptor((baseUrl, params, headerMap, body) => {
const clonedHeaders = headerMap ? (JSON.parse(JSON.stringify(headerMap))) : [];
if (localStorage.getItem('access_token')) {
clonedHeaders.push(['Authorization', 'Bearer 'access_token')}']);
}
return {
baseUrl, params, headerMap: clonedHeaders, body
}
})(JsonClient);
export const ServicesCtx = {
scenarioService: new ScenarioService(AuthenticatedJsonClient, baseUrl),
earnedIncomeForPersonService: new EarnedIncomeForPersonService(AuthenticatedJsonClient, baseUrl),
primaryPersonService: new PrimaryPersonService(AuthenticatedJsonClient, baseUrl),
livingExpenseForPersonService: new LivingExpenseForPersonService(AuthenticatedJsonClient, baseUrl),
portfolioContributionExpenseService: new PortfolioContributionExpenseService(AuthenticatedJsonClient, baseUrl),
pensionAnnuityIncomeForPersonService: new PensionAnnuityIncomeForPersonService(AuthenticatedJsonClient, baseUrl),
typedGoalForPersonService: new TypedGoalForPersonService(AuthenticatedJsonClient, baseUrl),
portfolioAssetForPersonService: new PortfolioAssetForPersonService(AuthenticatedJsonClient, baseUrl),
resultService: new ResultsService(AuthenticatedJsonClient, baseUrl)
}
Import and use the services wherever you need in the app, for instance:
import { ServicesCtx } from "./utils/services.utils";
import {getPrimaryPerson} from '@envizage/services';
import {
BaseGrowthRate,
Currency,
DateRangeType,
EducationLevel,
Frequency,
Gender,
GoalType,
HealthStatus,
JobType,
MaritalStatus,
} from '@envizage/model';
export default function ScenarioBuilder() {
const updatedPrimary = {
name: 'George',
jobType: JobType.Unemployed,
educationLevel: EducationLevel.NoEducation,
expectedRetirementAge: 67,
yearOfBirth: 1980,
gender: Gender.Unspecified,
maritalStatus: MaritalStatus.Unspecified,
healthStatus: HealthStatus.Average,
}
const earnedIncome = {
name: 'Earned income',
amount: 20000,
currency: Currency.Usd,
startsOn: DateRangeType.UserDefined,
startDate: "2023-01-01T00:00:00Z",
endsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
maximumAbsoluteSpreadAllowed: 0,
spreadOverGrowthRate: 0
}
const livingExpense = {
name: 'Living expense',
amount: 15000,
frequency: Frequency.Unspecified,
currency: Currency.Usd,
growthRate: BaseGrowthRate.Cpi,
startsOn: DateRangeType.UserDefined,
startDate: "2023-01-01T00:00:00Z",
endsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
nonDiscretionaryPercentage: 0,
survivorAdjustmentPercentage: 0.02
}
const pot = {
name: 'Pension pot',
value: 30000,
profile: 'low',
currency: Currency.Usd,
wrapper: 'sipp',
fees: 0.02
}
const portfolioContributionExpense = {
portfolioId: '',
name: 'Portfolio contribution expense',
growthRate: BaseGrowthRate.Calculated,
frequency: Frequency.Annually,
amount: 2000,
currency: Currency.Usd,
startDate: "2023-01-01T00:00:00Z",
startsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
endsOn: DateRangeType.UserDefined,
}
const pensionAnnuityIncome = {
portfolioId: '',
name: 'Annuity income',
amount: 0,
currency: Currency.Usd,
startDate: "2023-01-01T00:00:00Z",
startsOn: DateRangeType.UserDefined,
endDate: "2025-01-01T00:00:00Z",
frequency: Frequency.Annually,
endsOn: DateRangeType.UserDefined,
growthRate: BaseGrowthRate.Cpi,
maximumAbsoluteSpreadAllowed: 0.02,
spreadOverGrowthRate: 0.02,
toSurvivorPercentage: 0.01,
percentage: 0.1
}
const buyAhouseGoal = {
name: 'Buy a house',
type: GoalType.BuyAHouse,
priority: 5,
frequency: Frequency.OneOff,
minimumAmount: 80000,
desiredAmount: 80000,
currency: Currency.Usd,
startDate: "2025-01-01T00:00:00Z",
endDate: "2025-01-01T00:00:00Z",
enabled: true,
growthRate: BaseGrowthRate.Calculated,
maximumAbsoluteSpreadAllowed: 0.04,
spreadOverGrowthRate: 0.02,
fundingSources: [{
class: 'SaveFundingSource',
name: 'Save in years',
saveInYears: 6000
}],
properties: {
primary: true
}
}
const setupScenario = async() => {
const scenario = await ServicesCtx.scenarioService.create({name: 'New scenario'});
const primary = getPrimaryPerson(scenario);
const portfolio = await ServicesCtx.primaryPersonService.update(scenario.id, updatedPrimary)
.then(() => ServicesCtx.earnedIncomeForPersonService.createForPerson(scenario.id, primary.id, earnedIncome))
.then(() => ServicesCtx.livingExpenseForPersonService.createForPerson(scenario.id, primary.id, livingExpense))
.then(() => ServicesCtx.portfolioAssetForPersonService.createForPerson(scenario.id, primary.id, pot))
ServicesCtx.portfolioContributionExpenseService.create(scenario.id, {...portfolioContributionExpense, portfolioId: portfolio.id})
.then(() => ServicesCtx.pensionAnnuityIncomeForPersonService.createForPerson(scenario.id, primary.id, {...pensionAnnuityIncome, portfolioId: portfolio.id}))
.then(() => ServicesCtx.typedGoalForPersonService.createForPerson(scenario.id, primary.id, buyAhouseGoal))
.then(() => ServicesCtx.scenarioService.get(scenario.id))
.then(createdScenario => console.log(createdScenario))
}
return (
<div>
<button onClick={() => setupScenario()}>Setup scenario</button>
</div>
)
}