Skip to content

Commit 49913a5

Browse files
committed
feat: updated rules checks
BREAKING CHANGE: for propertyType add
1 parent 107d574 commit 49913a5

File tree

17 files changed

+256
-31
lines changed

17 files changed

+256
-31
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ Once a feature's PR is merged, the pipeline will run checks and publish.
8686
- What would the transition look like in code?
8787
- TODO: Cash flow ramp (0 - 3 months depending on rehab, conventional, or existing tenant)
8888
- Transition would be around Hard Money vs Conventional
89-
- TODO: delay 1st mortgage payment
90-
- TODO: figure out 1031 exchange
9189

9290
## Missing features
9391

94-
1. apartments (passive investor)
92+
1. delay 1st mortgage payment
93+
2. implement hold rules
94+
3. apartments (passive investor)
9595

9696
## Future
9797

@@ -159,8 +159,8 @@ const options: ISimulateOptions = {
159159

160160
const actual = simulate(options);
161161

162-
//Finally, to review your results, you can use the user's getSummariesAnnual.
163-
const lastYear = actual.user.getSummariesAnnual(
162+
//Finally, to review your results, you can use the ledgerCollection's getSummariesAnnual.
163+
const lastYear = actual.user.ledgerCollection.getSummariesAnnual(
164164
actual.endDate.getUTCFullYear()
165165
);
166166
```

src/rules/get-rules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ export function getRules<E extends PurchaseRuleTypes | HoldRuleTypes>(goals: IRu
77
if (!goals || goals.length === 0) {
88
return [];
99
}
10-
return goals.map((g: IRule<E>) => new RuleEvaluation<E>(g.value, g.type));
10+
return goals.map((g: IRule<E>) => new RuleEvaluation<E>(g.value, g.type, g.propertyType));
1111
}

src/rules/i-rule.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import { PropertyType } from '../account/property-type';
2+
13
export interface IRule<E> {
24
type: E;
35
value: number;
6+
propertyType: PropertyType;
47
}

src/rules/rule-evaluation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@ import { PurchaseRuleTypes } from './purchase-rule-types';
22
import { HoldRuleTypes } from './hold-rule-types';
33
import { AtLeastOrMore, NoMoreThan } from './eval-types';
44
import { IRule } from './i-rule';
5+
import { PropertyType } from '../account/property-type';
56

67
export interface IRuleEvaluation<E extends PurchaseRuleTypes | HoldRuleTypes> extends IRule<E> {
78
evaluate(dataValue: number): boolean;
89
}
910

1011
export class RuleEvaluation<E extends PurchaseRuleTypes | HoldRuleTypes> implements IRuleEvaluation<E> {
11-
constructor(value: number, type: E) {
12+
constructor(value: number, type: E, propertyType: PropertyType) {
1213
this.value = value;
1314
this.type = type;
15+
this.propertyType = propertyType;
1416
}
1517

1618
type: E;
1719
value: number;
20+
propertyType: PropertyType;
1821

1922
evaluate(dataValue: number): boolean {
2023
if (!this.type || this.type.toString().indexOf('none') !== -1) {

src/time/movement.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { LedgerItemType } from '../ledger/ledger-item-type';
88
import { LedgerItem } from '../ledger/ledger-item';
99
import propertySort from '../properties/property-sort';
1010
import { cloneDateUtc } from '../utils/data-clone-date';
11+
import { ensureArray } from '../utils/ensure';
12+
import { ILoanSetting } from '../account/i-loan-settings';
13+
import { PropertyType } from '../account/property-type';
14+
import { IRuleEvaluation } from '../rules/rule-evaluation';
15+
import { PurchaseRuleTypes } from '../rules/purchase-rule-types';
1116

1217
export interface ILoopOptions {
1318
/**
@@ -49,6 +54,15 @@ export function loop(options: ILoopOptions, user: IUser): ITimeline {
4954
options.startDate = cloneDateUtc(setupDate);
5055
}
5156

57+
ensureArray<ILoanSetting>(user.loanSettings, {
58+
predicate: (item) => item.propertyType === PropertyType.SingleFamily,
59+
message: 'no single family loan settings for user: loanSettings',
60+
});
61+
ensureArray<IRuleEvaluation<PurchaseRuleTypes>>(user.purchaseRules, {
62+
predicate: (item) => item.propertyType === PropertyType.SingleFamily,
63+
message: 'no single family purchase rules for user: purchaseRules',
64+
});
65+
5266
let today = cloneDateUtc(options.startDate);
5367

5468
const result: ITimeline = {

src/time/simulate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export function simulate(options: ISimulateOptions): ITimeline {
8888
user.monthlySavedAmount = options.monthlySavedAmount;
8989
user.monthlyIncomeAmountGoal = options.monthlyIncomeAmountGoal;
9090
user.loanSettings = options.loanSettings;
91-
user.purchaseRules = (options.purchaseRules || []).map((r) => new RuleEvaluation(r.value, r.type));
91+
user.purchaseRules = (options.purchaseRules || []).map((r) => new RuleEvaluation(r.value, r.type, r.propertyType));
9292

9393
return loop(
9494
{

src/utils/ensure.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export type EnsureArrayPredicate<T> = (item: T, index?: number) => boolean;
2+
3+
export function ensureArray<T>(array?: T[], options?: { predicate?: EnsureArrayPredicate<T>; message?: string }): void {
4+
const message = 'array is invalid';
5+
if (!options) {
6+
options = { message };
7+
}
8+
9+
if (!options.message) {
10+
options.message = message;
11+
}
12+
13+
if (!array || array.length === 0) {
14+
throw new Error(options.message);
15+
}
16+
17+
if (options.predicate && array.filter(options.predicate).length === 0) {
18+
throw new Error(options.message);
19+
}
20+
}

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './data-are-same-date';
22
export * from './data-clone-date';
33
export * from './data-number';
44
export * from './data-property-entity';
5+
export * from './ensure';

tests/accounts/user.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,45 @@ describe('User unit tests', () => {
140140
});
141141
});
142142

143+
describe('and getCashFlowMonth', () => {
144+
test('should match', () => {
145+
const date = new Date();
146+
147+
const expected = 500;
148+
149+
ledgerCollection.getCashFlowMonth.mockReturnValueOnce(expected);
150+
151+
expect(instance.getCashFlowMonth(date)).toEqual(expected);
152+
expect(ledgerCollection.getCashFlowMonth).toBeCalledWith(date);
153+
});
154+
});
155+
156+
describe('and metMonthlyGoal', () => {
157+
test('should be true', () => {
158+
const date = new Date();
159+
160+
const expected = 500;
161+
162+
ledgerCollection.getCashFlowMonth.mockReturnValueOnce(expected);
163+
instance.monthlyIncomeAmountGoal = 500;
164+
165+
expect(instance.metMonthlyGoal(date)).toBeTruthy();
166+
expect(ledgerCollection.getCashFlowMonth).toBeCalledWith(date);
167+
});
168+
169+
test('should be false', () => {
170+
const date = new Date();
171+
172+
const expected = 499;
173+
174+
ledgerCollection.getCashFlowMonth.mockReturnValueOnce(expected);
175+
instance.monthlyIncomeAmountGoal = 500;
176+
177+
expect(instance.metMonthlyGoal(date)).toBeFalsy();
178+
expect(ledgerCollection.getCashFlowMonth).toBeCalledWith(date);
179+
});
180+
});
181+
143182
describe('and getSummaryAnnual', () => {
144183
test('should match', () => {
145184
const year = chance.integer();

tests/calculations/can-invest-by-user.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UserInvestResult } from '../../src/investments/user-invest-result';
55
import { RentalSingleFamily } from '../../src/properties/rental-single-family';
66
import { PurchaseRuleTypes } from '../../src/rules/purchase-rule-types';
77
import { IUserInvestorCheck } from '../../src/account/i-user-investor-check';
8+
import { PropertyType } from '../../src/account/property-type';
89

910
describe('and canInvestByUser', () => {
1011
let instance: RentalSingleFamily;
@@ -98,11 +99,13 @@ describe('and canInvestByUser', () => {
9899
purchaseRules: [
99100
{
100101
type: PurchaseRuleTypes.minAskingPrice,
102+
propertyType: PropertyType.SingleFamily,
101103
value: 50000,
102104
evaluate: jest.fn().mockReturnValue(false),
103105
},
104106
{
105107
type: PurchaseRuleTypes.maxEstimatedOutOfPocket,
108+
propertyType: PropertyType.SingleFamily,
106109
value: 50000,
107110
evaluate: jest.fn().mockReturnValue(false),
108111
},
@@ -130,11 +133,13 @@ describe('and canInvestByUser', () => {
130133
purchaseRules: [
131134
{
132135
type: PurchaseRuleTypes.minAfterRepairPrice,
136+
propertyType: PropertyType.SingleFamily,
133137
value: 50000,
134138
evaluate: jest.fn().mockReturnValue(false),
135139
},
136140
{
137141
type: PurchaseRuleTypes.maxEstimatedOutOfPocket,
142+
propertyType: PropertyType.SingleFamily,
138143
value: 50000,
139144
evaluate: jest.fn().mockReturnValue(false),
140145
},

0 commit comments

Comments
 (0)