ML-155: Compact collection summary and add stats preamble

This commit is contained in:
Claudio Ortolina
2026-05-01 22:51:45 +01:00
parent 179ac35130
commit 0b78f24fed
3 changed files with 152 additions and 16 deletions
@@ -0,0 +1,26 @@
---
id: ML-155
title: Compact collection summary format + statistical preamble for chat context
status: Done
assignee: []
created_date: '2026-05-01 21:43'
updated_date: '2026-05-01 21:49'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce the token count of collection_summary/0 by:
1. Compacting format_group/1 (year-only dates, remove type field, reduce max genres 3→2)
2. Adding statistical preamble (genre/formats/decade distribution) computed inline from fetched records
3. Updating tests for the new format
<!-- SECTION:DESCRIPTION:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Compacted collection_summary format_group/1: year-only dates, removed type field, reduced max genres 3→2. Added statistical preamble (genre/formats/eras distribution + artist count) computed in-memory from already-fetched records. ~27% token reduction (~26.4k → ~19.4k for 1200 records). All 886 tests pass.
<!-- SECTION:FINAL_SUMMARY:END -->
+100 -4
View File
@@ -197,7 +197,8 @@ defmodule MusicLibrary.Collection do
|> MapSet.new() |> MapSet.new()
end end
@max_genres_per_record 3 @max_genres_per_record 2
@stats_limit 10
@spec collection_summary() :: {String.t(), non_neg_integer()} @spec collection_summary() :: {String.t(), non_neg_integer()}
def collection_summary do def collection_summary do
@@ -215,15 +216,92 @@ defmodule MusicLibrary.Collection do
|> Enum.map(fn {_id, group} -> format_group(group) end) |> Enum.map(fn {_id, group} -> format_group(group) end)
|> Enum.sort() |> Enum.sort()
summary = Enum.join(groups, "\n") catalog = Enum.join(groups, "\n")
stats = build_stats(records, length(groups))
summary =
cond do
stats == "" and catalog == "" -> ""
stats == "" -> catalog
catalog == "" -> stats
true -> stats <> "\n\n" <> catalog
end
{summary, length(groups)} {summary, length(groups)}
end end
defp build_stats([], _group_count), do: ""
defp build_stats(records, group_count) do
genre_counts = compute_genre_counts(records)
format_counts = compute_format_counts(records)
decade_counts = compute_decade_counts(records)
artist_count =
records
|> Enum.flat_map(& &1.artists)
|> Enum.uniq_by(& &1.musicbrainz_id)
|> length()
[
"# Stats: #{group_count} releases, #{artist_count} artists",
genres_line(genre_counts),
formats_line(format_counts),
eras_line(decade_counts)
]
|> Enum.join("\n")
end
defp compute_genre_counts(records) do
records
|> Enum.flat_map(&(&1.genres || []))
|> Enum.reject(&(&1 in @excluded_genres))
|> Enum.frequencies()
|> Enum.sort_by(&elem(&1, 1), :desc)
|> Enum.take(@stats_limit)
end
defp compute_format_counts(records) do
records
|> Enum.map(& &1.format)
|> Enum.frequencies()
|> Enum.sort_by(&elem(&1, 1), :desc)
end
defp compute_decade_counts(records) do
records
|> Enum.map(&extract_decade(&1.release_date))
|> Enum.reject(&is_nil/1)
|> Enum.frequencies()
|> Enum.sort_by(&elem(&1, 1), :desc)
end
defp genres_line([]), do: ""
defp genres_line(counts) do
items = Enum.map(counts, fn {genre, count} -> "#{genre} #{count}" end)
"Genres: " <> Enum.join(items, ", ")
end
defp formats_line([]), do: ""
defp formats_line(counts) do
items = Enum.map(counts, fn {format, count} -> "#{format} #{count}" end)
"Formats: " <> Enum.join(items, ", ")
end
defp eras_line([]), do: ""
defp eras_line(counts) do
items = Enum.map(counts, fn {decade, count} -> "#{decade} #{count}" end)
"Eras: " <> Enum.join(items, ", ")
end
defp format_group(records) do defp format_group(records) do
record = hd(records) record = hd(records)
artist_names = Record.artist_names(record) artist_names = Record.artist_names(record)
formats = records |> Enum.map(& &1.format) |> Enum.uniq() |> Enum.join("/") formats = records |> Enum.map(& &1.format) |> Enum.uniq() |> Enum.join("/")
year = extract_year(record.release_date)
genres = genres =
records records
@@ -231,8 +309,7 @@ defmodule MusicLibrary.Collection do
|> Enum.uniq() |> Enum.uniq()
|> Enum.take(@max_genres_per_record) |> Enum.take(@max_genres_per_record)
base = base = "#{artist_names} - #{record.title} (#{year}, #{formats})"
"#{artist_names} - #{record.title} (#{record.release_date || "Unknown"}, #{formats}, #{record.type})"
if genres == [] do if genres == [] do
base base
@@ -241,6 +318,25 @@ defmodule MusicLibrary.Collection do
end end
end end
defp extract_year(nil), do: "Unknown"
defp extract_year(date_str) when is_binary(date_str) do
if String.length(date_str) >= 4 do
String.slice(date_str, 0, 4)
else
date_str
end
end
defp extract_decade(nil), do: nil
defp extract_decade(date_str) when is_binary(date_str) do
case Integer.parse(String.slice(date_str, 0, 4)) do
{year, _} -> "#{div(year, 10) * 10}s"
:error -> nil
end
end
defp base_search do defp base_search do
from r in SearchIndex, from r in SearchIndex,
where: not is_nil(r.purchased_at) where: not is_nil(r.purchased_at)
+26 -12
View File
@@ -372,7 +372,7 @@ defmodule MusicLibrary.CollectionTest do
assert Collection.collection_summary() == {"", 0} assert Collection.collection_summary() == {"", 0}
end end
test "returns one line per collected record with record count" do test "returns stats preamble and one catalog line per collected record" do
record_with_artist("Radiohead", %{ record_with_artist("Radiohead", %{
title: "OK Computer", title: "OK Computer",
format: :cd, format: :cd,
@@ -392,20 +392,28 @@ defmodule MusicLibrary.CollectionTest do
}) })
{summary, record_count} = Collection.collection_summary() {summary, record_count} = Collection.collection_summary()
lines = String.split(summary, "\n") [stats, catalog] = String.split(summary, "\n\n")
lines = String.split(catalog, "\n")
assert record_count == 2 assert record_count == 2
assert length(lines) == 2 assert length(lines) == 2
# Stats section
assert stats =~ "# Stats: 2 releases"
assert stats =~ "Genres:"
assert stats =~ "Formats:"
assert stats =~ "Eras:"
# Catalog lines use year-only dates and exclude type field
assert Enum.any?(lines, fn line -> assert Enum.any?(lines, fn line ->
line =~ "Radiohead" and line =~ "OK Computer" and line =~ "Radiohead" and line =~ "OK Computer" and
line =~ "1997-06-16" and line =~ "cd" and line =~ "album" and line =~ "(1997, cd)" and
line =~ "alternative rock, art rock" line =~ "alternative rock, art rock"
end) end)
assert Enum.any?(lines, fn line -> assert Enum.any?(lines, fn line ->
line =~ "Pink Floyd" and line =~ "The Dark Side of the Moon" and line =~ "Pink Floyd" and line =~ "The Dark Side of the Moon" and
line =~ "1973-03-01" and line =~ "vinyl" and line =~ "album" and line =~ "(1973, vinyl)" and
line =~ "progressive rock" line =~ "progressive rock"
end) end)
end end
@@ -434,7 +442,8 @@ defmodule MusicLibrary.CollectionTest do
}) })
{summary, record_count} = Collection.collection_summary() {summary, record_count} = Collection.collection_summary()
lines = String.split(summary, "\n") catalog = String.split(summary, "\n\n") |> List.last()
lines = String.split(catalog, "\n")
assert record_count == 1 assert record_count == 1
assert length(lines) == 1 assert length(lines) == 1
@@ -444,7 +453,7 @@ defmodule MusicLibrary.CollectionTest do
assert line =~ "vinyl" assert line =~ "vinyl"
end end
test "caps genres to 3 per record" do test "caps genres to 2 per record" do
record_with_artist("ABC", %{ record_with_artist("ABC", %{
title: "The Lexicon of Love", title: "The Lexicon of Love",
format: :vinyl, format: :vinyl,
@@ -455,10 +464,11 @@ defmodule MusicLibrary.CollectionTest do
}) })
{summary, _count} = Collection.collection_summary() {summary, _count} = Collection.collection_summary()
[genres_str] = Regex.run(~r/\[(.+)\]/, summary, capture: :all_but_first) catalog = String.split(summary, "\n\n") |> List.last()
[genres_str] = Regex.run(~r/\[(.+)\]/, catalog, capture: :all_but_first)
genre_count = genres_str |> String.split(", ") |> length() genre_count = genres_str |> String.split(", ") |> length()
assert genre_count == 3 assert genre_count == 2
end end
test "omits genre brackets when genres are empty" do test "omits genre brackets when genres are empty" do
@@ -472,8 +482,9 @@ defmodule MusicLibrary.CollectionTest do
}) })
{summary, _count} = Collection.collection_summary() {summary, _count} = Collection.collection_summary()
refute summary =~ "[" catalog = String.split(summary, "\n\n") |> List.last()
refute summary =~ "]" refute catalog =~ "["
refute catalog =~ "]"
end end
test "excludes wishlist records" do test "excludes wishlist records" do
@@ -489,8 +500,11 @@ defmodule MusicLibrary.CollectionTest do
{summary, record_count} = Collection.collection_summary() {summary, record_count} = Collection.collection_summary()
assert record_count == 1 assert record_count == 1
assert summary =~ "OK Computer"
refute summary =~ "Wish You Were Here" # Catalog portion should contain the purchased record but not the wishlist one
catalog = String.split(summary, "\n\n") |> List.last()
assert catalog =~ "OK Computer"
refute catalog =~ "Wish You Were Here"
end end
end end
end end