Grocy自定义报表制作:家庭消费数据分析指南

Grocy自定义报表制作:家庭消费数据分析指南

【免费下载链接】grocy ERP beyond your fridge - Grocy is a web-based self-hosted groceries & household management solution for your home 【免费下载链接】grocy 项目地址: https://gitcode.com/GitHub_Trending/gr/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

关键表关系图

mermaid

消费数据流程分析

Grocy中的消费数据主要通过以下流程产生:

mermaid

当你购买商品并录入Grocy系统时,会触发一系列数据更新:

  1. stock表记录当前库存状态,包括购买价格和日期
  2. stock_log表记录每一笔库存变动,包括购买、消耗、过期等交易类型
  3. product_barcodes表更新该商品的最近购买价格
  4. 这些数据共同构成了消费分析的基础

Grocy API实战:获取消费数据

Grocy提供了强大的REST API,可以方便地获取各类数据,为自定义报表提供数据源。

API认证与基础设置

在使用Grocy API前,需要先获取API密钥:

  1. 登录Grocy网页界面
  2. 进入"设置" > "管理API密钥"
  3. 创建新的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. 自定义报表自动化

为了让报表自动更新,可以使用以下方法:

  1. 创建定时任务:使用cron或任务计划程序定期运行数据提取脚本
  2. 生成静态HTML:将报表结果保存为HTML文件,通过Web服务器提供访问
  3. 设置数据缓存:避免频繁查询数据库,提高报表加载速度
# 示例:每月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与智能家居设备集成,实现自动化家庭管理。

【免费下载链接】grocy ERP beyond your fridge - Grocy is a web-based self-hosted groceries & household management solution for your home 【免费下载链接】grocy 项目地址: https://gitcode.com/GitHub_Trending/gr/grocy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值