Created
April 6, 2024 02:24
-
-
Save psybers/08209da87e7b8fba731aa7a375b09b33 to your computer and use it in GitHub Desktop.
Finds uncategorized transactions in Actual Budget and asks Chat GPT to suggest the category.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"@actual-app/api": "^6.7.0", | |
"dotenv": "^16.4.5", | |
"openai": "^4.33.0" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require("dotenv").config(); | |
const api = require('@actual-app/api'); | |
const url = process.env.ACTUAL_SERVER_URL || ''; | |
const password = process.env.ACTUAL_SERVER_PASSWORD || ''; | |
const sync_id = process.env.ACTUAL_SYNC_ID || ''; | |
const cache = process.env.IMPORTER_CACHE_DIR || './cache'; | |
if (!url || !password || !sync_id) { | |
console.error('Required settings for Actual not provided.'); | |
process.exit(1); | |
} | |
const OpenAI = require("openai"); | |
const openai_key = process.env.OPENAI_API_KEY || ''; | |
if (!openai_key) { | |
console.error('OpenAI API key missing.'); | |
process.exit(1); | |
} | |
const openai = new OpenAI({ apiKey: openai_key }); | |
const chat = async (messages) => { | |
return await openai.chat.completions.create({ | |
model: "gpt-3.5-turbo", | |
messages: messages, | |
temperature: 0, | |
max_tokens: 500, | |
}); | |
}; | |
const instructions = (categories) => { | |
return { | |
role: "system", | |
content: 'You are a tool to automatically categorize transactions into one category, without providing any additional information or context.' + | |
' Responses do not need to be full sentences.' + | |
' Only respond with the category.' + | |
` These are the categories: ${categories}.` | |
}; | |
} | |
const categorize = (transaction) => { | |
return { role: "user", content: transaction }; | |
} | |
(async () => { | |
console.log("connect"); | |
await api.init({ serverURL: url, password: password, dataDir: cache }); | |
console.log("open file"); | |
await api.downloadBudget(sync_id); | |
const res = await api.runQuery( | |
api.q('transactions') | |
.filter({ | |
'category': null, | |
'transfer_id': null, | |
'tombstone': false, | |
'starting_balance_flag': false, | |
'account.closed': false, | |
'account.offbudget': false, | |
'account.tombstone': false, | |
}).select('*') | |
.orderBy({ 'date': 'desc' }) | |
); | |
if (res.data.length) { | |
console.log(`${res.data.length} uncategorized transactions found`); | |
console.log("get categories"); | |
let existing_categories = await api.getCategories(); | |
let categories = []; | |
for (i = 0; i < existing_categories.length; i++){ | |
categories.push(existing_categories[i].name); | |
} | |
let messages = []; | |
messages.push(instructions(categories.join(', '))); | |
for (i = 0; i < res.data.length; i++) { | |
console.log(`transaction: ${res.data[i].imported_payee}`); | |
messages.push(categorize(res.data[i].imported_payee)) | |
const response = await chat(messages); | |
console.log(`response: ${response.choices[0].message.content}`); | |
messages.pop(); | |
} | |
} else { | |
console.log("no uncategorized transactions found"); | |
} | |
console.log("done"); | |
await api.shutdown(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment