J.A.R.V.I.S-rust/resources/commands/expenses/breakdown.lua
Bossiara13 87bb186e94
Some checks are pending
Rust CI / cargo test (jarvis-core) (push) Waiting to run
Rust CI / cargo clippy (push) Waiting to run
Rust CI / cargo check (workspace) (push) Waiting to run
feat: Wave 10 — expense tracker pack (voice-driven)
resources/commands/expenses/ (4 commands):
- expenses.log     "потратил 500 на еду"     → append entry
- expenses.today   "сколько потратил сегодня" → today's total
- expenses.week    "сколько на неделе"       → 7-day rolling total
- expenses.breakdown "куда ушли деньги"      → top-5 categories

Storage: `jarvis.state` (auto-namespaced per-pack JSON). Wire format is
compact "ts1,amount1,cat1|ts2,amount2,cat2" so we don't need a JSON parser
inside Lua.

Phrase parsing:
- Trigger stripped via jarvis.text.strip_trigger.
- First number captured; "12,50" comma decimals accepted.
- Category extracted from "на <слово>" (RU) or "for/on <word>" (EN);
  defaults to "разное" / "misc".

Time math:
- Day boundary: local-calendar midnight via os.time({year,month,day,...}).
- Week boundary: rolling 7×86400 seconds.

Pack count: 98 → 99 functional.
2026-05-16 14:57:58 +03:00

55 lines
1.7 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Per-category breakdown for the last 7 days, sorted by amount desc.
local lang = jarvis.context.language
local raw_state = jarvis.state.get("entries") or ""
if raw_state == "" then
return jarvis.cmd.not_found(lang == "ru"
and "Нечего разбивать, сэр."
or "Nothing to break down, sir.")
end
local now = jarvis.context.time
local now_ts = os.time({
year = now.year, month = now.month, day = now.day,
hour = now.hour, min = now.minute, sec = now.second or 0,
})
local cutoff = now_ts - 7 * 86400
local by_cat = {}
for chunk in raw_state:gmatch("([^|]+)") do
local ts, am, ca = chunk:match("^(%d+),([%-%d%.]+),(.+)$")
ts = tonumber(ts); am = tonumber(am)
if ts and am and ts >= cutoff then
by_cat[ca] = (by_cat[ca] or 0) + am
end
end
local rows = {}
for ca, total in pairs(by_cat) do
table.insert(rows, { ca = ca, total = total })
end
table.sort(rows, function(a, b) return a.total > b.total end)
if #rows == 0 then
return jarvis.cmd.ok(lang == "ru"
and "На этой неделе тишина по тратам, сэр."
or "Quiet week on spending, sir.")
end
-- Top 5 categories spoken; rest collapses into "и другое"
local top = math.min(5, #rows)
local parts = {}
for i = 1, top do
table.insert(parts, string.format("%s %.0f", rows[i].ca, rows[i].total))
end
local tail = ""
if #rows > top then
local rest_total = 0
for i = top + 1, #rows do rest_total = rest_total + rows[i].total end
tail = string.format(lang == "ru"
and " и другое %.0f" or " and other %.0f", rest_total)
end
return jarvis.cmd.ok(string.format(lang == "ru"
and "По категориям: %s%s."
or "By category: %s%s.", table.concat(parts, ", "), tail))