Org-anizing My Fragrance Collection with Emacs (March, 2025)

I can’t decide whether my habit of weaving Emacs—especially Org-Mode—into every hobby of mine is a blessing or a curse sometimes. One of those hobbies I keep coming back to is fragrances. A few weeks ago I started putting all flacons I own on a denote page - after a friend of mine was surprised that I didn’t have a spreadsheet or a Parfumo account. We were sampling fragrances together when they asked how I keep track of the bottles I own and the ones I’ve liked over the years - turns out: I simply didn't!

Could This Be A Capture Template?

Since this is a lisp and emacs focussed blog, and my denote Zettelkasten is already the place where I keep track of my hobbies, like books, recipes, music and pretty much everything else in my life - instead of signing up to yet-another-web-service like Parfumo, I did the obvious thing: I started outlining a capture template to track parfumes with:

("h" "New Fragrance Entry" entry
             (file+headline "~/docs/denote/20250315T200825--my-fragrances__fragrances_personal.org" "My Fragrance Collection")
             "** %^{Fragrance Name}
  <%(format-time-string \"%Y-%m-%d\")>
  :PROPERTIES:
  :BRAND:       %^{Brand}
  :PARFUMER:    %^{Parfumer}
  :TYPE:        %^{Type|Eau de Parfum|Eau de Toilette|Extrait de Parfum}
  :TOP_NOTES:   %^{Top Notes}
  :MIDDLE_NOTES: %^{Middle Notes}
  :BASE_NOTES:  %^{Base Notes}
  :ACCORDS:     %^{Accords}
  :SEASON:      %^{Season|Spring|Summer|Fall|Winter}
  :OCCASION:    %^{Occasion|Casual|Formal|Evening|Office}
  :RATING:      %^{Rating|10|9|8|7|6|5|4|3|2|1}
  :WHERE_BOUGHT: %^{Where Bought}
  :END:
  \n")

which would then result in entries like this:

* My Fragrance Collection
** Barénia
<2025-03-16>
:PROPERTIES:
:BRAND:       Hermès
:PARFUMER:    Christine Nagel
:TYPE:        Eau de Parfum
:TOP_NOTES:   Miracle Berry, Bergamont
:MIDDLE_NOTES: White Ginger Lily
:BASE_NOTES:  Patchouli, Akigalawood, Oak
:ACCORDS:     Woody, Patchouli, Warm Spicy, Oud, Fruity, Floral, Citrus, Sweet, Earthy, Fresh Spicy
:SEASON:      Spring, Summer
:OCCASION:    Casual
:RATING:      10
:WHERE_BOUGHT: Hermès Paris Sèvres
:END:

for each of my parfumes.

Another Thing I Like Besides Fragrances And Emacs: Statistics

Now that we have a capture template and the logged the first few fragrances, we want to define a few helpful functions to make running statistics on this easier. I'm reusing a function here that I use for my booktracking statistics that roughly counts over properties:

#+NAME: my-count-org-property-values
#+BEGIN_SRC elisp :tangle yes
(defun my-count-org-property-values (property)
  (let ((notes-count (make-hash-table :test 'equal)))
    (org-map-entries
     (lambda ()
       (let ((notes (org-entry-get (point) property)))
         (when notes
           (dolist (note (split-string notes ", *"))
             (puthash note (1+ (gethash note notes-count 0)) notes-count))))))
    (let ((sorted-notes
           (sort (mapcar (lambda (k) (list k (gethash k notes-count 0)))
                         (hash-table-keys notes-count))
                 (lambda (a b) (> (cadr a) (cadr b))))))
      (cons (list property "Count") sorted-notes))))
#+END_SRC

As an example: This allows us to compile a list on whom our favorite parfumers are:

#+BEGIN_SRC elisp :noweb yes :results value
<<my-count-org-property-values>>
(my-count-org-property-values "PARFUMER")
#+END_SRC

#+RESULTS:
| PARFUMER             | Count |
| Christine Nagel      |     3 |
| Philippe Romano      |     1 |
| Ane Ayo              |     1 |
| Christian Dussoulier |     1 |
| Alberto Morillas     |     1 |
| Maurice Roucel       |     1 |
| Beatrice Piquet      |     1 |
| Alain Astori         |     1 |
| Jean-Claude Ellena   |     1 |
| Ralf Schwieger       |     1 |
| Nathalie Feisthauer  |     1 |
| François Demachy     |     1 |
| Calice Becker        |     1 |

or what accords we like:

#+BEGIN_SRC elisp :noweb yes :results value
<<my-count-org-property-values>>
(my-count-org-property-values "ACCORDS")
#+END_SRC

#+RESULTS:
| ACCORDS      | Count |
| Woody        |    10 |
| Citrus       |     7 |
| Sweet        |     6 |
| Fresh        |     6 |
| Aromatic     |     6 |
| Powdery      |     6 |
| Musky        |     6 |
| Floral       |     5 |
| Amber        |     5 |
| Warm Spicy   |     4 |
| Green        |     4 |
| Fruity       |     3 |
| Earthy       |     3 |
| Fresh Spicy  |     3 |
| Vanilla      |     3 |
| Balsamic     |     3 |
| Aquatic      |     3 |
| Rose         |     2 |
| Smoky        |     2 |
| White Floral |     2 |

even though this seems to give away that woody and citrus notes are present in many fragrances, as they seem to be some of the most common base notes.

I also wanted to see which brands rate the highest for me... even though I think there would be a smarter way and more terse way to do this:

#+BEGIN_SRC emacs-lisp :results value table :exports both
(defun wilko/fragrance-brand-ratings (key value)
  (let ((brand-ratings (make-hash-table :test 'equal)))
    (org-map-entries
     (lambda ()
       (when-let* ((brand (org-entry-get (point) key))
                   (rating (org-entry-get (point) value)))
         (cl-destructuring-bind (sum count) (gethash brand brand-ratings '(0 0))
           (puthash brand (list (+ sum (string-to-number rating)) (1+ count)) brand-ratings)))))

    (cons (list key "Average Rating")
          (sort (mapcar (lambda (brand)
                          (cl-destructuring-bind (sum count) (gethash brand brand-ratings)
                            (list brand (format "%.2f" (/ (float sum) count)))))
                        (hash-table-keys brand-ratings))
                (lambda (a b) (string< (car a) (car b)))))))

(wilko/fragrance-brand-ratings "BRAND" "RATING")
#+end_src

#+RESULTS:
| BRAND                   | Average Rating |
| Acca Kappa              |           9.00 |
| Dior                    |           9.00 |
| Essential Parfums Paris |           9.00 |
| Hermès                  |           9.80 |
| Jil Sander              |           8.00 |
| Kenzo                   |           8.50 |
| Nautica                 |           7.00 |

Agenda

I decided to add my fragrances note to org-agenda-files, this not only allows me to see the date where the fragrance was added, which in most cases is approx. the purchase date, so I can keep track of when I buy parfumes:

Friday     14 March 2025
  20250315T200825--my-fragrances__fragrances_personal:The Musc    :fragrances:personal::
  20250315T200825--my-fragrances__fragrances_personal:Sakura Tokyo :fragrances:personal::

it also opens up the possibility of introducing agenda commands to run specific filters:

(setq org-agenda-custom-commands '(("l" "Top-Rated Scents" tags "+RATING>9")))

which would compile me a list of all fragrances I rated 9/10 out of 10:

20250315T200825--my-fragrances__fragrances_personal:L'Ombre Des Merveilles :fragrances:personal::
20250315T200825--my-fragrances__fragrances_personal:Eau des Merveilles :fragrances:personal::
20250315T200825--my-fragrances__fragrances_personal:Barénia     :fragrances:personal::
20250315T200825--my-fragrances__fragrances_personal:Terre d'Hermès :fragrances:personal::

I consider adding another capture template for samplings/potential future fragrances to buy in the future and would probably also want to toy with the idea of tracking when and to what occasion I wore which fragrance in my journal entries and run statistics against that. I think this was an evening well spent and way more fun than creating that Parfumo account.