#!/usr/bin/env python3 """ Pobiera dane z Google Ads API + GA4 za wskazany miesiąc i zapisuje jako JSON. Użycie: python scripts/reports/fetch_monthly_report_data.py --customer studio-zoe.pl --month 2026-02 python scripts/reports/fetch_monthly_report_data.py --customer 3871661050 --month 2026-02 --output output/report.json """ import argparse import calendar import csv import json import os import re import sys import io import tomllib from datetime import datetime, timedelta from pathlib import Path from urllib.parse import parse_qs, urlparse sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") sys.path.insert(0, str(Path(__file__).parent.parent)) from lib.gads_client import get_client, get_customer_id, run_query import requests ROOT = Path(__file__).parent.parent.parent sys.path.insert(0, str(ROOT)) from src.gads_v2.config import load_env load_env(ROOT / ".env") def load_client_report_config(domain): """Load scalar report settings for a client from config/clients.toml.""" config_path = ROOT / "config" / "clients.toml" if not config_path.exists(): return {} data = tomllib.loads(config_path.read_text(encoding="utf-8")) return data.get("clients", {}).get(domain, {}) def parse_month(month_str): """Parse YYYY-MM to (year, month) and calculate date range.""" year, month = map(int, month_str.split("-")) last_day = calendar.monthrange(year, month)[1] start = f"{year}-{month:02d}-01" end = f"{year}-{month:02d}-{last_day:02d}" return year, month, start, end def prev_month(year, month): """Calculate previous month's date range.""" if month == 1: py, pm = year - 1, 12 else: py, pm = year, month - 1 last_day = calendar.monthrange(py, pm)[1] start = f"{py}-{pm:02d}-01" end = f"{py}-{pm:02d}-{last_day:02d}" return py, pm, start, end def pct_change(current, previous): """Calculate percentage change, handling zero division.""" if previous == 0: return 100.0 if current > 0 else 0.0 return round(((current - previous) / previous) * 100, 1) def normalize_header(value): value = (value or "").strip().lower() replacements = { "ą": "a", "ć": "c", "ę": "e", "ł": "l", "ń": "n", "ó": "o", "ś": "s", "ź": "z", "ż": "z", } for src, dst in replacements.items(): value = value.replace(src, dst) return re.sub(r"[^a-z0-9]+", "", value) def parse_money(value): text = str(value or "").strip() if not text: return 0.0 text = text.replace("PLN", "").replace("zl", "").replace("zł", "") text = text.replace("\u00a0", " ").replace(" ", "") if "," in text and "." in text: text = text.replace(".", "").replace(",", ".") elif "," in text: text = text.replace(",", ".") text = re.sub(r"[^0-9.\-]", "", text) return round(float(text), 2) if text else 0.0 def parse_int_value(value): return int(round(parse_money(value))) def parse_history_month(value): text = str(value or "").strip() if not text: return "" if re.fullmatch(r"\d{4}-\d{2}", text): return text if re.fullmatch(r"\d{2}[.-]\d{4}", text): month, year = re.split(r"[.-]", text) return f"{int(year):04d}-{int(month):02d}" if re.fullmatch(r"\d{4}[./-]\d{1,2}[./-]\d{1,2}", text): year, month, _day = re.split(r"[./-]", text) return f"{int(year):04d}-{int(month):02d}" if re.fullmatch(r"\d{1,2}[./-]\d{1,2}[./-]\d{4}", text): _day, month, year = re.split(r"[./-]", text) return f"{int(year):04d}-{int(month):02d}" return text def month_locative(month): names = { "01": "styczniu", "02": "lutym", "03": "marcu", "04": "kwietniu", "05": "maju", "06": "czerwcu", "07": "lipcu", "08": "sierpniu", "09": "wrześniu", "10": "październiku", "11": "listopadzie", "12": "grudniu", } return names.get(str(month)[5:7], str(month)) def normalize_month_reference(text, month): """Keep SEO worksheet text aligned with the report month when row date matches.""" target = month_locative(month) return re.sub( r"^W miesiącu [A-Za-ząćęłńóśźżĄĆĘŁŃÓŚŹŻ]+", f"W {target}", text, count=1, ) def parse_sheet_config(sheet_config): value = str(sheet_config or "").strip() if not value: return "", "0" if value.startswith("http"): parsed = urlparse(value) match = re.search(r"/spreadsheets/d/([^/]+)", parsed.path) spreadsheet_id = match.group(1) if match else value query_gid = parse_qs(parsed.query).get("gid", [None])[0] fragment_gid = parse_qs(parsed.fragment).get("gid", [None])[0] return spreadsheet_id, query_gid or fragment_gid or "" if ":" in value: return value.split(":", 1) return value, "" def fetch_sales_history_from_sheet(domain, sheet_config): """Fetch monthly sales history from a public Google Sheet CSV export.""" spreadsheet_id, gid = parse_sheet_config(sheet_config) export_url = f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}/gviz/tq?tqx=out:csv" if gid: export_url += f"&gid={gid}" response = requests.get(export_url, timeout=30) response.raise_for_status() response.encoding = "utf-8" reader = csv.DictReader(io.StringIO(response.text)) history = [] for row in reader: normalized = {normalize_header(key): value for key, value in row.items()} month = parse_history_month( normalized.get("month") or normalized.get("miesiac") or normalized.get("data") or normalized.get("date") ) revenue = parse_money( normalized.get("revenue") or normalized.get("przychod") or normalized.get("przychody") or normalized.get("sprzedaz") or normalized.get("wartosc") ) transactions = parse_int_value( normalized.get("transactions") or normalized.get("transakcje") or normalized.get("zamowienia") or normalized.get("orders") ) if not month or not revenue: continue aov = parse_money( normalized.get("aov") or normalized.get("sredniakoszyka") or normalized.get("sredniawartosckoszyka") or normalized.get("sredniawartosczamowienia") ) if not aov and transactions: aov = round(revenue / transactions, 2) history.append({ "month": month, "transactions": transactions, "revenue": revenue, "aov": aov, "source": "google_sheet", }) return sorted(history, key=lambda item: item["month"]) def fetch_seo_text_rows_from_sheet(sheet_config, month): """Fetch monthly SEO text rows from a public Google Sheet CSV export.""" spreadsheet_id, gid = parse_sheet_config(sheet_config) export_url = f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}/gviz/tq?tqx=out:csv" if gid: export_url += f"&gid={gid}" response = requests.get(export_url, timeout=30) response.raise_for_status() response.encoding = "utf-8" reader = csv.DictReader(io.StringIO(response.text)) rows = [] for row in reader: normalized = {normalize_header(key): value for key, value in row.items()} row_month = parse_history_month( normalized.get("month") or normalized.get("miesiac") or normalized.get("data") or normalized.get("date") ) text = ( normalized.get("url") or normalized.get("link") or normalized.get("tekst") or normalized.get("opis") or normalized.get("dzialanie") or normalized.get("dzialania") or "" ).strip() if row_month == month and text: rows.append({ "date": row.get("Data") or row.get("data") or row_month, "url": normalize_month_reference(text, month), }) return rows def apply_sheet_ecommerce(report, sales_history, month, previous_month): """Use Google Sheet sales data for e-commerce KPI cards.""" by_month = {row["month"]: row for row in sales_history} current = by_month.get(month) if not current: return False previous = by_month.get(previous_month, {"transactions": 0, "revenue": 0.0, "aov": 0.0}) has_previous = previous_month in by_month ecommerce = (report.get("ga4") or {}).get("ecommerce") or {} ecommerce["current"] = { "transactions": current.get("transactions", 0), "revenue": current.get("revenue", 0.0), "aov": current.get("aov", 0.0), } ecommerce["previous"] = { "transactions": previous.get("transactions", 0), "revenue": previous.get("revenue", 0.0), "aov": previous.get("aov", 0.0), } ecommerce["mom_change"] = { "transactions_pct": pct_change(ecommerce["current"]["transactions"], ecommerce["previous"]["transactions"]) if has_previous else None, "revenue_pct": pct_change(ecommerce["current"]["revenue"], ecommerce["previous"]["revenue"]) if has_previous else None, "aov_pct": pct_change(ecommerce["current"]["aov"], ecommerce["previous"]["aov"]) if has_previous else None, } ecommerce["source"] = "google_sheet" if report.get("ga4") is None: report["ga4"] = {} report["ga4"]["ecommerce"] = ecommerce return True def fetch_google_ads_data(client, customer_id, start_date, end_date): """Fetch campaign metrics for a date range.""" query = f""" SELECT campaign.id, campaign.name, campaign.status, campaign.advertising_channel_type, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.conversions_value, metrics.ctr, metrics.average_cpc FROM campaign WHERE segments.date BETWEEN '{start_date}' AND '{end_date}' AND campaign.status != 'REMOVED' """ rows = run_query(client, customer_id, query) campaigns = {} for r in rows: cid = str(r.campaign.id) if cid not in campaigns: campaigns[cid] = { "id": cid, "name": r.campaign.name, "status": r.campaign.status.name, "type": r.campaign.advertising_channel_type.name, "impressions": 0, "clicks": 0, "cost": 0.0, "conversions": 0.0, "conversion_value": 0.0, "ctr": 0.0, "cpc": 0.0, } c = campaigns[cid] c["impressions"] += r.metrics.impressions c["clicks"] += r.metrics.clicks c["cost"] += r.metrics.cost_micros / 1_000_000 c["conversions"] += r.metrics.conversions c["conversion_value"] += r.metrics.conversions_value # Calculate derived metrics for c in campaigns.values(): c["cost"] = round(c["cost"], 2) c["conversions"] = round(c["conversions"], 1) c["conversion_value"] = round(c["conversion_value"], 2) c["ctr"] = round((c["clicks"] / c["impressions"] * 100) if c["impressions"] else 0, 2) c["cpc"] = round((c["cost"] / c["clicks"]) if c["clicks"] else 0, 2) c["cpa"] = round((c["cost"] / c["conversions"]) if c["conversions"] else 0, 2) c["roas"] = round((c["conversion_value"] / c["cost"]) if c["cost"] else 0, 2) return list(campaigns.values()) def calc_totals(campaigns): """Sum up totals across campaigns.""" t = {"impressions": 0, "clicks": 0, "cost": 0.0, "conversions": 0.0, "conversion_value": 0.0} for c in campaigns: t["impressions"] += c["impressions"] t["clicks"] += c["clicks"] t["cost"] += c["cost"] t["conversions"] += c["conversions"] t["conversion_value"] += c.get("conversion_value", 0.0) t["cost"] = round(t["cost"], 2) t["conversions"] = round(t["conversions"], 1) t["conversion_value"] = round(t["conversion_value"], 2) t["ctr"] = round((t["clicks"] / t["impressions"] * 100) if t["impressions"] else 0, 2) t["cpc"] = round((t["cost"] / t["clicks"]) if t["clicks"] else 0, 2) t["cpa"] = round((t["cost"] / t["conversions"]) if t["conversions"] else 0, 2) t["roas"] = round((t["conversion_value"] / t["cost"]) if t["cost"] else 0, 2) return t def fetch_daily_data(client, customer_id, start_date, end_date): """Fetch daily breakdown for charts.""" query = f""" SELECT segments.date, metrics.impressions, metrics.clicks, metrics.cost_micros FROM campaign WHERE segments.date BETWEEN '{start_date}' AND '{end_date}' AND campaign.status != 'REMOVED' """ rows = run_query(client, customer_id, query) daily = {} for r in rows: d = r.segments.date if d not in daily: daily[d] = {"date": d, "impressions": 0, "clicks": 0, "cost": 0.0} daily[d]["impressions"] += r.metrics.impressions daily[d]["clicks"] += r.metrics.clicks daily[d]["cost"] += r.metrics.cost_micros / 1_000_000 result = sorted(daily.values(), key=lambda x: x["date"]) for d in result: d["cost"] = round(d["cost"], 2) return result def fetch_search_terms(client, customer_id, start_date, end_date, limit=15): """Fetch top search terms by clicks.""" query = f""" SELECT search_term_view.search_term, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions FROM search_term_view WHERE segments.date BETWEEN '{start_date}' AND '{end_date}' ORDER BY metrics.clicks DESC LIMIT {limit} """ rows = run_query(client, customer_id, query) terms = [] for r in rows: clicks = r.metrics.clicks impressions = r.metrics.impressions terms.append({ "term": r.search_term_view.search_term, "impressions": impressions, "clicks": clicks, "cost": round(r.metrics.cost_micros / 1_000_000, 2), "conversions": round(r.metrics.conversions, 1), "ctr": round((clicks / impressions * 100) if impressions else 0, 2), }) return terms def fetch_ga4_data(property_id, start_date, end_date, prev_start, prev_end): """Fetch GA4 data: sessions, users, traffic sources, devices.""" from google.oauth2.credentials import Credentials from google.analytics.data_v1beta import BetaAnalyticsDataClient from google.analytics.data_v1beta.types import ( RunReportRequest, DateRange, Metric, Dimension, OrderBy, ) credentials = Credentials( token=None, refresh_token=os.environ["GA4_REFRESH_TOKEN"], client_id=os.environ["GOOGLE_ADS_OAUTH2_CLIENT_ID"], client_secret=os.environ["GOOGLE_ADS_OAUTH2_CLIENT_SECRET"], token_uri="https://oauth2.googleapis.com/token", ) client = BetaAnalyticsDataClient(credentials=credentials) prop = f"properties/{property_id}" # 1. Sessions & Users (current + previous month) def get_totals(sd, ed): resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=sd, end_date=ed)], metrics=[ Metric(name="sessions"), Metric(name="totalUsers"), Metric(name="newUsers"), Metric(name="screenPageViews"), Metric(name="averageSessionDuration"), Metric(name="bounceRate"), ], )) row = resp.rows[0] if resp.rows else None if not row: return {"sessions": 0, "users": 0, "new_users": 0, "pageviews": 0, "avg_duration": 0, "bounce_rate": 0} return { "sessions": int(row.metric_values[0].value), "users": int(row.metric_values[1].value), "new_users": int(row.metric_values[2].value), "pageviews": int(row.metric_values[3].value), "avg_duration": round(float(row.metric_values[4].value), 1), "bounce_rate": round(float(row.metric_values[5].value) * 100, 1), } current = get_totals(start_date, end_date) previous = get_totals(prev_start, prev_end) # 2. Traffic sources resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=start_date, end_date=end_date)], dimensions=[Dimension(name="sessionSourceMedium")], metrics=[Metric(name="sessions")], order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="sessions"), desc=True)], limit=10, )) sources = [] for row in resp.rows: sources.append({ "source_medium": row.dimension_values[0].value, "sessions": int(row.metric_values[0].value), }) # 3. Devices resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=start_date, end_date=end_date)], dimensions=[Dimension(name="deviceCategory")], metrics=[Metric(name="sessions")], order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="sessions"), desc=True)], )) devices = [] for row in resp.rows: devices.append({ "device": row.dimension_values[0].value, "sessions": int(row.metric_values[0].value), }) # 4. Daily sessions (for chart) resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=start_date, end_date=end_date)], dimensions=[Dimension(name="date")], metrics=[Metric(name="sessions"), Metric(name="totalUsers")], order_bys=[OrderBy(dimension=OrderBy.DimensionOrderBy(dimension_name="date"))], )) daily_sessions = [] for row in resp.rows: raw = row.dimension_values[0].value formatted = f"{raw[:4]}-{raw[4:6]}-{raw[6:]}" daily_sessions.append({ "date": formatted, "sessions": int(row.metric_values[0].value), "users": int(row.metric_values[1].value), }) return { "current": current, "previous": previous, "mom_change": { "sessions_pct": pct_change(current["sessions"], previous["sessions"]), "users_pct": pct_change(current["users"], previous["users"]), "new_users_pct": pct_change(current["new_users"], previous["new_users"]), "pageviews_pct": pct_change(current["pageviews"], previous["pageviews"]), "avg_duration_pct": pct_change(current["avg_duration"], previous["avg_duration"]), "bounce_rate_pct": pct_change(current["bounce_rate"], previous["bounce_rate"]), }, "sources": sources, "devices": devices, "daily": daily_sessions, } def fetch_ga4_ecommerce(property_id, start_date, end_date, prev_start, prev_end): """Fetch GA4 e-commerce data: transactions, revenue, AOV.""" from google.oauth2.credentials import Credentials from google.analytics.data_v1beta import BetaAnalyticsDataClient from google.analytics.data_v1beta.types import ( RunReportRequest, DateRange, Metric, Dimension, OrderBy, ) credentials = Credentials( token=None, refresh_token=os.environ["GA4_REFRESH_TOKEN"], client_id=os.environ["GOOGLE_ADS_OAUTH2_CLIENT_ID"], client_secret=os.environ["GOOGLE_ADS_OAUTH2_CLIENT_SECRET"], token_uri="https://oauth2.googleapis.com/token", ) client = BetaAnalyticsDataClient(credentials=credentials) prop = f"properties/{property_id}" def get_ecom(sd, ed): resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=sd, end_date=ed)], metrics=[ Metric(name="transactions"), Metric(name="purchaseRevenue"), Metric(name="averagePurchaseRevenue"), ], )) row = resp.rows[0] if resp.rows else None if not row: return {"transactions": 0, "revenue": 0.0, "aov": 0.0} return { "transactions": int(row.metric_values[0].value), "revenue": round(float(row.metric_values[1].value), 2), "aov": round(float(row.metric_values[2].value), 2), } current = get_ecom(start_date, end_date) previous = get_ecom(prev_start, prev_end) # Daily revenue chart resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=start_date, end_date=end_date)], dimensions=[Dimension(name="date")], metrics=[Metric(name="purchaseRevenue"), Metric(name="transactions")], order_bys=[OrderBy(dimension=OrderBy.DimensionOrderBy(dimension_name="date"))], )) daily_revenue = [] for row in resp.rows: raw = row.dimension_values[0].value formatted = f"{raw[:4]}-{raw[4:6]}-{raw[6:]}" daily_revenue.append({ "date": formatted, "revenue": round(float(row.metric_values[0].value), 2), "transactions": int(row.metric_values[1].value), }) # Revenue by source resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=start_date, end_date=end_date)], dimensions=[Dimension(name="sessionSourceMedium")], metrics=[Metric(name="purchaseRevenue"), Metric(name="transactions")], order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="purchaseRevenue"), desc=True)], limit=10, )) revenue_by_source = [] for row in resp.rows: revenue_by_source.append({ "source_medium": row.dimension_values[0].value, "revenue": round(float(row.metric_values[0].value), 2), "transactions": int(row.metric_values[1].value), }) # Top products by revenue resp = client.run_report(RunReportRequest( property=prop, date_ranges=[DateRange(start_date=start_date, end_date=end_date)], dimensions=[Dimension(name="itemName")], metrics=[ Metric(name="itemRevenue"), Metric(name="itemsPurchased"), ], order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="itemRevenue"), desc=True)], limit=10, )) top_products = [] for row in resp.rows: top_products.append({ "name": row.dimension_values[0].value, "revenue": round(float(row.metric_values[0].value), 2), "quantity": int(row.metric_values[1].value), }) return { "current": current, "previous": previous, "mom_change": { "transactions_pct": pct_change(current["transactions"], previous["transactions"]), "revenue_pct": pct_change(current["revenue"], previous["revenue"]), "aov_pct": pct_change(current["aov"], previous["aov"]), }, "daily": daily_revenue, "revenue_by_source": revenue_by_source, "top_products": top_products, } def main(): parser = argparse.ArgumentParser(description="Pobierz dane do raportu miesięcznego") parser.add_argument("--customer", required=True, help="Domena lub Google Ads customer ID") parser.add_argument("--month", required=True, help="Miesiąc raportu (YYYY-MM)") parser.add_argument("--output", help="Ścieżka do pliku JSON") parser.add_argument("--ga4-property", help="GA4 Property ID (domyślnie z .env)") parser.add_argument("--skip-ga4", action="store_true", help="Pomiń dane GA4") args = parser.parse_args() customer_id = get_customer_id(args.customer) client = get_client(use_proto_plus=True) year, month, start_date, end_date = parse_month(args.month) py, pm, prev_start, prev_end = prev_month(year, month) month_names_pl = { 1: "Styczeń", 2: "Luty", 3: "Marzec", 4: "Kwiecień", 5: "Maj", 6: "Czerwiec", 7: "Lipiec", 8: "Sierpień", 9: "Wrzesień", 10: "Październik", 11: "Listopad", 12: "Grudzień", } # Resolve domain name for output domain = args.customer if not args.customer.replace("-", "").isdigit() else args.customer print(f"Pobieram dane Google Ads: {domain} za {args.month}...") # Google Ads data campaigns = fetch_google_ads_data(client, customer_id, start_date, end_date) prev_campaigns = fetch_google_ads_data(client, customer_id, prev_start, prev_end) totals = calc_totals(campaigns) prev_totals = calc_totals(prev_campaigns) daily = fetch_daily_data(client, customer_id, start_date, end_date) search_terms = fetch_search_terms(client, customer_id, start_date, end_date) mom_change = { "impressions_pct": pct_change(totals["impressions"], prev_totals["impressions"]), "clicks_pct": pct_change(totals["clicks"], prev_totals["clicks"]), "cost_pct": pct_change(totals["cost"], prev_totals["cost"]), "conversions_pct": pct_change(totals["conversions"], prev_totals["conversions"]), "conversion_value_pct": pct_change(totals["conversion_value"], prev_totals["conversion_value"]), "ctr_pct": pct_change(totals["ctr"], prev_totals["ctr"]), "cpc_pct": pct_change(totals["cpc"], prev_totals["cpc"]), "cpa_pct": pct_change(totals["cpa"], prev_totals["cpa"]), "roas_pct": pct_change(totals["roas"], prev_totals["roas"]), } report = { "client": domain, "month": args.month, "month_name": month_names_pl[month], "year": year, "prev_month": f"{py}-{pm:02d}", "prev_month_name": month_names_pl[pm], "generated_at": datetime.now().isoformat(), "google_ads": { "campaigns": campaigns, "totals": totals, "prev_totals": prev_totals, "mom_change": mom_change, "daily": daily, "search_terms": search_terms, }, } # GA4 data if not args.skip_ga4: ga4_property = args.ga4_property if not ga4_property: # Try to find GA4 property in .env env_key = f"GA4_PROPERTY_ID_{domain}" ga4_property = os.environ.get(env_key) if ga4_property: print(f"Pobieram dane GA4 (property: {ga4_property})...") try: ga4 = fetch_ga4_data(ga4_property, start_date, end_date, prev_start, prev_end) report["ga4"] = ga4 print(f" GA4: {ga4['current']['sessions']} sesji, {ga4['current']['users']} uzytkownikow") except Exception as e: print(f" UWAGA: Blad GA4: {e}") report["ga4"] = None else: print(f" Brak GA4 Property ID w .env ({env_key}) - pomijam GA4") report["ga4"] = None else: report["ga4"] = None # Semstorm SEO data semstorm_login = os.environ.get("SEMSTORM_LOGIN", "") if semstorm_login: print(f"Pobieram dane Semstorm...") try: sys.path.insert(0, str(Path(__file__).parent)) from fetch_semstorm_data import fetch_domain_stats semstorm = fetch_domain_stats(domain, args.month) report["semstorm"] = semstorm if semstorm and semstorm.get("current"): cur = semstorm["current"] print(f" Semstorm: TOP3={cur['top3']}, TOP10={cur['top10']}, TOP50={cur['top50']}, traffic={cur['traffic']}") except Exception as e: print(f" UWAGA: Blad Semstorm: {e}") report["semstorm"] = None else: report["semstorm"] = None # E-commerce data: Shoper (primary) or GA4 (fallback) shoper_key = f"SHOPER_API_URL_{domain}" if os.environ.get(shoper_key): print(f"Pobieram dane e-commerce ze Shoper...") try: from fetch_shoper_data import fetch_shoper_ecommerce shoper_ecom = fetch_shoper_ecommerce(domain, args.month, f"{py}-{pm:02d}") if shoper_ecom and shoper_ecom["current"]["transactions"] > 0: # Get revenue_by_source and top_products from GA4 if report.get("ga4") and ga4_property: try: ga4_ecom = fetch_ga4_ecommerce(ga4_property, start_date, end_date, prev_start, prev_end) if ga4_ecom: shoper_ecom["revenue_by_source"] = ga4_ecom.get("revenue_by_source", []) shoper_ecom["top_products"] = ga4_ecom.get("top_products", []) except Exception as e: print(f" UWAGA: GA4 revenue_by_source/top_products: {e}") shoper_ecom["revenue_by_source"] = [] shoper_ecom["top_products"] = [] if report.get("ga4") is None: report["ga4"] = {} report["ga4"]["ecommerce"] = shoper_ecom cur = shoper_ecom["current"] print(f" Shoper: {cur['transactions']} zamówień, {cur['revenue']:.2f} PLN, AOV {cur['aov']:.2f} PLN") else: if report.get("ga4"): report["ga4"]["ecommerce"] = None except Exception as e: print(f" UWAGA: Blad Shoper: {e}") if report.get("ga4"): report["ga4"]["ecommerce"] = None elif report.get("ga4") and ga4_property: print(f"Pobieram dane GA4 e-commerce...") try: ecom = fetch_ga4_ecommerce(ga4_property, start_date, end_date, prev_start, prev_end) if ecom and ecom["current"]["transactions"] > 0: report["ga4"]["ecommerce"] = ecom cur = ecom["current"] print(f" E-commerce (GA4): {cur['transactions']} transakcji, {cur['revenue']:.2f} PLN przychodu") else: report["ga4"]["ecommerce"] = None except Exception as e: print(f" UWAGA: Blad GA4 e-commerce: {e}") if report.get("ga4"): report["ga4"]["ecommerce"] = None # Monthly sales history for chart. Prefer client Google Sheet when configured. client_report_config = load_client_report_config(domain) sales_history_sheet = client_report_config.get("sales_history_sheet") or os.environ.get(f"GSHEET_SALES_HISTORY_{domain}") report_start = os.environ.get(f"REPORT_START_DATE_{domain}") if sales_history_sheet: try: sales_history = fetch_sales_history_from_sheet(domain, sales_history_sheet) if apply_sheet_ecommerce(report, sales_history, args.month, f"{py}-{pm:02d}"): current_sheet = report["ga4"]["ecommerce"]["current"] print( f" E-commerce (Google Sheet): {current_sheet['transactions']} transakcji, " f"{current_sheet['revenue']:.2f} PLN przychodu" ) filtered = [e for e in sales_history if not report_start or e["month"] >= report_start] report["sales_history"] = filtered print(f" Historia sprzedaży z Google Sheet: {len(filtered)} miesięcy") except Exception as e: report["sales_history"] = [] print(f" UWAGA: Nie udalo sie pobrac historii sprzedazy z Google Sheet: {e}") else: ecom_data = report.get("ga4", {}).get("ecommerce") if report.get("ga4") else None if ecom_data and ecom_data.get("current", {}).get("transactions", 0) > 0: history_path = ROOT / "clients" / domain / "sales_history.json" history_path.parent.mkdir(parents=True, exist_ok=True) sales_history = [] if history_path.exists(): with open(history_path, "r", encoding="utf-8") as f: sales_history = json.load(f) cur_entry = { "month": args.month, "transactions": ecom_data["current"]["transactions"], "revenue": ecom_data["current"]["revenue"], "aov": ecom_data["current"]["aov"], "source": ecom_data.get("source", "ga4"), } by_month = {e["month"]: e for e in sales_history} by_month[args.month] = cur_entry sales_history = sorted(by_month.values(), key=lambda x: x["month"]) with open(history_path, "w", encoding="utf-8") as f: json.dump(sales_history, f, indent=2, ensure_ascii=False) filtered = [e for e in sales_history if not report_start or e["month"] >= report_start] report["sales_history"] = filtered print(f" Historia sprzedaży: {len(filtered)} miesięcy zapisanych") else: report["sales_history"] = [] # SEO links from Google Sheets seo_links_key = f"GSHEET_SEO_LINKS_{domain}" if os.environ.get(seo_links_key): print(f"Pobieram linki SEO...") try: from fetch_seo_links import fetch_seo_links, fetch_seo_activities seo_links = fetch_seo_links(domain, args.month) report["seo_links"] = seo_links or [] print(f" Linki SEO: {len(report['seo_links'])} w {args.month}") # SEO activities (text box) seo_act_key = f"GSHEET_SEO_ACTIVITIES_{domain}" if os.environ.get(seo_act_key): seo_activities = fetch_seo_activities(domain, args.month) report["seo_activities"] = seo_activities if seo_activities: print(f" Działania SEO: {len(seo_activities)} znaków") except Exception as e: print(f" UWAGA: Blad SEO links: {e}") report["seo_links"] = [] else: report["seo_links"] = [] # SEO works and links from config/clients.toml override the legacy .env-only fetch above. seo_works_sheet = client_report_config.get("seo_works_history_sheet") if seo_works_sheet: print(f"Pobieram dzialania SEO z config/clients.toml...") try: seo_work_rows = fetch_seo_text_rows_from_sheet(seo_works_sheet, args.month) report["seo_activities"] = "\n\n".join(row["url"] for row in seo_work_rows) print(f" Dzialania SEO: {len(seo_work_rows)} wpisow w {args.month}") except Exception as e: print(f" UWAGA: Blad dzialan SEO: {e}") report["seo_activities"] = "" seo_links_sheet = client_report_config.get("seo_links_history_sheet") if seo_links_sheet: print(f"Pobieram linki SEO z config/clients.toml...") try: seo_links = fetch_seo_text_rows_from_sheet(seo_links_sheet, args.month) report["seo_links"] = seo_links or [] print(f" Linki SEO: {len(report['seo_links'])} w {args.month}") except Exception as e: print(f" UWAGA: Blad SEO links: {e}") report["seo_links"] = [] # Output if args.output: output_path = Path(args.output) else: output_path = ROOT / "scripts" / "reports" / "output" / f"{domain}_{args.month}.json" output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=2) print(f"\nZapisano: {output_path}") print(f"Google Ads: {totals['clicks']} klikniec, {totals['conversions']} konwersji, {totals['cost']:.2f} PLN") if __name__ == "__main__": main()