knuspermagier.de
Der privateste Blog von Philipp.

Kassenbons mit OpenAI Vision verstehen

Vor ein paar Monaten habe ich einmal probiert meine Ausgaben mit Firefly III zu tracken, aber ich muss leider sagen, dass es nicht so richtig gefunkt hat. Es ist einfach keine moderne App und lässt an manchen Stellen UX-technisch zu wünschen übrig. Außerdem hat es ein paar Funktionen, wie Budgets und Sparschweine, die ich (gerade) gar nicht brauche.

Was liegt da also näher als es kurz selber zu bauen. Ich hatte ja schon jahrelang eine eigene Lösung im Einsatz, die ich allerdings etwas vernachlässigt habe, da ich keine Lust auf eine uralte Vue 1-App mehr hatte. Danach ging es also zurück zur Excel-Tabelle und zum kurzen, eben erwähnten, Abstecher zu Firefly III.

Ich kopierte mir also eines meiner Standard-Laravel/Livewire-Projekte und klöppelte mir schnell etwas zusammen, was den Firefly-Export lesen konnte und bin schonmal halbwegs zufrieden.


Irgendwann, als ich einen Stapel Kassenbons fand, fiel mir mal wieder mein lange währender Wunsch ein, die zu digitalisieren. Wäre ja schon witzig zu wissen, was man damals eigentlich für 653,21€ bei Obi gekauft hat. Naja. Aber es ist ja immer noch alles nervig dachte ich. Zwar ist OCR-Technologie besser geworden, aber trotzdem müsste man ja noch alles irgendwie auswerten und zuordnen. Zudem stehen auf den Bons ja oft nur absurde Kürzel für die Produkte.

Plötzlich erinnerte ich mich daran, dass es ja OpenAI Vision gibt. Da sah ich in letzter Zeit ein paar verrückte Videos, wo Leute einfach ihr Bücherregal aufnehmen und die AI alle Buchtitel zurück liefert, und ähnliches.

Ich probierte also mal die Vision API mit einem Kassenbon und folgendem Prompt:

Das Bild ist ein Scan von einem Kassenbon. Bitte liefere mir folgende Daten als JSON.

  • Das Datum des Einkaufs (Feldname: date, im Format d.m.Y – Wenn keine Jahreszahl bekannt ist, nimm das aktuelle Jahr.),
  • Die Gesamtsumme (Feldname: total_sum),
  • Die Namen des Geschäfts (Feldname: store_name),
  • Eine Liste der Produkte, mit folgenden Unterfeldern: (Feldname: items)
    • Name (Feldname: name)
    • Geratener Vollständiger Langer name, falls der Name nur ein Kürzel ist (Feldname: long_name)
    • Preis (Feldname: amount, in Euro-Cents)

Dankeschön!

Was soll ich sagen — es funktioniert eigentlich ziemlich gut. Er schafft es ziemlich gut, sich plausible Sachen auszudenken, sodass der Bon fast benutzbar wird. Ich erwarte hier ja auch gar keine Perfektion, mir reicht, wenn es halbwegs Sinn ergibt. Im Zweifelsfall kann man ja immer noch den Scan direkt angucken.

kassenbon_json.png
Ausschnitt aus dem Ergebnis. “Spachtelstift” ist eigentlich “Spachtel, steif”, aber nun gut.

Drei Nachteile gibt es allerdings noch:

  • Es dauert relativ lange, also mehrere Sekunden, aber das ist man ja von ChatGPT gewohnt.
  • Es kostet pro Kassenbon so 3 - 5 Cent.
  • Manchmal kommen auch kuriose Antworten wie “Ich kann das leider nicht verarbeiten, weil da personenbezogene Daten drin sind”, oder er kennt einfach gar nichts. Im nächsten Anlauf funktioniert es aber. Der Geist in der Maschine!
Nach diesem geglückten Versuch, probierte ich später noch, ob er meinen Tisch mit Canasta-Karten zählen kann, aber das scheiterte leider kläglich. Muss also manuell weiterzählen. Schlimm.

Nach meinem kurzen Test baute ich es als Feature also in meine neue App ein. Die heißt natürlich Serenity, weil es zu Firefly passt und auch der viel bessere Name für so ein Tool ist als Firefly… DREI.

Verrat - Das Imperium der Ströme 2

John Scalzi

Im ersten und letzten Drittel sehr unterhaltsam, zwischendurch leider etwas viel Politik. Insgesamt etwas zu viele Erwähnungen, wie irgendwelche Leute Sex haben, was nicht so richtig etwas zur Story beiträgt.

Gute Lieder in März 2024

Ich habe mal wieder ein paar schöne Dinge auf Youtube entdeckt!
Hier eine umfassende Liste von Videos, die ich in den letzten Monaten sehr genossen habe, in umgekehrter zeitlicher Reihenfolge, da ich meinen Youtube-Verlauf von oben nach unten durchgangen bin:

Am Ende des Jahres muss ich mich auf jeden Fall nicht fragen, warum mein Spotify Wrapped mal wieder eine noch geringere Zahl an Minuten ausspucken wird…

Dinge, die mich aktuell an Swift und SwiftUI nerven (und teilweise Lösungen)

Gerade baue ich mal wieder ein bisschen an der Tagebuch-App, da ich sie, seitdem sie eine Swift-App ist, tatsächlich gerne benutze. Ich versuche also das ein oder andere, was die Webapp schon kann nachzurüsten und komme auch langsam voran, aber manche Dinge nerven mich aktuell schon ein bisschen:

Xcode gibt mir kein wohliges Gefühl mehr

Damals, als ich noch beruflich Objective C schrieb und mich sehr viel in Xcode aufgehalten habe, fand ich, dass es der beste Editor ist. Mittlerweile hat sich einiges getan und ich lebe quasi in PhpStorm. Jede Berührung mit Xcode tut seitdem ein bisschen weh, da es langsam ist, die Code Completion immer noch blöd ist und es sich teilweise dumm verhält. Außerdem gibt es kein automatisches Code-Formatting.

Ja, es gibt mit Ctrl + I das automatische re-indent, aber gefühlt geht das nicht so weit, wie das Formatting in PhpStorm und man muss vorher den Code markieren

Folgendes kann man tun um dem etwas entgegen zu wirken:

  • Im Theme die Font zu Jetbrains Mono wechseln, dann sieht es immerhin etwas bekannter aus.
  • SwiftFormat installieren. Damit erhält man ein tolles Tool, das einem die Dateien vernünftig formatiert, einrückt und sogar ein paar Fehler automatisch fixt. Ich hab mir Format File auf den gleichen Shortcut gelegt wie in PhpStorm.

Das “Live-Preview” ist nett gemeint, aber ziemlich unbrauchbar

Wenn man SwiftUI-Views baut, hat man rechts so ein Fenster, das einem Live alles anzeigt, damit man direkt sieht, was man da gebaut hat. An sich toll, auf der anderen Seite stürzt es bei mir öfter ab und braucht um die dreißig Sekunden, bis es wieder läuft. Oder, es pausiert wegen eines Syntaxfehler und wenn ich es ent-pausiere läd es wieder für dreißig Sekunden. Ich habe ein sehr teures Macbook mit einem sehr schnellen M1-Prozessor, das könnte alles schneller gehen.

screenshot-2024-02-26-at-14.30.09.png
Typische Ansicht des Preview-Fensters

SwiftData ist cool aber auch etwas beschränkend

Ich habe wirklich keine Lust jetzt etwas anderes zu lernen als SwiftData, aber dieser ganze Quatsch mit den #Predicate-Dingern, die irgendwie vom Compiler übersetzt werden sorgt dafür, dass ein paar Sachen noch nicht vernünftig gehen, weil in der Closure vom Predicate nur wenige Anweisungen erlaubt sind. Hier und da wünschte ich mir, ich könnte einfach SQL-Queries (bzw WHERE-Conditions) schnell selber tippen, statt sie in das Predicate-Konzept zu quetschen.

Für den “Alle Beiträge am gleichen Tag, aber in verschiedenen Jahren”-View muss ich z.B. schauen, ob der Monat und der Tag vom Erstellungsdatum der Einträge dem heutigen entsprechen. Man könnte also hoffen, dass man sowas gehen würde:

#Predicate<Item> { item in
    Calendar.current.dateComponents([.month, .day], from: item.timestamp) == dateComponentsNachDenenIchSuche
}

Geht aber nicht, da ich die Funktion in dem Predicate nicht aufrufen kann. Ich hab leider nach zehn Minuten googeln keine Lösung gefunden und speichere nun beim Erstellen der Einträge halt noch das Datum als MM-dd extra ab um danach zu selektieren.

Im Vergleich dazu, kann ich im Backend ja einfach folgendes machen:

$query->whereRaw('strftime("%m-%d", date) = ?', $filter['day-of-year']);

Ja, ich weiß, wahrscheinlich liegt es auch einfach daran, dass das hart unperformant ist, wenn man eine Million Tagebucheinträge hat. Jedes mal irgendeine Funktion aufrufen sorgt natürlich dafür, dass die Query einen Full Table Scan machen muss und nicht auf einen Index zurückgreifen kann, von daher ist es wahrscheinlich gar nicht dumm, hier vorzeitig zu optimieren

Versions-Wirrwarr

Es ist weiterhin so, dass sich zwischen den Swift-Versionen so viel getan hat und die Stack Overflow-Antworten teilweise uralt sind. Die Guten wurden irgendwann geupdated und sehen dann so aus:


UPDATE 3: Swift 6
[…]

UPDATE 2: Swift 3 - 5
[…]

UPDATE: Swift 2
[…]

Swift 1:
[…]


Viele aber auch nicht. ChatGPT ist ja leider auch schon etwas älter und liefert daher oft Code aus, der in aktuellem Swift einfach nicht mehr funktioniert. Leider konnte ich die ChatGPT-Dinger, die extra auf die aktuelle Doku trainiert wurden noch nicht testen, das sind glaub ich so custom GPTs, die man nur in der Bezahlversion bekommt? Hat das mal jemand getestet?

Xcode hat kein Copilot

Damit verbunden: Xcode hat kein Github Copilot-Ding. Seeehr nervig. Wobei ich auch nicht weiß, wie gut der Copilot wäre, vielleicht würde er an dem gleichen Problem scheitern, wie ChatGPT?

(Es gibt zwar so ein Projekt auf Github, das ist mir aber nicht ganz geheuer)

Der async/await-Support ist lückenhaft

Weiter kleine Nervigkeit: Noch nicht alle Apple Frameworks haben support für async/await, was ziemlich nervig ist, wenn man z.B. kurz mal ein paar Reminder mit EventKit rauspulen will. Schön wieder completionBlocks, nee, nee!

Fazit

Man muss schon sagen, dass das aktuell mein erfolgreichstes Abtauchen in die SwiftUI-Welt ist. Die App funktioniert gut, ich nutze sie täglich und ich bekomme es halbwegs schnell hin neue Features einzubauen.

Der größte Spaß kommt allerdings eher beim benutzen der App und weniger beim Entwickeln, weil ich ständig anecke, irgendwo warten muss, oder mich durch verwirrende Stack Overflow-Posts wurschteln muss. Letzteres wird natürlich irgendwann wegfallen, wenn ich genug Erfahrung habe und einfach alles weiß, aber bis dahin ist sicher auch schon Swift 8 und 9 raus und alles geht von vorne los!

Ich bin bei Sideprojects ein großer Quick’n’Dirty-Verfechter. Ich habe keine Lust hier Stunden zu verbringen um eine App, die nur ich benutze, perfekt zu optimieren und fehlerfrei zu bauen. Mit den ganzen Web-Technologien funktioniert das supergut, mit Laravel baue ich in wenigen Stunden alles, was ich brauchen könnte als Prototyp zusammen. In Swift bin ich was das angeht leider (noch) sehr langsam, was mich tierisch nervt. Aber das ist eher ein persönliches Problem.

Immerhin fühlt sich das Ergebnis, eine native App, am Ende so gut an, dass es mich genug motiviert, weiter zu bauen und weiter SwiftUI zu lernen, dass meine fehlende Erfahrung zumindest nicht mehr der Roadblock ist.

Kollaps - Das Imperium der Ströme 1

John Scalzi

Musste mal wieder etwas spannenderes dazwischen schieben. Dieses Buch hat mich sehr unterhalten, fand aber, dass ein bisschen zu viel "ficken" vorkam. Im englischen steht da sicher "fuck", was ich irgendwie angenehmer gefunden hätte, das klingt irgendwie schöner. Naja.

Google Sheets als Datenbasis

Im Wiki gibt es jetzt eine Seite auf der ich unsere Everdell-Spielergebnisse in einer kurzen Statistik präsentiere. Doch woher kommen die Daten dafür? Aufmerksame Leser:innen wissen jetzt schon Bescheid!

Natürlich habe ich keine Lust, die Punktzahlen hier im Kirby in zu pflegen, sowas gehört natürlich in ein Spreadsheet, damit man da wichtige wissenschaftliche Auswertungen fahren kann. Glücklickerweise hat Google Sheets ein tolles “Im Web veröffentlichen”-Feature mit dem man eine Tabelle über einen Link teilen kann, und zwar in verschiedenen Formaten, unter anderem als CSV! Das Beste: Die CSV aktualisiert sich regelmäßig, wenn man etwas am Sheet ändert.

step1.png
Hier klicken…
step2.png
…und fertig!

Der everdell-results-Block vom Wiki schnappt sich also einfach die CSV-Datei, läd sie herunter und zeigt die Daten schick an! Juchu!

(Diesen Trick hat mir mal ein Kollege aus meiner damaligen Firma gezeigt, danke Micha!)

Posts, die ich niemals schrieb (1)

Ich habe eine Ich habe eine viel zu lange Liste von möglichen Ideen für Posts in diesem Blog und manche sind so unspezifisch, dass ich gar keine Ahnung mehr habe, was ich eigentlich schreiben wollte. Teilweise sind die Sachen auch einfach so alt, dass sie gar keine Relevanz mehr haben. Da ich ungern etwas wegwerfe, gibt es hier eine spannende Liste:

  • Mein plötzliches Interesse an Turnschuhen

  • Webpack, Vue.JS und moderne Webentwicklung

  • Warum ich Webentwicklung hasse

  • Die großen fünf Trash-TV-Formate, die ich trotzdem gerne gucke
    (???)

  • Warum ich gerne ein iPad Pro hätte, aber noch keins habe
    (habe mittlerweile eins)

  • Was ich mir von der 6D Mark II erhoffe, und warum es wohl meine letzte DSLR sein wird
    (habe niemals eine 6Dm2 gekauft)

  • Der Youtube-Report 2017

  • 5 Jahre ohne Windows

  • Wie man mit 25 Jahren Autofahren lernt

  • Schwimmen für Anfänger

  • Minimalismus und oder her, Fotobücher müssen sein

  • Manche Dinge können weg

  • Warum mich der neue Fluch der Karibik-Film nicht interessiert obwohl Paul McCartney mitspielt
    (habe ich den mittlerweile überhaupt gesehen?)

  • Die großen drei Batterien, die man immer im Haus haben sollte

  • Warum Redmine scheiße ist, aber doch ganz ok

  • Meine neue Jacke
    (Sie ging nach einem Jahr kaputt)

  • Warum es so schwer ist schöne Schuhe zu finden und was man dagegen machen kann

  • Warum hier solang nichts passiert ist - Bloggen im Jahr 2017 und First World Problems

  • Soundbar vs 5.1

  • 10 Jahre Instagram

Wenn euch einer der Titel interessant vorkommt und ihr unbedingt einen Artikel dazu lesen wollt, sagt Bescheid.

MessageSifter

Letztens beschwerte ich mich ja, dass ich es nicht mehr schaffe, leicht per sqlite3-Befehl meine Nachrichten aus der iMessage-Datenbank zu exportieren. Irgendwann in den letzten Jahren hat Apple anscheinend angefangen, die Nachrichten lieber als NSAttributedString zu speichern, statt im Klartext.

Da ich trotzdem gerne weiterhin diverse Informationen, wie zum Beispiel gesendete Links oder Everdell-Ergebnisse exportieren wollte, musste ich mir also schnell eine Applikation bauen, mit der ich die Datenbank öffnen und die NSAttributedStrings dekodieren kann. Zum Glück konnte mir ChatGPT und das Internet helfen ein paar Zeilen Code zu schreiben, die ich mal bei Github hochgeladen habe, falls jemand ein ähnliches Problem hat.

Einmal kompilieren und dann kann man es wie folgt ausführen:

./MessageSifter "pfad-zur-chat.db" [Anzahl der Nachrichten]

Es werden dann alle Nachrichten, nach Datum abwärts sortiert ausgegeben und können mit grep oder so weiter durchsucht werden.

sifter.png
Ziemlich sinnloses Bild, wenn am alles zensiert!

Wahrscheinlich sollte man den Code aber anpassen, denn aktuell hängt da noch ein WHERE handle_id = 1 mit drin, das dafür sorgt, dass nur die Nachrichten zu einer bestimmten Person ausgegeben werden. Entweder löscht man das noch raus, oder man sucht in der Datenbank mal nach der passenden ID.

Für den unwahrscheinlichen Fall, dass das irgendwer braucht: Viel Spaß!

Homescreen, Februar 2024

homescreen.jpg

Mein Homescreen im Februar!

Neu dabei ist Home Assistant, dass ich tatsächlich gerne benutze, seitdem ich gelernt habe, wie man ein schönes Dashboard baut. Ansonsten habe ich mir Reminders noch hin gepackt, bin damit aber noch nicht so richtig zufrieden, denn mit dem Widget und Remind Faster habe ich damit quasi dreimal die gleiche App auf dem Homescreen. Naja.

Neu ist bejou24, was jetzt endlich nativ ist aber noch ein appigeres Icon braucht und Tabspace, woran ich natürlich seit dem Blogpost nichts gemacht habe, aber es funktioniert auch einfach ausreichend und das Icon ist einfach so schön.

Das Dock ist quasi seit Jahren unverändert, auch mit dem Kindle-Icon rechts, das ich seit Monaten nicht angetappt habe. Immerhin erinnert es mich daran, dass ich lieber mal ein Buch lesen sollte, statt meine Zeit mit anderen Dingen zu verschwenden. Leider recht erfolglos.

Mein Todo- und Notizsystem 2024

In den letzten Jahren benutzte ich viele Apps um alles was mit Todos und Notizen irgendwie zu tun hat zu sortieren. Eine meiner Lieblingsapps war Things. Es war und ist eine wunderschöne App!

Ich legte dort alles mögliche an, diverse wiederholende Sachen, den ganzen Hausputz als wiederkehrende Projekte und vor allem alles mögliche, was mir einfiel, was ich irgendwann mal machen wollte, als abhakbare Todo. Nach einer Zeit merkte ich, auch mit Hilfe diverser Artikel und Freunde, dass das ein Fehler war. Der wichtigste Satz ist für mich seit dem: Nicht alles braucht eine Checkbox.

Im Folgenden möchte ich mal die vier Grundpfeiler meiner aktuellen Selbstorganisation vorstellen.

Weiterlesen →