Lubię Kontomierz. Serio. Pozwala na ogarnięcie finansów i ma przypominajkę dzięki czemu nie trzeba koniecznie pamiętać o wszystkich wydatkach. Taki manager finansowy tylko trochę bardziej estetyczny. Ma jednak kilka wad związanych ze statykami. Na przykład nie potrafi odpowiedzieć na pytanie jakiej wielkości transakcje w danej kategorii stanowią większość. Niby duperela, ale istotna, bo pozwala na wyszukiwanie „wycieków” gotówki w ramach np. zakupów spożywczych.

Kontomierz pozwala na eksport danych do pliku csv. Kodowanego w CP1250 (żenua), ale to rozwiązujemy za pomocą:

Listing 1. Zmiana kodowania pliku

$ iconf -f cp1250 -t utf8 in.csv > out.csv

Mając już taki plik możemy go sobie obrobić w javie… jest jednak mały problem, bo maksymalna wielkość eksportu to 2000 transakcji (bardzo mało tak naprawdę). Zatem trzeba łączyć pliki z danego roku (co jest trochę upierdliwe). W nagrodę dostajemy jednak możliwość zrobienia dokładniejszych statystyk dla naszych finansów.

Poniżej przykładowy program liczący kilka statystyk dla zakupów z kategorii „spożywcze”.

Listing 2. Statystyka zakupów spożywczych

public class App {

	private static Set<Range> ranges = new HashSet<>();

	static {
		ranges.add(Range.closedOpen(0., 10.));
		ranges.add(Range.closedOpen(10., 20.));
		ranges.add(Range.closedOpen(20., 50.));
		ranges.add(Range.closedOpen(50., 100.));
		ranges.add(Range.closedOpen(100., 200.));
		ranges.add(Range.closedOpen(200., 500.));
		ranges.add(Range.closedOpen(500., 1000000.));
	}

	public static void main(String[] args) throws IOException, ParseException {
		CSVReader csvReader = new CSVReader();
		TransactionFile read = csvReader.read(new File("all_2014.csv"));

		Predicate<Record> ekonto = r -> r.accountName.equalsIgnoreCase("ekonto");
		Predicate<Record> portfel = r -> r.accountName.equalsIgnoreCase("portfel");
		read.transactions.stream()
				.filter(ekonto.or(portfel).and(r-> r.category.equalsIgnoreCase("spożywcze")))
				.collect(Collectors.groupingBy(r -> r.category))
				.entrySet()
				.forEach(e -> stat(e.getKey(), e.getValue()));
	}

	private static void stat(String key, List<Record> value) {
		System.out.println("Statystyka dla " + key);
		System.out.println("Liczba transakcji " + value.size());
		OptionalDouble average = value.stream()
				.mapToDouble(r -> Math.abs(r.amount.getNumber().doubleValueExact())).average();
		System.out.println("Średnia wartość transakcji " + average.orElse(0.));
		Map<Range, List<Record>> byRange = value.stream().collect(Collectors.groupingBy(r -> inRange(r)));
		ranges.stream()
				.sorted((l, r) -> l.lowerEndpoint().compareTo(r.lowerEndpoint()))
				.forEach(rng -> {
					Optional<List<Record>> records = Optional.ofNullable(byRange.get(rng));
					System.out.println("W zakresie " + rng + ": jest " + records.orElse(Collections.emptyList()).size() + " transakcji");
				});

	}

	private static Range inRange(Record r) {
		Optional<Range> range = ranges.stream()
				.filter(rng -> rng.contains(Math.abs(r.amount.getNumber().doubleValueExact()))).findFirst();
		return range.orElse(Range.closedOpen(-1., 0.));
	}
}

Nie jest to wzór piękna, ale napisanie tego zajęło mi wraz z napisaniem parsera do plików około godzinki.

Całość może pisać do pliku by można było to wrzucić do gnuplota aby mieć ładny rysunek.

Co jeszcze można np. odpowiednio dobierając filtry można sprawdzić czy wydajemy więcej na piwo czy żona na kosmetyki. Można też śledzić zmiany wydatków dla poszczególnych kategorii na przestrzeni np. lat czy miesięcy. Generalnie ogranicza nas wyobraźnia. Kod wrzucę na githuba jak go ogarnę tak by był bardziej przyjazny w użyciu.

Jest to przykład narzędzia, które robimy gdy chcemy szybko załatać braki w funkcjonalności jakiegoś innego narzędzia. Przy czym to inne narzędzie, tu Kontomierz, musi pozwalać na eksport danych w jakiś rozsądny sposób.