ML-155: Compact collection summary and add stats preamble
This commit is contained in:
+26
@@ -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 -->
|
||||
@@ -197,7 +197,8 @@ defmodule MusicLibrary.Collection do
|
||||
|> MapSet.new()
|
||||
end
|
||||
|
||||
@max_genres_per_record 3
|
||||
@max_genres_per_record 2
|
||||
@stats_limit 10
|
||||
|
||||
@spec collection_summary() :: {String.t(), non_neg_integer()}
|
||||
def collection_summary do
|
||||
@@ -215,15 +216,92 @@ defmodule MusicLibrary.Collection do
|
||||
|> Enum.map(fn {_id, group} -> format_group(group) end)
|
||||
|> 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)}
|
||||
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
|
||||
record = hd(records)
|
||||
artist_names = Record.artist_names(record)
|
||||
formats = records |> Enum.map(& &1.format) |> Enum.uniq() |> Enum.join("/")
|
||||
year = extract_year(record.release_date)
|
||||
|
||||
genres =
|
||||
records
|
||||
@@ -231,8 +309,7 @@ defmodule MusicLibrary.Collection do
|
||||
|> Enum.uniq()
|
||||
|> Enum.take(@max_genres_per_record)
|
||||
|
||||
base =
|
||||
"#{artist_names} - #{record.title} (#{record.release_date || "Unknown"}, #{formats}, #{record.type})"
|
||||
base = "#{artist_names} - #{record.title} (#{year}, #{formats})"
|
||||
|
||||
if genres == [] do
|
||||
base
|
||||
@@ -241,6 +318,25 @@ defmodule MusicLibrary.Collection do
|
||||
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
|
||||
from r in SearchIndex,
|
||||
where: not is_nil(r.purchased_at)
|
||||
|
||||
@@ -372,7 +372,7 @@ defmodule MusicLibrary.CollectionTest do
|
||||
assert Collection.collection_summary() == {"", 0}
|
||||
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", %{
|
||||
title: "OK Computer",
|
||||
format: :cd,
|
||||
@@ -392,20 +392,28 @@ defmodule MusicLibrary.CollectionTest do
|
||||
})
|
||||
|
||||
{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 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 ->
|
||||
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"
|
||||
end)
|
||||
|
||||
assert Enum.any?(lines, fn line ->
|
||||
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"
|
||||
end)
|
||||
end
|
||||
@@ -434,7 +442,8 @@ defmodule MusicLibrary.CollectionTest do
|
||||
})
|
||||
|
||||
{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 length(lines) == 1
|
||||
@@ -444,7 +453,7 @@ defmodule MusicLibrary.CollectionTest do
|
||||
assert line =~ "vinyl"
|
||||
end
|
||||
|
||||
test "caps genres to 3 per record" do
|
||||
test "caps genres to 2 per record" do
|
||||
record_with_artist("ABC", %{
|
||||
title: "The Lexicon of Love",
|
||||
format: :vinyl,
|
||||
@@ -455,10 +464,11 @@ defmodule MusicLibrary.CollectionTest do
|
||||
})
|
||||
|
||||
{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()
|
||||
|
||||
assert genre_count == 3
|
||||
assert genre_count == 2
|
||||
end
|
||||
|
||||
test "omits genre brackets when genres are empty" do
|
||||
@@ -472,8 +482,9 @@ defmodule MusicLibrary.CollectionTest do
|
||||
})
|
||||
|
||||
{summary, _count} = Collection.collection_summary()
|
||||
refute summary =~ "["
|
||||
refute summary =~ "]"
|
||||
catalog = String.split(summary, "\n\n") |> List.last()
|
||||
refute catalog =~ "["
|
||||
refute catalog =~ "]"
|
||||
end
|
||||
|
||||
test "excludes wishlist records" do
|
||||
@@ -489,8 +500,11 @@ defmodule MusicLibrary.CollectionTest do
|
||||
|
||||
{summary, record_count} = Collection.collection_summary()
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user