Spaces:
Runtime error
Runtime error
wen Qiu commited on
Commit ·
48fbfa9
1
Parent(s): ec533d1
fix: prevent duplicate data when Export table and platform sheets overlap
Browse filesRoot cause: _load_from_google_sheets was reading both the Export sheet
(which contains all platforms) AND individual platform sheets (Meta,
Google, Criteo, Bing written by API sync), then concatenating them.
This caused campaigns present in both sources to be double-counted,
inflating Cost/Revenue figures (e.g. US Conversion showing $407K
instead of the correct $307K).
Fix: When a platform has its own API-synced sheet, exclude that
platform's rows from the Export sheet before merging. The API sheet
data takes priority as it's more current and accurately formatted.
Made-with: Cursor
- src/loader.py +32 -10
src/loader.py
CHANGED
|
@@ -104,7 +104,6 @@ def _load_from_google_sheets(config: dict[str, Any]) -> pd.DataFrame | None:
|
|
| 104 |
gc = gspread.authorize(creds)
|
| 105 |
sh = gc.open_by_url(url)
|
| 106 |
|
| 107 |
-
all_dfs = []
|
| 108 |
platform_sheets = gs_cfg.get("platform_sheets", ["Meta", "Google", "Criteo", "Bing"])
|
| 109 |
main_sheet = gs_cfg.get("worksheet", "Export")
|
| 110 |
|
|
@@ -117,14 +116,14 @@ def _load_from_google_sheets(config: dict[str, Any]) -> pd.DataFrame | None:
|
|
| 117 |
col_cfg.get("date_idx", 4): "date",
|
| 118 |
}
|
| 119 |
|
| 120 |
-
|
| 121 |
try:
|
| 122 |
-
ws = sh.worksheet(
|
| 123 |
records = ws.get_all_values()
|
| 124 |
if len(records) < 2:
|
| 125 |
-
|
| 126 |
header = records[0]
|
| 127 |
-
seen = {}
|
| 128 |
unique_header = []
|
| 129 |
for col in header:
|
| 130 |
if col in seen:
|
|
@@ -139,17 +138,40 @@ def _load_from_google_sheets(config: dict[str, Any]) -> pd.DataFrame | None:
|
|
| 139 |
if idx < len(sdf.columns):
|
| 140 |
rename[sdf.columns[idx]] = name
|
| 141 |
sdf = sdf.rename(columns=rename)
|
| 142 |
-
log.info(f"从 Sheet [{
|
| 143 |
-
|
| 144 |
except Exception:
|
| 145 |
-
|
| 146 |
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
log.warning("Google Sheets 所有工作表均无数据")
|
| 149 |
return None
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
df = pd.concat(all_dfs, ignore_index=True)
|
| 152 |
-
log.info(f"从 Google Sheets 共读取 {len(df)} 行数据")
|
| 153 |
return df
|
| 154 |
|
| 155 |
except Exception as e:
|
|
|
|
| 104 |
gc = gspread.authorize(creds)
|
| 105 |
sh = gc.open_by_url(url)
|
| 106 |
|
|
|
|
| 107 |
platform_sheets = gs_cfg.get("platform_sheets", ["Meta", "Google", "Criteo", "Bing"])
|
| 108 |
main_sheet = gs_cfg.get("worksheet", "Export")
|
| 109 |
|
|
|
|
| 116 |
col_cfg.get("date_idx", 4): "date",
|
| 117 |
}
|
| 118 |
|
| 119 |
+
def _read_one_sheet(ws_name: str) -> pd.DataFrame | None:
|
| 120 |
try:
|
| 121 |
+
ws = sh.worksheet(ws_name)
|
| 122 |
records = ws.get_all_values()
|
| 123 |
if len(records) < 2:
|
| 124 |
+
return None
|
| 125 |
header = records[0]
|
| 126 |
+
seen: dict[str, int] = {}
|
| 127 |
unique_header = []
|
| 128 |
for col in header:
|
| 129 |
if col in seen:
|
|
|
|
| 138 |
if idx < len(sdf.columns):
|
| 139 |
rename[sdf.columns[idx]] = name
|
| 140 |
sdf = sdf.rename(columns=rename)
|
| 141 |
+
log.info(f"从 Sheet [{ws_name}] 读取 {len(sdf)} 行")
|
| 142 |
+
return sdf
|
| 143 |
except Exception:
|
| 144 |
+
return None
|
| 145 |
|
| 146 |
+
main_df = _read_one_sheet(main_sheet)
|
| 147 |
+
|
| 148 |
+
api_platform_dfs: dict[str, pd.DataFrame] = {}
|
| 149 |
+
for ps in platform_sheets:
|
| 150 |
+
ps_df = _read_one_sheet(ps)
|
| 151 |
+
if ps_df is not None and not ps_df.empty:
|
| 152 |
+
api_platform_dfs[ps] = ps_df
|
| 153 |
+
|
| 154 |
+
if main_df is None and not api_platform_dfs:
|
| 155 |
log.warning("Google Sheets 所有工作表均无数据")
|
| 156 |
return None
|
| 157 |
|
| 158 |
+
if main_df is not None and api_platform_dfs:
|
| 159 |
+
platforms_with_api_data = set(api_platform_dfs.keys())
|
| 160 |
+
if "platform" in main_df.columns:
|
| 161 |
+
main_keep = main_df[~main_df["platform"].astype(str).str.strip().isin(platforms_with_api_data)]
|
| 162 |
+
main_drop_count = len(main_df) - len(main_keep)
|
| 163 |
+
if main_drop_count:
|
| 164 |
+
log.info(f"Export 表中排除 {main_drop_count} 行已有分平台 API 数据的平台({', '.join(platforms_with_api_data)}),避免重复")
|
| 165 |
+
all_dfs = [main_keep] + list(api_platform_dfs.values())
|
| 166 |
+
else:
|
| 167 |
+
all_dfs = [main_df] + list(api_platform_dfs.values())
|
| 168 |
+
elif main_df is not None:
|
| 169 |
+
all_dfs = [main_df]
|
| 170 |
+
else:
|
| 171 |
+
all_dfs = list(api_platform_dfs.values())
|
| 172 |
+
|
| 173 |
df = pd.concat(all_dfs, ignore_index=True)
|
| 174 |
+
log.info(f"从 Google Sheets 共读取 {len(df)} 行数据(去重后)")
|
| 175 |
return df
|
| 176 |
|
| 177 |
except Exception as e:
|