+ kasownik_classifier

main
radex 2023-07-13 18:28:39 +02:00
parent c0c72966d7
commit 56b4aebb91
7 changed files with 408 additions and 0 deletions

3
kasownik_classifier/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
raw_transfers.json
raw_transfers.txt

View File

@ -0,0 +1,59 @@
Finanse w 2021, wszystko podane w przeliczeniu na miesiąc
Koszty stałe:
- czynsz, woda, prąd, ogrzewanie: -7095 PLN
- bgp.wtf (nitronet, epix, ripe): -1201 PLN
- księgowość: -984 PLN
= 9280/ms
Przychody główne:
- składki członkowskie: 9645 PLN
- przychody bgp.wtf: 1002 PLN
= 10647/ms
Pozostałe przychody:
- granty: 1667
- darowizny: 1335
- darowizny celowe: 611
= 3613/ms
Pozostałe koszty:
- wydatki związane z grantmi: 2304
- prawnicy: 210
- zakupy (budowlane): 181
- zakupy (materiały): 317
- zakupy (pozostałe): 955
- zakupy (zwroty): 285
- wydatki nieskategoryzowane: 660
- opłaty bankowe: 11
= 4923/ms
balance: +57/ms
————————————————————————
czynsz:
2017: 5119/mo
2018: 5394/mo
2019: 5458/mo
2020: 6457/mo
2021: 7094/mo +38% vs 2017
————————————————————————
składki członkowskie:
2017: 6987/mo
2018: 8227/mo
2019: 7730/mo
2020: 8905/mo
2021: 9644/mo +38% vs 2017
————————————————————————
przychody bgp.wtf:
2019: 507/mo
2020: 361/mo
2021: 1002/mo

View File

@ -0,0 +1,285 @@
const fs = require('fs')
const fp = require('rambdax')
let rawTransfers = JSON.parse(fs.readFileSync('raw_transfers.json').toString('utf8'))
rawTransfers = fp.piped(rawTransfers, fp.sortByPath('date'))
const pad = (chars, text, padding = ' ') => {
return `${text}${Array(Math.max(chars - text.length, 0)).join(padding)}`
}
const padRight = (chars, text, padding = ' ') => {
return `${Array(Math.max(chars - text.length, 0) + 1).join(padding)}${text}`
}
const formatAmmount = (amount) => {
const zl = amount >= 100 ? String(amount).slice(0, -2) : '0'
const gr = String(amount).slice(-2)
return zl + ',' + padRight(2, gr, '0')
}
const toPln = (currency) => {
switch (currency) {
case 'PLN':
return 1.0
case 'EUR':
return 4.57
default:
throw new Error('Unknown currency multiplier for ' + currency)
}
}
const sign = (transfer) => {
return transfer.type.startsWith('IN') ? 1 : -1
}
const transferPlnValue = (transfer) => {
return sign(transfer) * Number(transfer.amount) * toPln(transfer.currency)
}
const sum = (xs) => xs.reduce((a, b) => a + b, 0)
function printTransfer(transfer) {
const { title, type, date, amount, currency } = transfer
// type: [ 'IN', 'OUT_TO_OWN', 'BANK_FEE', 'IN_FROM_OWN', 'OUT' ]
const sign = type.startsWith('IN') ? '+' : '-'
console.log(
`${date} | ${pad(15, classify(transfer))} | ${pad(80, title)} | ${sign} ${padRight(
8,
formatAmmount(amount),
)} ${currency}`,
)
}
function classify(transfer) {
if (
transfer.type === 'IN' &&
(transfer.to_account === 'PL48195000012006000648890002' ||
transfer.title.match(/^\w+ ?- ?(fatty|starving|superfatty) ?- ?sk(l|ł)adka/i) ||
transfer.title.match(/^sk(l|ł)adka ?- \w+ ?- ?\d+ ?m/i) ||
transfer.title.match(/skladka - wooddy/i)) &&
!transfer.title.match(/(kaucj|zwrot|lokata|grant|covid)/i)
) {
return 'memberships'
}
if (
transfer.type === 'BANK_FEE' ||
transfer.title.match(
/(Opłata za przelew|Miesięczny abonament|ZWROT OPŁATY ZA PROWADZENIE RACHUNKU)/,
)
) {
return 'bank_fees'
}
if (
transfer.type === 'OUT' &&
(transfer.to_name.includes('RIPE') ||
transfer.to_name.includes('Stowarzyszenie e-Południe') ||
transfer.to_name.includes('Nitronet sp. z o. o.') ||
transfer.to_name.includes('Nitronet sp. z o.o.'))
) {
return 'isp_fees'
}
if (
transfer.to_name === 'PSP Zjednoczenie' ||
transfer.title.startsWith('CZYNSZ - GRZYBOWSKA 85c') ||
transfer.title.match(/(zwrot kaucji|zwrot wadium|najem lokali)/i)
) {
return 'rent'
}
if (
transfer.type === 'OUT' &&
(transfer.to_name.startsWith('A&M') ||
transfer.title.match(/usługi prawne/i) ||
transfer.to_name.match(/Lookreatywni/))
) {
return 'legal'
}
if (
(transfer.type === 'IN' &&
transfer.to_account === 'PL64195000012006000648890005' &&
transfer.title.toLowerCase().includes('fv')) ||
transfer.title.match(/internet BGP.WTF/i) ||
transfer.title.match(/internet - umowa HSWAW/i) ||
transfer.title.includes('Invoice N. FV/21043')
) {
return 'bgp_wtf_income'
}
if (
transfer.type === 'IN' &&
transfer.date.startsWith('2020-') &&
transfer.title.match(
/(coronavirus|c.vid|przy(ł|l)bic|curvovid|powodzenia m|owner w k|dzialal. promocyj.|^Przelew$|^TRANSFER$|^Outgoing payment$)/i,
)
) {
return 'covid19_donation'
}
if (
transfer.type === 'OUT' &&
transfer.date >= '2020-03-31' &&
transfer.date <= '2020-06-24' &&
transfer.from_account === 'PL91195000012006000648890004'
) {
return 'covid19'
}
if (
transfer.type === 'IN' &&
transfer.to_account === 'PL64195000012006000648890005' &&
transfer.title.match(/(koszulk|\d ?szt)/i)
) {
return 'swag_sale'
}
if (transfer.type === 'IN' && transfer.title.match(/(grant|mikrodotacja|transza)/i)) {
return 'grant'
}
const projectDonation = transfer.title.match(
/(^\w+[\s-]*(?:darowizna|darownizna|DAROWNIZNA)[\s-]*(.*)$|(?:skladka|darowizna) celowa -? ?(.*)$|skladka na pokrycie (.*)|na zakup (.*)|darowizna na (.*))/i,
)
if (transfer.type === 'IN' && projectDonation && !transfer.title.match(/cele statutowe/)) {
const cause = projectDonation.slice(2).filter(Boolean)[0]
return `donation_cause`
// return `donation: ${cause.toLowerCase()}`
}
if (transfer.type === 'IN' && transfer.title.match(/(darowizn|uk online giving|testow)/i)) {
return `donation`
}
if (
transfer.type === 'OUT' &&
transfer.title.match(
/(Leroy Merlin Warszawa|Castorama Warszawa|bricoman.pl|zakup materiałów budowlanych|Market Budowlany)/i,
)
) {
return 'construction'
}
if (transfer.title.match(/(zwrot z podatku vat|KRS)/i)) {
return 'taxes'
}
if (transfer.from_name.match(/CURRENCY ONE/i) || transfer.to_name.match(/CURRENCY ONE/i)) {
return 'currency_exchange'
}
if (transfer.title.match(/lokata nr/i)) {
return 'time_deposit'
}
if (
transfer.type === 'OUT' &&
(transfer.title.match(/(stal hutnicza|stawex)/) ||
transfer.to_name.match(/stawex/i) ||
transfer.to_name.match(/nor-gaz/i))
) {
return 'materials'
}
if (
transfer.type === 'OUT' &&
transfer.title.match(/(^zwrot |Zakup przy użyciu karty|PayU w Allegro|Przelewy24|^zwroty )/i)
) {
return 'purchases'
}
if (transfer.type === 'OUT' && transfer.to_name.match(/(eniy soro|radosław pietruszewski)/i)) {
return 'purchases_returns'
}
if (
transfer.type === 'OUT' &&
(transfer.date === '2021-12-21' || transfer.to_name.startsWith('Maker Kids Michał'))
) {
return 'grant_expenses'
}
if (transfer.type === 'IN') {
return 'in?'
}
if (transfer.type === 'OUT') {
return 'out?'
}
return '??'
}
const printTransfers = (transfers) => {
transfers.forEach(printTransfer)
console.log('')
const total = sum(transfers.map(transferPlnValue))
console.log(`Total: ${Math.round(total) / 100} PLN`)
console.log(`Total/mo: ${Math.round(total / 12 / 100)} PLN`)
console.log(`Transfers: ${transfers.length}`)
}
const printCategorized = (transfers) => {
const categories = {}
transfers.forEach((tx) => {
const type = classify(tx)
if (!categories[type]) {
categories[type] = []
}
categories[type].push(tx)
})
Object.entries(categories).map(([category, categoryTransfers]) => {
console.log('')
console.log('___________________________')
console.log(`Category: ${category}`)
console.log('')
printTransfers(categoryTransfers)
})
}
// printCategorized(
// rawTransfers
// .filter((x) => classify(x) !== 'memberships')
// // .filter((x) => classify(x) === 'bank_fees')
// // .filter((x) => classify(x).startsWith('donation:'))
// // .filter((x) => x.date.startsWith('2021-'))
// // .filter((x) => x.amount > 10_000_00)
// // .filter((x) => x.type === 'OUT_TO_OWN')
// // .filter((x) => classify(x) === '??')
// // .filter((x) => x.title.toLowerCase().includes('uk online giving'))
// // .filter((x) => x.title.match(/kuh/i))
// // .filter(
// // (x) =>
// // x.type === 'IN' &&
// // x.date.startsWith('2020-') &&
// // x.title.match(/(coronavirus|c.vid|przy(ł|l)bic|curvovid)/i),
// // )
// // .slice(-500),
// .concat([]),
// )
const generateMonthDates = () => {
const now = new Date()
const currentMonth = now.getMonth() + 1
const currentYear = now.getFullYear()
const dates = []
for (let y = 2016; y <= currentYear; y++) {
const initialMonth = y == 2016 ? 11 : 1
const maxMonth = y == currentYear ? currentMonth : 12
for (let m = initialMonth; m <= maxMonth; m++) {
dates.push(`${y}-${m < 10 ? 0 : ''}${m}`)
}
}
return dates
}
const monthDates = generateMonthDates()
const printMonthSums = (transfers) => {
const groupped = fp.piped(
transfers,
fp.groupBy((t) => t.date.slice(0, -3)),
)
monthDates.forEach((date) => {
const txs = groupped[date] || []
const sum = txs.reduce((sum, transfer) => sum + transferPlnValue(transfer), 0)
console.log(`${date} | ${padRight(8, formatAmmount(Math.round(sum)))}`)
})
}
// printMonthSums(rawTransfers.filter((x) => classify(x) === 'bank_fees'))
const days = rawTransfers
.filter((x) => classify(x) !== 'memberships')
.concat([])
.map((x) => Number(x.date.match(/.*-(\d{2})$/)[1]))
.sort()
const dayCounts = Array(32).fill(0)
days.forEach((day) => {
dayCounts[day] += 1
})
console.log(
dayCounts
.slice(1)
.map((count, day) => ({ day, count }))
.sort((a, b) => b.count - a.count)
.map(({ day, count }) => `${day + 1} | ${count}`)
.join('\n'),
)

View File

@ -0,0 +1,32 @@
const fs = require('fs')
// INSTRUCTIONS FOR GENERATION:
// psql -h 127.0.0.1 -U radex kasownik
// \copy (select * from raw_transfer) to 'raw_transfers.txt' delimiter '~' csv header;
// scp radex@hackerspace.pl:~/raw_transfers.txt .
// node convert_to_json.js
function zipObj(keys, values) {
const obj = {}
if (keys.length !== values.length) {
console.error(keys)
console.error(values)
throw new Error(`broken row`)
}
keys.forEach((key, i) => {
obj[key] = values[i]
})
return obj
}
function convert() {
let data = fs.readFileSync('raw_transfers.txt').toString('utf8').trim().split('\n')
const header = data.shift().split('~')
console.log(header)
const rows = data.filter(Boolean).map((row) => zipObj(header, row.split('~')))
const json = JSON.stringify(rows, null, ' ')
fs.writeFileSync('raw_transfers.json', json)
}
convert()
k

View File

@ -0,0 +1,14 @@
{
"name": "kasownik_clasifier",
"version": "1.0.0",
"description": "",
"main": "analyze.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"rambdax": "^7.2.0"
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
printWidth: 100,
trailingComma: "all",
semi: false,
singleQuote: true,
bracketSpacing: true,
};

View File

@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
rambdax@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/rambdax/-/rambdax-7.2.0.tgz#f2632cb96a89d330d05fa41b488c12185b46001a"
integrity sha512-l1r7tYf/oRIpCiw1BJB7kSYl9i7H4tJheKbnWq95DM83Q4CazmRYJ+WM/VRaffr09yvMCdgeF4n6Ya4uWBe/Aw==