Grocy自定义报表制作:家庭消费数据分析指南
引言:家庭消费数据的痛点与解决方案
你是否还在为家庭每月的消费支出模糊不清而烦恼?是否想知道钱究竟花在了哪些地方,却苦于缺乏有效的数据分析工具?Grocy作为一款强大的自托管家庭管理系统,不仅能帮助你追踪库存和购物清单,还能通过自定义报表功能,让你轻松掌握家庭消费的每一笔明细。本文将详细介绍如何利用Grocy的API和数据库结构,制作个性化的家庭消费数据分析报表,让你的每一分钱都花得明明白白。
读完本文,你将能够:
- 理解Grocy的核心数据结构,特别是与消费相关的表和字段
- 使用Grocy API获取原始数据,进行自定义分析
- 编写SQL查询,生成各类消费统计报表
- 利用用户自定义字段扩展数据维度,满足个性化分析需求
- 通过实际案例,掌握从数据采集到报表可视化的完整流程
Grocy数据结构解析:消费分析的基石
要制作自定义报表,首先需要深入了解Grocy的数据库结构。通过分析Grocy的迁移文件,我们可以梳理出与消费分析相关的核心表结构。
核心表结构概览
以下是进行家庭消费分析时最常用的几个表:
| 表名 | 主要用途 | 关键字段 |
|---|---|---|
| products | 存储产品基本信息 | id, name, product_group_id, price |
| product_groups | 产品类别分组 | id, name |
| stock | 当前库存信息 | product_id, amount, price, purchased_date |
| stock_log | 库存变动记录 | product_id, amount, price, transaction_type, purchased_date |
| product_barcodes | 产品条形码信息 | product_id, barcode, last_price, shopping_location_id |
| shopping_locations | 购物地点信息 | id, name |
| userfields | 用户自定义字段定义 | id, entity, name, type |
| userfield_values | 用户自定义字段值 | field_id, object_id, value |
关键表关系图
消费数据流程分析
Grocy中的消费数据主要通过以下流程产生:
当你购买商品并录入Grocy系统时,会触发一系列数据更新:
stock表记录当前库存状态,包括购买价格和日期stock_log表记录每一笔库存变动,包括购买、消耗、过期等交易类型product_barcodes表更新该商品的最近购买价格- 这些数据共同构成了消费分析的基础
Grocy API实战:获取消费数据
Grocy提供了强大的REST API,可以方便地获取各类数据,为自定义报表提供数据源。
API认证与基础设置
在使用Grocy API前,需要先获取API密钥:
- 登录Grocy网页界面
- 进入"设置" > "管理API密钥"
- 创建新的API密钥,记录下来备用
所有API请求需要在HTTP头中包含认证信息:
GROCY-API-KEY: your_api_key_here
核心API端点详解
获取产品列表
GET /api/objects/products
响应示例:
[
{
"id": 1,
"name": "牛奶",
"product_group_id": 5,
"location_id": 1,
"qu_id_purchase": 2,
"qu_id_stock": 2,
"qu_factor_purchase_to_stock": 1,
"price": 5.50
},
// 更多产品...
]
获取库存变动记录
GET /api/objects/stock_log?query=transaction_type:Purchase
响应示例:
[
{
"id": 100,
"product_id": 1,
"amount": 2,
"price": 5.50,
"purchased_date": "2023-06-15",
"transaction_type": "purchase",
"shopping_location_id": 3
},
// 更多记录...
]
获取产品价格历史
虽然Grocy没有直接提供products_price_history表的API端点,但可以通过组合调用获取类似数据:
// 使用JavaScript获取产品价格历史示例
async function getProductPriceHistory(productId) {
const stockLogs = await fetch(`/api/objects/stock_log?query=product_id:${productId} AND transaction_type:purchase`);
const barcodes = await fetch(`/api/objects/product_barcodes?query=product_id:${productId}`);
return {
stockLogs: await stockLogs.json(),
lastPrice: barcodes.length > 0 ? barcodes[0].last_price : null
};
}
API调用示例:按月获取消费数据
以下是一个使用Python调用Grocy API,获取特定月份消费数据的示例:
import requests
import json
from datetime import datetime, timedelta
API_KEY = "your_api_key_here"
BASE_URL = "http://your-grocy-instance/api"
headers = {
"GROCY-API-KEY": API_KEY,
"Content-Type": "application/json"
}
# 获取上个月的消费数据
end_date = datetime.now().replace(day=1) - timedelta(days=1)
start_date = end_date.replace(day=1)
params = {
"start_date": start_date.strftime("%Y-%m-%d"),
"end_date": end_date.strftime("%Y-%m-%d"),
"group-by": "productgroup"
}
response = requests.get(f"{BASE_URL}/stockreports/spendings", headers=headers, params=params)
spending_data = response.json()
print(json.dumps(spending_data, indent=2))
SQL查询进阶:定制化数据分析
对于更复杂的分析需求,直接查询Grocy数据库可以提供更大的灵活性。以下是一些常用的SQL查询示例,帮助你从不同维度分析家庭消费数据。
1. 按产品类别统计月度消费
SELECT
pg.name AS product_group,
p.name AS product,
SUM(sl.amount * sl.price) AS total_spent,
COUNT(DISTINCT DATE(sl.purchased_date)) AS purchase_days,
SUM(sl.amount) AS total_quantity
FROM stock_log sl
JOIN products p ON sl.product_id = p.id
LEFT JOIN product_groups pg ON p.product_group_id = pg.id
WHERE sl.transaction_type = 'purchase'
AND sl.purchased_date BETWEEN '2023-01-01' AND '2023-01-31'
GROUP BY pg.id, p.id
ORDER BY total_spent DESC;
2. 分析购物地点消费对比
SELECT
sl.shopping_location_id,
sl.name AS store_name,
SUM(sl.amount * sl.price) AS total_spent,
COUNT(DISTINCT sl.product_id) AS unique_products,
AVG(sl.amount * sl.price) AS avg_purchase_value
FROM (
SELECT
sl.*,
shp.name
FROM stock_log sl
LEFT JOIN shopping_locations shp ON sl.shopping_location_id = shp.id
WHERE sl.transaction_type = 'purchase'
AND sl.purchased_date >= DATE('now', '-3 months')
) sl
GROUP BY sl.shopping_location_id
ORDER BY total_spent DESC;
3. 识别价格波动较大的产品
SELECT
p.id,
p.name,
MIN(sl.price) AS min_price,
MAX(sl.price) AS max_price,
AVG(sl.price) AS avg_price,
(MAX(sl.price) - MIN(sl.price))/AVG(sl.price)*100 AS price_fluctuation,
COUNT(DISTINCT sl.purchased_date) AS purchase_count
FROM stock_log sl
JOIN products p ON sl.product_id = p.id
WHERE sl.transaction_type = 'purchase'
AND sl.purchased_date >= DATE('now', '-6 months')
AND sl.price > 0
GROUP BY p.id
HAVING purchase_count >= 3
ORDER BY price_fluctuation DESC
LIMIT 10;
4. 使用用户自定义字段进行高级分析
如果创建了"品牌"和"有机"等用户自定义字段,可以这样查询:
SELECT
uv_brand.value AS brand,
uv_organic.value AS is_organic,
SUM(sl.amount * sl.price) AS total_spent,
COUNT(DISTINCT p.id) AS product_count
FROM stock_log sl
JOIN products p ON sl.product_id = p.id
LEFT JOIN userfield_values uv_brand ON p.id = uv_brand.object_id
AND uv_brand.field_id = (SELECT id FROM userfields WHERE entity = 'products' AND name = 'brand')
LEFT JOIN userfield_values uv_organic ON p.id = uv_organic.object_id
AND uv_organic.field_id = (SELECT id FROM userfields WHERE entity = 'products' AND name = 'organic')
WHERE sl.transaction_type = 'purchase'
AND sl.purchased_date BETWEEN '2023-01-01' AND '2023-06-30'
GROUP BY uv_brand.value, uv_organic.value
ORDER BY total_spent DESC;
自定义报表实战:从数据到可视化
有了数据基础,我们可以开始构建自定义报表。下面介绍几种常见的报表类型及其实现方法。
1. 月度消费趋势报表
使用Chart.js创建月度消费趋势图:
<!DOCTYPE html>
<html>
<head>
<title>家庭月度消费趋势</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
</head>
<body>
<canvas id="spendingTrendChart" width="800" height="400"></canvas>
<script>
// 假设从API获取的数据
const monthlyData = [
{ month: "2023-01", total: 1250.50, groceries: 850.75, household: 400.25 },
{ month: "2023-02", total: 1180.20, groceries: 790.30, household: 389.90 },
{ month: "2023-03", total: 1320.80, groceries: 920.50, household: 400.30 },
{ month: "2023-04", total: 1290.30, groceries: 890.10, household: 400.20 },
{ month: "2023-05", total: 1350.75, groceries: 950.25, household: 400.50 },
{ month: "2023-06", total: 1420.90, groceries: 1000.70, household: 420.20 }
];
const ctx = document.getElementById('spendingTrendChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(item => item.month),
datasets: [
{
label: '总消费',
data: monthlyData.map(item => item.total),
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
},
{
label: '食品杂货',
data: monthlyData.map(item => item.groceries),
borderColor: 'rgb(54, 162, 235)',
tension: 0.1
},
{
label: '家庭用品',
data: monthlyData.map(item => item.household),
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}
]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: '家庭月度消费趋势'
},
tooltip: {
mode: 'index',
intersect: false,
},
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '消费金额 (元)'
}
}
}
}
});
</script>
</body>
</html>
2. 产品类别消费占比饼图
// 使用D3.js创建产品类别消费占比饼图
async function createCategoryPieChart() {
// 获取数据
const response = await fetch('/api/stockreports/spendings?group-by=productgroup&start_date=2023-01-01&end_date=2023-06-30');
const data = await response.json();
// 处理数据
const chartData = data.metrics.map(item => ({
category: item.name,
value: item.total
}));
// 创建SVG
const svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 600)
.append("g")
.attr("transform", "translate(300, 300)");
// 创建颜色比例尺
const color = d3.scaleOrdinal()
.domain(chartData.map(d => d.category))
.range(d3.quantize(d3.interpolateRainbow, chartData.length));
// 创建饼图布局
const pie = d3.pie()
.value(d => d.value);
// 添加弧生成器
const arc = d3.arc()
.innerRadius(0)
.outerRadius(200);
// 添加标签弧生成器
const labelArc = d3.arc()
.innerRadius(210)
.outerRadius(210);
// 绘制饼图
const arcs = svg.selectAll("arc")
.data(pie(chartData))
.enter();
arcs.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.category))
.attr("stroke", "white")
.style("stroke-width", "2px");
// 添加标签
arcs.append("text")
.attr("transform", d => `translate(${labelArc.centroid(d)})`)
.text(d => d.data.category)
.style("text-anchor", "middle");
// 添加图例
const legend = d3.select("body").append("div")
.attr("class", "legend")
.style("display", "flex")
.style("flex-direction", "column")
.style("position", "absolute")
.style("left", "620px")
.style("top", "20px");
chartData.forEach(item => {
const legendItem = legend.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("margin", "5px 0");
legendItem.append("div")
.style("width", "15px")
.style("height", "15px")
.style("background-color", color(item.category))
.style("margin-right", "10px");
legendItem.append("span")
.text(`${item.category}: ¥${item.value.toFixed(2)}`);
});
}
3. 自定义报表自动化
为了让报表自动更新,可以使用以下方法:
- 创建定时任务:使用cron或任务计划程序定期运行数据提取脚本
- 生成静态HTML:将报表结果保存为HTML文件,通过Web服务器提供访问
- 设置数据缓存:避免频繁查询数据库,提高报表加载速度
# 示例:每月1日运行数据提取脚本的cron任务
0 0 1 * * /usr/bin/python3 /path/to/your/script.py >> /var/log/grocy_report.log 2>&1
高级技巧与最佳实践
1. 数据清洗与预处理
在进行数据分析前,确保数据质量:
- 处理缺失值:使用平均价格或最近价格填充
- 标准化单位:确保同类产品使用相同的计量单位
- 去除异常值:识别并处理明显不合理的价格或数量
def clean_price_data(prices):
"""清洗价格数据,处理异常值"""
if not prices:
return []
# 计算四分位数
sorted_prices = sorted(prices)
q1 = sorted_prices[int(len(sorted_prices) * 0.25)]
q3 = sorted_prices[int(len(sorted_prices) * 0.75)]
iqr = q3 - q1
# 定义异常值边界
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
# 过滤异常值
cleaned = [p for p in prices if lower_bound <= p <= upper_bound]
# 如果过滤后数据过少,使用原始数据
return cleaned if len(cleaned) >= len(prices) * 0.5 else prices
2. 性能优化策略
- 创建视图:为常用查询创建数据库视图,提高查询速度
- 索引优化:为频繁过滤和排序的字段添加索引
- 数据聚合:预计算常用统计数据,避免重复计算
-- 创建消费分析视图示例
CREATE VIEW vw_monthly_spending_summary AS
SELECT
strftime('%Y-%m', sl.purchased_date) AS month,
pg.id AS product_group_id,
pg.name AS product_group,
SUM(sl.amount * sl.price) AS total_spent,
COUNT(DISTINCT sl.id) AS transaction_count,
SUM(sl.amount) AS total_quantity
FROM stock_log sl
JOIN products p ON sl.product_id = p.id
LEFT JOIN product_groups pg ON p.product_group_id = pg.id
WHERE sl.transaction_type = 'purchase'
GROUP BY month, pg.id;
3. 数据安全与隐私保护
- 限制API访问权限,仅授予必要权限
- 定期轮换API密钥
- 避免在报表中包含敏感信息
- 考虑对家庭消费数据进行加密存储
结语:从数据到决策
通过Grocy的自定义报表功能,你不仅可以清晰地了解家庭消费模式,还能基于数据做出更明智的购物决策。无论是识别价格波动、优化购物地点选择,还是控制特定类别的支出,数据分析都能为你提供有力支持。
随着使用的深入,你可以不断扩展报表功能,例如:
- 添加预算管理功能,设置消费预警
- 分析季节性消费模式,提前规划大额支出
- 结合菜谱功能,分析食材成本与浪费情况
希望本文提供的指南能帮助你充分利用Grocy的强大功能,让家庭消费管理变得更加科学、高效。开始你的数据分析之旅吧,让每一分钱都花在刀刃上!
如果你觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多关于Grocy高级使用技巧的内容。下期我们将介绍如何利用Grocy API与智能家居设备集成,实现自动化家庭管理。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



