diff --git a/README.md b/README.md index 2e02abf..c496f3f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This is a collection of useful scripts to help you manage your Actual Budget. - Scripts: - [Sync Remote Banks](#sync-remote-banks) - [Loan Interest Calculator](#loan-interest-calculator) + - [Tracking Home Prices (RentCast's Value Estimate)](#tracking-home-prices-rentcasts-value-estimate) - [Tracking Home Prices (Zillow's Zestimate)](#tracking-home-prices-zillows-zestimate) - [Tracking Car Prices (Kelley Blue Book)](#tracking-car-prices-kelley-blue-book) - [Tracking Investment Accounts](#tracking-investment-accounts) @@ -166,6 +167,54 @@ node apply-interest.js It is recommended to run this script once per month. +### Tracking Home Prices (RentCast's value estimate) + +This script tracks the RentCast value for a home. It adds new transactions +to keep the account balance equal to the latest value. Rentcast values differ +from Zillow since they don't have as complete a database, but they are close in +most cases. + +To use this script, you need to create a new account in Actual Budget and set +the account note to populate the fields that RentCast needs: `address`, `bedrooms`, +`bathrooms`, `squareFootage`, and optionally `propertyType` and/or `compCount`. Values with +spaces and special characters need to be URL encoded, an online encoder +like https://www.urlencoder.org/ is helpful. + +`address` needs be one line with commas separating the address lines, then +URL encoded. Supply the number of bedrooms, bathrooms, and square footage for a +more accurate estimate. + +Example note using address "123 Example, St Anytown, CA ,12345": +``` +address:123%20Example%2C%20St%20Anytown%2C%20CA%20%2C12345 +bedrooms:4 +bathrooms:2 +squareFootage:1600 +``` + +`compCount` defaults to 25 for higher accuracy. `propertyType` defaults to "Single Family". +See https://developers.rentcast.io/reference/property-types for other options. + +Optionally, you can also specify if you only own a portion of the home by +adding an `ownership:0.0X` tag to the account note. For example, if you own +10% of the home, add `ownership:0.10` to the account note. The script will +then use that percentage to track the home's value. + +You will need to create an account on https://app.rentcast.io/app/api and setup +billing for an API Developer plan. They offer 50 API calls per month for free. +Copy you API key into `RENTCAST_API_KEY` setting the `.env` file. + +You can optionally change the payee used for the transactions by setting +`RENTCAST_PAYEE_NAME` in the `.env` file. + +To run: + +```console +node rentcast.js +``` + +It is recommended to run this script once per month. + ### Tracking Home Prices (Zillow's Zestimate) This script tracks the Zillow Zestimate for a home. It adds new transactions diff --git a/example.env b/example.env index c6d49c2..acb45d1 100644 --- a/example.env +++ b/example.env @@ -19,3 +19,7 @@ INVESTMENT_PAYEE_NAME="Investment" INVESTMENT_CATEGORY_GROUP_NAME="Income" # optional, name of the category for added investment tracking transactions INVESTMENT_CATEGORY_NAME="Investment" + +#optional, RentCast API key for fetching property data +RENTCAST_API_KEY="" +RENTCAST_PAYEE_NAME="RentCast" \ No newline at end of file diff --git a/rentcast.js b/rentcast.js new file mode 100644 index 0000000..a3be362 --- /dev/null +++ b/rentcast.js @@ -0,0 +1,79 @@ +const api = require('@actual-app/api'); +const jsdom = require("jsdom"); +const { closeBudget, ensurePayee, getAccountBalance, getAccountNote, getTagValue, openBudget, showPercent, sleep } = require('./utils'); +require("dotenv").config(); + +async function getRentCast(URL) { + console.log('RentCast URL:', URL); + const response = await fetch(URL, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'X-Api-Key': process.env.RENTCAST_API_KEY || '', + } + }); + + return await response.json(); +} + +(async function () { + await openBudget(); + + const payeeId = await ensurePayee(process.env.RENTCAST_PAYEE_NAME || 'RentCast'); + + const accounts = await api.getAccounts(); + for (const account of accounts) { + const note = await getAccountNote(account); + + if (note) { + let address = getTagValue(note, 'address'); + if (address) { + let URL = "https://api.rentcast.io/v1/avm/value?"; + URL += `address=${address}`; + + let propertyType = getTagValue(note, 'propertyType', 'Single%20Family'); + if (propertyType) URL += `&propertyType=${propertyType}`; + let bedrooms = parseInt(getTagValue(note, 'bedrooms')); + if (bedrooms) URL += `&bedrooms=${bedrooms}`; + let bathrooms = parseFloat(getTagValue(note, 'bathrooms')); + if (bathrooms) URL += `&bathrooms=${bathrooms}`; + let squareFootage = parseInt(getTagValue(note, 'squareFootage')); + if (squareFootage) URL += `&squareFootage=${squareFootage}`; + let compCount = parseInt(getTagValue(note, 'compCount', 25)); + if (compCount) URL += `&compCount=${compCount}`; + + let ownership = 1; + if (note.indexOf('ownership:') > -1) { + ownership = parseFloat(note.split('ownership:')[1].split(' ')[0]); + } + + console.log('Fetching RentCast for account:', account.name); + + const rc = await getRentCast(URL); + const value = rc.price * 100; // Convert to cents + const balance = await getAccountBalance(account); + const diff = (value * ownership) - balance; + + console.log('RentCast Value:', value); + console.log('Ownership:', value * ownership); + console.log('Balance:', balance); + console.log('Difference:', diff); + + if (diff != 0) { + await api.importTransactions(account.id, [{ + date: new Date(), + payee: payeeId, + amount: diff, + cleared: true, + reconciled: true, + notes: `Update Value to ${value * ownership / 100} (${value / 100}*${showPercent(ownership)})`, + }]); + } + + await sleep(200); // 1/4 the "20 per second" rate limit, just to be safe + } + } + } + + await closeBudget(); +}) ();