Skip to content

Commit 85d60ba

Browse files
committed
feat: user
1 parent 709c1f1 commit 85d60ba

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1349
-417
lines changed

.circleci/config.yml

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ version: 2.1
33
executors:
44
my-custom-executor:
55
docker:
6-
- image: cimg/node:14.17.6
6+
- image: cimg/node:14.18.0
77

88
# Define the jobs we want to run for this project
99
jobs:
1010
build:
1111
executor: my-custom-executor
12+
working_directory: ~/tmp
1213
steps:
1314
- checkout
1415
- restore_cache:
1516
keys:
1617
# when lock file changes, use increasingly general patterns to restore cache
17-
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}
18+
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}-<< pipeline.id >>
1819
- restore_cache:
1920
keys:
2021
# when lock file changes, use increasingly general patterns to restore cache
@@ -25,20 +26,24 @@ jobs:
2526
command: npm run build
2627
- save_cache:
2728
paths:
28-
- node_modules
29-
key: node-{{ .Branch }}-{{ checksum "package-lock.json" }}
29+
- ~/tmp/node_modules
30+
key: node-{{ .Branch }}-{{ checksum "package-lock.json" }}-<< pipeline.id >>
3031
- save_cache:
3132
paths:
32-
- dist
33+
- ~/tmp/dist
3334
key: node-{{ .Branch }}-build-<< pipeline.id >>
35+
- store_artifacts:
36+
path: ~/tmp/dist
37+
destination: artifact-dist
3438
lint:
3539
executor: my-custom-executor
40+
working_directory: ~/tmp
3641
steps:
3742
- checkout
3843
- restore_cache:
3944
keys:
4045
# when lock file changes, use increasingly general patterns to restore cache
41-
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}
46+
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}-<< pipeline.id >>
4247
- restore_cache:
4348
keys:
4449
# when lock file changes, use increasingly general patterns to restore cache
@@ -48,27 +53,34 @@ jobs:
4853
command: npm run eslint
4954
unit-test:
5055
executor: my-custom-executor
56+
working_directory: ~/tmp
5157
steps:
5258
- checkout
5359
- restore_cache:
5460
keys:
5561
# when lock file changes, use increasingly general patterns to restore cache
56-
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}
62+
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}-<< pipeline.id >>
5763
- restore_cache:
5864
keys:
5965
# when lock file changes, use increasingly general patterns to restore cache
6066
- node-{{ .Branch }}-build-<< pipeline.id >>
6167
- run:
62-
name: unit testing
63-
command: npm run test
68+
name: Run tests with JUnit as reporter
69+
command: npm run test:ci
70+
environment:
71+
- JEST_JUNIT_OUTPUT_FILE: "reports/junit/junit.xml"
72+
- store_test_results:
73+
path: ./reports/junit/
74+
- store_artifacts:
75+
path: ./reports/junit/
6476
publish:
6577
executor: my-custom-executor
6678
steps:
6779
- checkout
6880
- restore_cache:
6981
keys:
7082
# when lock file changes, use increasingly general patterns to restore cache
71-
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}
83+
- node-{{ .Branch }}-{{ checksum "package-lock.json" }}-<< pipeline.id >>
7284
- restore_cache:
7385
keys:
7486
# when lock file changes, use increasingly general patterns to restore cache

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ typings/
5757
# Optional eslint cache
5858
.eslintcache
5959

60+
# Optional jest cache
61+
.jestcache
62+
6063
# Microbundle cache
6164
.rpt2_cache/
6265
.rts2_cache_cjs/

README.md

Lines changed: 161 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,34 @@ Will be based on version 1
1313
## Inspiration
1414

1515
This was originally used as a discussion point between me and my SO. The idea is that we like our jobs, but we also want
16-
a way to ensure that if anything happens to us, would we be able to sustain our income.
16+
a way to ensure that if anything happens to us, we would be able to sustain our income.
1717

1818
For me, I was inspired by a game originally from early 2000's where you could quickly click on houses and flip them, but
1919
after this idea and speaking with mentors, the idea has become find an optimal way to _ramp-up_.
2020

21+
> ☝️Mentors are great! They do a great job at helping you discover how to get to the next point in your life. Also, a
22+
> mentor is someone who has done the thing and is either where you've been or want to go. AKA, don't ask a someone how
23+
> to become a CEO when that person has never been a CEO. (Hopefully that give you a good idea)
24+
2125
## What this does
2226

23-
As mentioned prior, we want to find a way to _ramp-up_. This library will show work through the scenarios of houses and
24-
provide a way to collect them. Equally, it'll provide feedback as to why you passed on a property. For example, it could
25-
be that you didn't have enough cash. Another is that you may have wanted more cash flow per month. After you see the
26-
trends, and based on the time-line hand at play, you could realize that your ideas need to become a littler more broad.
27+
As mentioned prior, we want to find a way to _ramp-up_. This library will work through the scenarios of houses and
28+
provide a way to collect them. Once it completes, it'll provide feedback as to why you passed on a property.
29+
30+
For example, it could be that you didn't have enough cash. Another is that you may have wanted more cash flow per month.
31+
After you see the trends, and based on the time-line hand at play, you could realize that you might need to expand on
32+
your ideal finds.
2733

28-
The basics are that we do a loop that simulates a per-month advance. In there we take your money saved and determine if
29-
you have enough to acquire more properties. Equally, we run through selling of properties too, because it's a common
30-
practice.
34+
The library loop simulates a per-month savings. In there it will take your money saved and determine if you have enough
35+
to acquire more properties. Equally, we run through selling of properties too, because it's a common practice.
3136

3237
## Building
3338

3439
### Node version
3540

36-
On linux, mac, etc... do: `$ nvm install`
41+
On linux, mac, etc... do: `$ nvm install`, referencing this manager: `https://github.com/nvm-sh/nvm`
3742

38-
on windows: `$ nvs install`
43+
on windows: `$ nvs install`, referencing this manager: `https://github.com/jasongin/nvs`
3944

4045
### Getting the Repo up and running
4146

@@ -47,7 +52,7 @@ on windows: `$ nvs install`
4752

4853
`$ npm run tests`
4954

50-
> ⚠ This can run using Wallaby.js in automatic config, or you can use the config supplied here
55+
> ☝️This can run using Wallaby.js in automatic config, or you can use the config supplied here
5156
5257
### CI/CD (Delivery)
5358

@@ -68,7 +73,10 @@ Once a feature's PR is merged, the pipeline will run checks and publish.
6873
- Mortgage calc ✔
6974
- Rent amount ✔
7075
- Cash flow ✔
76+
- Appreciation calculation ✔
7177
- Equity (simple) ✔
78+
- Monthly summaries ✔
79+
- Annual summaries ✔
7280

7381
### Needed things (in no order)
7482

@@ -81,8 +89,9 @@ Once a feature's PR is merged, the pipeline will run checks and publish.
8189

8290
## Missing features
8391

84-
1. user
85-
2. apartments (passive investor)
92+
1. currency formatting
93+
2. easier setup
94+
3. apartments (passive investor)
8695

8796
## Future
8897

@@ -93,27 +102,148 @@ Once a feature's PR is merged, the pipeline will run checks and publish.
93102
As this is still in progress, the current flow is as follows:
94103

95104
```typescript
96-
import * as all from "@cubedelement.com/realty-investor-timeline";
97-
98-
const metGoal: all.HasMetGoalOrMaxTime = (
99-
start: Date,
100-
today: Date,
101-
user: all.IUser,
102-
maxYears: number
103-
): boolean => {
104-
if (user.goals.metMonthlyGoal(today)) {
105-
return true;
106-
}
107-
108-
return false;
105+
import {
106+
HasMetGoalOrMaxTime,
107+
ILoopOptions,
108+
loop,
109+
LedgerCollection,
110+
LoanSettings,
111+
PropertyType,
112+
RentalGenerator,
113+
RentalSingleFamily,
114+
RuleEvaluation,
115+
PurchaseRuleTypes,
116+
ValueCache,
117+
User,
118+
} from "@cubedelement.com/realty-investor-timeline";
119+
120+
// setup up how much money you have to get started
121+
const totalSavings = new LedgerItem();
122+
totalSavings.amount = 100000;
123+
totalSavings.note = "already saved";
124+
totalSavings.type = LedgerItemType.Saved;
125+
totalSavings.created = new Date();
126+
totalSavings.created.setDate(1);
127+
128+
const ledgerCollection = new LedgerCollection();
129+
ledgerCollection.add(totalSavings);
130+
131+
//you
132+
const user = new User(ledgerCollection);
133+
user.monthlySavedAmount = 10000; //everything you put into savings each month after your expenses
134+
135+
//TODO: clean this idea up
136+
user.goals = {
137+
metMonthlyGoal(today: Date): boolean {
138+
return (
139+
ledgerCollection.getCashFlowMonth(today) >=
140+
user.goals.monthlyIncomeAmountGoal
141+
);
142+
},
143+
monthlyIncomeAmountGoal: 10000,
109144
};
110145

111-
const results = all.loop(
146+
//These are loan rules. In this example for SingleFamily (SF) we have good credit loan amount with a conventional 30 year mortgage.
147+
//Also, for this state, it's required to keep 6 months of savings for each rental
148+
user.loanSettings = [
149+
{
150+
propertyType: PropertyType.SingleFamily,
151+
name: LoanSettings.minimumReservesSingleFamily,
152+
value: 6,
153+
},
154+
{
155+
name: LoanSettings.loanRatePercent,
156+
value: 4,
157+
propertyType: PropertyType.SingleFamily,
158+
},
112159
{
113-
startDate: new Date(),
114-
maxYears: 30,
115-
hasMetGoalOrMaxTime: metGoal,
160+
name: LoanSettings.loanTermInYears,
161+
value: 30,
162+
propertyType: PropertyType.SingleFamily,
116163
},
117-
user
164+
];
165+
166+
//These are your requirements for what you're looking for in a property!
167+
user.purchaseRules = [
168+
new RuleEvaluation(30000, PurchaseRuleTypes.maxEstimatedOutOfPocket),
169+
new RuleEvaluation(7000, PurchaseRuleTypes.minEstimatedCapitalGains),
170+
new RuleEvaluation(200, PurchaseRuleTypes.minEstimatedCashFlowPerMonth),
171+
];
172+
173+
const date = new Date();
174+
const valueCache = new ValueCache(
175+
new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)),
176+
[],
177+
2
118178
);
179+
const propertyGeneratorSingleFamily = new RentalGenerator<RentalSingleFamily>(
180+
valueCache,
181+
generateSingleFamily
182+
);
183+
propertyGeneratorSingleFamily.maxRentalOpportunities = 4;
184+
propertyGeneratorSingleFamily.highestMinSellInYears = 1;
185+
propertyGeneratorSingleFamily.lowestMinSellInYears = 1;
186+
propertyGeneratorSingleFamily.highestPriceDown = 200000;
187+
propertyGeneratorSingleFamily.lowestPriceDown = 150000;
188+
propertyGeneratorSingleFamily.highestSellAppreciationPercent = 7;
189+
propertyGeneratorSingleFamily.lowestSellAppreciationPercent = 5;
190+
propertyGeneratorSingleFamily.lowestCashFlowMonthly = 200;
191+
propertyGeneratorSingleFamily.highestCashFlowMonthly = 500;
192+
propertyGeneratorSingleFamily.lowestEquityCapturePercent = 7;
193+
propertyGeneratorSingleFamily.highestEquityCapturePercent = 15;
194+
195+
const options: ILoopOptions = {
196+
propertyGeneratorSingleFamily,
197+
maxYears: 1,
198+
};
199+
200+
const actual = loop(options, user);
201+
202+
//Finally, to review your results, you can use the ledgerCollection from above.
203+
const lastYear = ledgerCollection.getSummariesAnnual(
204+
actual.endDate.getUTCFullYear()
205+
);
206+
```
207+
208+
The example result object models:
209+
ITimeline:
210+
211+
```JSON
212+
{
213+
"startDate": "2021-11-01T00:00:00.000Z",
214+
"endDate": "2022-11-01T00:00:00.000Z",
215+
"rentals": [
216+
{
217+
"property": {
218+
"sellPriceAppreciationPercent": 6,
219+
"minSellYears": 1,
220+
"id": "7674c1ec-2fb6-4828-9490-271e9ab4a3c4",
221+
"purchasePrice": 162373,
222+
"address": "1791 Wama Extension",
223+
"monthlyCashFlow": 237,
224+
"availableStartDate": "2021-12-01T00:00:00.000Z",
225+
"availableEndDate": "2022-02-01T00:00:00.000Z",
226+
"cashDownPercent": 25,
227+
"monthlyPrincipalInterestTaxInterest": 949.5723384565815,
228+
"_purchaseDate": "2022-01-01T06:00:00.000Z",
229+
"_soldDate": "2022-02-01T06:00:00.000Z"
230+
},
231+
"reasons": []
232+
}
233+
],
234+
"user": {
235+
"ledgerCollection": {
236+
"collection": {
237+
"source": [
238+
{
239+
"amount": 119603.25,
240+
"type": "Equity Capture",
241+
"created": "2022-11-01T00:00:00.000Z",
242+
"note": "for: 1791 Wama Extension, id: 7674c1ec-2fb6-4828-9490-271e9ab4a3c4"
243+
}
244+
]
245+
}
246+
}
247+
}
248+
}
119249
```

jest.ci.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property and type check, visit:
3+
* https://jestjs.io/docs/configuration
4+
*/
5+
6+
// eslint-disable-next-line @typescript-eslint/no-var-requires
7+
const uConfig = require('./jest.config').default;
8+
9+
export default {
10+
...uConfig,
11+
testResultsProcessor: 'jest-junit',
12+
reporters: ['default', 'jest-junit'],
13+
};

jest.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
// bail: 0,
1212

1313
// The directory where Jest should store its cached dependency information
14-
// cacheDirectory: "",
14+
cacheDirectory: ".jestcache",
1515

1616
// Automatically clear mock calls and instances between every test
1717
// clearMocks: false,
@@ -26,7 +26,7 @@ export default {
2626
coverageDirectory: "coverage",
2727

2828
// An array of regexp pattern strings used to skip coverage collection
29-
coveragePathIgnorePatterns: ["\\node_modules\\", "\\src\\**\\index.ts"],
29+
coveragePathIgnorePatterns: ["/node_modules/", "index.ts"],
3030

3131
// Indicates which provider should be used to instrument code for coverage
3232
// coverageProvider: "babel",
@@ -167,7 +167,7 @@ export default {
167167
// unmockedModulePathPatterns: undefined,
168168

169169
// Indicates whether each individual test should be reported during the run
170-
// verbose: true,
170+
verbose: true,
171171

172172
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
173173
// watchPathIgnorePatterns: [],

0 commit comments

Comments
 (0)