Wilson826 commited on
Commit
459fd65
·
verified ·
1 Parent(s): 3c13182

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -114
app.py CHANGED
@@ -67,8 +67,7 @@ MANUAL_FEE_TYPES = [
67
 
68
  # 归档时需要迁移的属性(按 Notion 字段类型分组)
69
  ARCHIVE_RICH_TEXT_FIELDS = [
70
- "Customer", "POD", "ETA", "ATA", "Service Level",
71
- "LM Carrier", "AR Completed Date", "AP Completed Date"
72
  ]
73
  ARCHIVE_NUMBER_FIELDS = [
74
  "Parcels", "HTS Count", "Entry Count", "CWT",
@@ -78,7 +77,15 @@ ARCHIVE_NUMBER_FIELDS = [
78
  "WT Fee", "CES Transfer Fee"
79
  ]
80
  ARCHIVE_CHECKBOX_FIELDS = ["AR", "AP"]
81
- ARCHIVE_DATE_FIELDS = ["WH-OUT Date"]
 
 
 
 
 
 
 
 
82
 
83
  # ================== 会话与重试配置 ==================
84
  session = requests.Session()
@@ -306,14 +313,17 @@ def extract_mawb_with_id_whout(page):
306
 
307
 
308
  def extract_mawb_with_id_ar_ap(page):
309
- """提取 MAWB 及页面 ID、AR/AP 状态"""
310
  title = page["properties"]["MAWB"]["title"]
311
  if title:
312
  mawb = title[0]["text"]["content"]
 
 
313
  return mawb, {
314
  "id": page["id"],
315
  "ar_checked": page["properties"]["AR"]["checkbox"],
316
  "ap_checked": page["properties"]["AP"]["checkbox"],
 
317
  }
318
  return None, None
319
 
@@ -385,14 +395,17 @@ def create_mawb_eta(file, logger=None):
385
  skipped += 1
386
  continue
387
 
388
- # -------- ETA 处理(文本写入)--------
389
- eta_text = None
 
 
390
  if pd.notna(row["Estimated Time Arrival"]):
391
  try:
392
  eta_dt = pd.to_datetime(row["Estimated Time Arrival"])
393
- eta_text = eta_dt.strftime("%Y/%m/%d %H:%M")
 
394
  except Exception:
395
- eta_text = None
396
 
397
  service_level = str(row["Service Level"]).strip()
398
  cbm = row["CBM"]
@@ -407,7 +420,7 @@ def create_mawb_eta(file, logger=None):
407
  "rich_text": [{"text": {"content": str(row["Customer"]).strip()}}]
408
  },
409
  "POD": {
410
- "rich_text": [{"text": {"content": str(row["Destination"]).strip()}}]
411
  },
412
  "Service Level": {
413
  "rich_text": [{"text": {"content": service_level}}]
@@ -422,10 +435,8 @@ def create_mawb_eta(file, logger=None):
422
  "AP": {"checkbox": False}
423
  }
424
 
425
- if eta_text:
426
- properties["ETA"] = {
427
- "rich_text": [{"text": {"content": eta_text}}]
428
- }
429
 
430
  # -------- HTS Count + Entry Count(仅 T01 服务等级)--------
431
  if service_level == "T01" and pd.notna(cbm):
@@ -486,77 +497,94 @@ def update_ar(file, logger=None):
486
  # 解析 Excel
487
  result = parse_ar_excel(file, logger)
488
  if isinstance(result, tuple) and len(result) == 2 and isinstance(result[1], str):
489
- # 返回错误信息
490
  return result[1]
491
 
492
  df, (mawb_col, hts_fee_col, total_col) = result
493
 
494
- # 查询 Notion
495
- logger.log("Querying the existing data in Notion...")
496
- existing_pages = query_all_pages(extract_mawb_with_ar)
497
- logger.log(f"Total of {len(existing_pages)} MAWBs in Notion")
 
 
 
 
 
 
498
 
499
- # 更新 AR
 
 
 
 
 
 
500
  updated, skipped, not_found = [], [], []
 
501
  total = len(df)
502
 
503
  for current, (_, row) in enumerate(df.iterrows(), 1):
504
  mawb = str(row[mawb_col]).strip()
505
- logger.log(f"Progress: {current}/{total} | Processing MAWB: {mawb}")
506
 
507
  if not mawb or mawb.lower() == 'nan':
508
  logger.log("Empty MAWB, skipped")
509
  continue
510
 
511
- if mawb not in existing_pages:
512
- logger.log("Not found in Notion")
513
- not_found.append(mawb)
514
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
- page_info = existing_pages[mawb]
517
- if page_info["ar_checked"]:
518
- logger.log("AR already checked, skip")
519
  skipped.append(mawb)
520
- continue
521
-
522
- # 构建更新属性
523
- properties = {}
524
- properties["AR"] = {"checkbox": True}
525
-
526
- # 获取当前时间(洛杉矶时区)
527
- la_tz = ZoneInfo("America/Los_Angeles")
528
- completed_time = datetime.now(la_tz).strftime("%Y/%m/%d %H:%M")
529
- properties["AR Completed Date"] = {"rich_text": [{"text": {"content": completed_time}}]}
530
 
531
- # 添加 Customs Clearance Fee(如果列存在)
532
- if hts_fee_col and pd.notna(row.get(hts_fee_col)):
533
- try:
534
- customs_fee = float(row[hts_fee_col])
535
- properties["Customs Clearance Fee"] = {"number": round(customs_fee, 2)}
536
- except (ValueError, TypeError):
537
- logger.log(f"Warning: Invalid HTS Processing Fee value, skipped")
538
 
539
- # 添加 AR amount(如果列存在)
540
- if total_col and pd.notna(row.get(total_col)):
541
- try:
542
- ar_amount = float(row[total_col])
543
- properties["AR amount"] = {"number": round(ar_amount, 2)}
544
- except (ValueError, TypeError):
545
- logger.log(f"Warning: Invalid Total value, skipped")
546
 
547
- r = notion_patch(page_info['id'], properties)
548
- if r.status_code == 200:
549
- log_parts = [f"AR checked successfully, AR Completed Date: {completed_time}"]
550
- if "Customs Clearance Fee" in properties:
551
- log_parts.append(f"Customs Clearance Fee: {properties['Customs Clearance Fee']['number']:.2f}")
552
- if "AR amount" in properties:
553
- log_parts.append(f"AR amount: {properties['AR amount']['number']:.2f}")
554
- logger.log(" | ".join(log_parts))
555
- updated.append(mawb)
556
- else:
557
- logger.log(f"Update failed: {r.text}")
558
 
559
- time.sleep(API_RATE_LIMIT_DELAY)
 
 
 
 
 
 
 
 
 
 
 
 
560
 
561
  logger.log("\n================ Process Completed ================")
562
  logger.log(f"AR checked: {len(updated)}")
@@ -666,17 +694,25 @@ def aggregate_ap_fees(df, columns, logger):
666
  return mawb_dict, extra_fee_records, jfk_mawbs
667
 
668
 
669
- def update_ap_in_notion(mawb_dict, extra_fee_records, existing_pages, isc_summary, jfk_mawbs, logger):
670
  """更新 AP 到 Notion"""
671
- updated_count = 0
672
  skipped_ap_mawbs = []
673
  not_found_mawbs = []
674
  total = len(mawb_dict)
675
 
676
  extra_fee_mawbs = {x.split(" - ")[0] for x in extra_fee_records}
677
 
 
 
 
 
 
 
 
 
 
678
  for current, (mawb, fees) in enumerate(mawb_dict.items(), 1):
679
- logger.log(f"Progress: {current}/{total} | Processing MAWB: {mawb}")
680
 
681
  # 跳过含有额外费用的 MAWB
682
  if mawb in extra_fee_mawbs:
@@ -684,20 +720,18 @@ def update_ap_in_notion(mawb_dict, extra_fee_records, existing_pages, isc_summar
684
  skipped_ap_mawbs.append(f"{mawb} - Extra fee present")
685
  continue
686
 
687
- # MAWB 不在 Notion 中,跳过
688
  if mawb not in existing_pages:
689
- logger.log("MAWB not found in Notion, skip")
690
- not_found_mawbs.append(mawb)
 
 
 
 
691
  continue
692
 
693
  page_info = existing_pages[mawb]
694
 
695
- # 跳过已勾选 AP 的记录
696
- if page_info["ap_checked"]:
697
- logger.log("AP already checked, skip")
698
- skipped_ap_mawbs.append(f"{mawb} - AP already checked")
699
- continue
700
-
701
  # ===== CWT 前置强校验 =====
702
  cwt_value = page_info.get("CWT")
703
 
@@ -743,11 +777,9 @@ def update_ap_in_notion(mawb_dict, extra_fee_records, existing_pages, isc_summar
743
  for col_name in ["ISC Fee", "BreakDown Fee", "Bypass Fee", "CES Inspection Fee", "CES Transfer Fee"]:
744
  properties[col_name] = {"number": round(fees.get(col_name, 0.0), 2)}
745
 
746
- # 写入 WT Fee
747
  wt_fee_value = round(fees.get("WT Fee", 0.0), 2)
748
  properties["WT Fee"] = {"number": wt_fee_value}
749
 
750
- # 计算 AP amount(所有费用总和)
751
  ap_amount = (
752
  fees.get("ISC Fee", 0.0) +
753
  fees.get("BreakDown Fee", 0.0) +
@@ -757,21 +789,34 @@ def update_ap_in_notion(mawb_dict, extra_fee_records, existing_pages, isc_summar
757
  wt_fee_value
758
  )
759
  properties["AP amount"] = {"number": round(ap_amount, 2)}
760
-
761
  properties["AP"] = {"checkbox": True}
762
- # 添加 AP Completed Date(洛杉矶时区)
763
- la_tz = ZoneInfo("America/Los_Angeles")
764
- completed_time = datetime.now(la_tz).strftime("%Y/%m/%d %H:%M")
765
- properties["AP Completed Date"] = {"rich_text": [{"text": {"content": completed_time}}]}
766
 
767
- r = notion_patch(page_info['id'], properties)
768
- if r.status_code == 200:
769
- logger.log(f"Update successful, AP checked, AP amount: {ap_amount:.2f}, AP Completed Date: {completed_time}")
770
- updated_count += 1
771
- else:
772
- logger.log(f"Update failed: {r.text}")
773
 
774
- time.sleep(API_RATE_LIMIT_DELAY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
 
776
  return updated_count, not_found_mawbs, skipped_ap_mawbs
777
 
@@ -798,14 +843,20 @@ def update_ap(file, isc_file, logger=None):
798
  else:
799
  logger.log("No JFK invoices found, ISC fee validation not required")
800
 
801
- # 查询 Notion
802
- logger.log("Querying Notion database...")
803
- existing_pages = query_all_pages(extract_mawb_with_ap_cwt)
804
- logger.log(f"Total {len(existing_pages)} MAWBs exist in Notion")
 
 
 
 
 
 
805
 
806
  # 更新 Notion
807
  updated_count, not_found_mawbs, skipped_ap_mawbs = update_ap_in_notion(
808
- mawb_dict, extra_fee_records, existing_pages, isc_summary, jfk_mawbs, logger
809
  )
810
 
811
  logger.log("\n================ Process Completed ================")
@@ -886,11 +937,12 @@ def update_cwt(file, logger=None):
886
  log_parts.append(f"CWT={weight:.2f}")
887
 
888
  # ATA 仅在 Excel 中有值时才写入(不受 AR/AP 影响)
889
- ata_str = None
890
  if pd.notna(ata):
891
- ata_str = ata.strftime("%Y/%m/%d %H:%M")
892
- properties["ATA"] = {"rich_text": [{"text": {"content": ata_str}}]}
893
- log_parts.append(f"ATA={ata_str}")
 
 
894
 
895
  # 没有任何属性需要更新,跳过
896
  if not properties:
@@ -993,28 +1045,23 @@ def archive_old_records(cutoff_date_str=None, logger=None):
993
  logger.log(f"Progress: {idx}/{total} | Processing MAWB: {mawb}")
994
 
995
  # 解析 ETA,判断是否超过 3 个月
996
- eta_rt = page["properties"].get("ETA", {}).get("rich_text", [])
997
- if not eta_rt:
998
  logger.log("ETA is empty, skipped")
999
  skipped_eta.append(f"{mawb} - ETA empty")
1000
  continue
1001
 
1002
- eta_text = eta_rt[0]["text"]["content"]
1003
- eta_date = None
1004
- for fmt in ("%Y/%m/%d", "%Y-%m-%d"):
1005
- try:
1006
- eta_date = datetime.strptime(eta_text[:10], fmt)
1007
- break
1008
- except ValueError:
1009
- continue
1010
- if eta_date is None:
1011
- logger.log(f"ETA parse failed: '{eta_text}', skipped")
1012
- skipped_eta.append(f"{mawb} - ETA parse failed: {eta_text}")
1013
  continue
1014
 
1015
  if eta_date > cutoff_date:
1016
- logger.log(f"ETA {eta_text} is after cutoff date, skipped")
1017
- skipped_eta.append(f"{mawb} - ETA {eta_text} after cutoff date")
1018
  continue
1019
 
1020
  # 检查 WH-OUT Date 是否为空
@@ -1036,7 +1083,7 @@ def archive_old_records(cutoff_date_str=None, logger=None):
1036
 
1037
  r_archive = notion_archive_page(page["id"])
1038
  if r_archive.status_code == 200:
1039
- logger.log(f"Archived successfully (ETA: {eta_text})")
1040
  archived_count += 1
1041
  else:
1042
  logger.log(f"Delete from main DB failed: {r_archive.text}")
 
67
 
68
  # 归档时需要迁移的属性(按 Notion 字段类型分组)
69
  ARCHIVE_RICH_TEXT_FIELDS = [
70
+ "Customer", "POD", "Service Level", "LM Carrier"
 
71
  ]
72
  ARCHIVE_NUMBER_FIELDS = [
73
  "Parcels", "HTS Count", "Entry Count", "CWT",
 
77
  "WT Fee", "CES Transfer Fee"
78
  ]
79
  ARCHIVE_CHECKBOX_FIELDS = ["AR", "AP"]
80
+ ARCHIVE_DATE_FIELDS = ["WH-OUT Date", "ETA", "ATA", "AR Completed Date", "AP Completed Date"]
81
+
82
+ # POD → IANA timezone mapping (for localizing Excel dates when writing ETA/ATA)
83
+ POD_TIMEZONE_MAP = {
84
+ "JFK": "America/New_York",
85
+ "ORD": "America/Chicago",
86
+ "LAX": "America/Los_Angeles",
87
+ "MIA": "America/New_York",
88
+ }
89
 
90
  # ================== 会话与重试配置 ==================
91
  session = requests.Session()
 
313
 
314
 
315
  def extract_mawb_with_id_ar_ap(page):
316
+ """提取 MAWB 及页面 ID、AR/AP 状态、POD"""
317
  title = page["properties"]["MAWB"]["title"]
318
  if title:
319
  mawb = title[0]["text"]["content"]
320
+ pod_rt = page["properties"].get("POD", {}).get("rich_text", [])
321
+ pod = pod_rt[0]["text"]["content"] if pod_rt else ""
322
  return mawb, {
323
  "id": page["id"],
324
  "ar_checked": page["properties"]["AR"]["checkbox"],
325
  "ap_checked": page["properties"]["AP"]["checkbox"],
326
+ "pod": pod,
327
  }
328
  return None, None
329
 
 
395
  skipped += 1
396
  continue
397
 
398
+ # -------- ETA 处理(日期写入,含目的地时区)--------
399
+ pod = str(row["Destination"]).strip()
400
+ tz_name = POD_TIMEZONE_MAP.get(pod.upper(), "America/Los_Angeles")
401
+ eta_date_prop = None
402
  if pd.notna(row["Estimated Time Arrival"]):
403
  try:
404
  eta_dt = pd.to_datetime(row["Estimated Time Arrival"])
405
+ eta_start = eta_dt.strftime("%Y-%m-%dT%H:%M:%S")
406
+ eta_date_prop = {"date": {"start": eta_start, "time_zone": tz_name}}
407
  except Exception:
408
+ eta_date_prop = None
409
 
410
  service_level = str(row["Service Level"]).strip()
411
  cbm = row["CBM"]
 
420
  "rich_text": [{"text": {"content": str(row["Customer"]).strip()}}]
421
  },
422
  "POD": {
423
+ "rich_text": [{"text": {"content": pod}}]
424
  },
425
  "Service Level": {
426
  "rich_text": [{"text": {"content": service_level}}]
 
435
  "AP": {"checkbox": False}
436
  }
437
 
438
+ if eta_date_prop:
439
+ properties["ETA"] = eta_date_prop
 
 
440
 
441
  # -------- HTS Count + Entry Count(仅 T01 服务等级)--------
442
  if service_level == "T01" and pd.notna(cbm):
 
497
  # 解析 Excel
498
  result = parse_ar_excel(file, logger)
499
  if isinstance(result, tuple) and len(result) == 2 and isinstance(result[1], str):
 
500
  return result[1]
501
 
502
  df, (mawb_col, hts_fee_col, total_col) = result
503
 
504
+ # -------- 服务端过滤:只查 AR=false 的记录 --------
505
+ logger.log("Querying Notion (filter: AR=false)...")
506
+ ar_false_filter = {"property": "AR", "checkbox": {"equals": False}}
507
+ pages_to_update = query_all_pages(extract_mawb_with_ar, filter_body=ar_false_filter)
508
+ logger.log(f"Records with AR unchecked: {len(pages_to_update)}")
509
+
510
+ # 轻量查询:全部 MAWB,用于区分「未找到」与「已勾选 AR」
511
+ logger.log("Querying all MAWBs for existence check...")
512
+ all_mawbs = set(query_all_pages(extract_mawb_only).keys())
513
+ logger.log(f"Total MAWBs in Notion: {len(all_mawbs)}")
514
 
515
+ # 统一时间戳(洛杉矶时区,同批次一致)
516
+ la_tz = ZoneInfo("America/Los_Angeles")
517
+ _now_la = datetime.now(la_tz)
518
+ completed_time = _now_la.strftime("%Y/%m/%d %H:%M") # 用于日志显示
519
+ completed_time_iso = _now_la.strftime("%Y-%m-%dT%H:%M:%S") # 用于 Notion 日期字段
520
+
521
+ # -------- Phase 1:预处理,构建更新任务列表 --------
522
  updated, skipped, not_found = [], [], []
523
+ tasks = [] # (mawb, page_id, properties, log_suffix)
524
  total = len(df)
525
 
526
  for current, (_, row) in enumerate(df.iterrows(), 1):
527
  mawb = str(row[mawb_col]).strip()
528
+ logger.log(f"Preparing: {current}/{total} | MAWB: {mawb}")
529
 
530
  if not mawb or mawb.lower() == 'nan':
531
  logger.log("Empty MAWB, skipped")
532
  continue
533
 
534
+ if mawb in pages_to_update:
535
+ properties = {
536
+ "AR": {"checkbox": True},
537
+ "AR Completed Date": {"date": {"start": completed_time_iso, "time_zone": "America/Los_Angeles"}}
538
+ }
539
+ log_parts = [f"AR checked, AR Completed Date: {completed_time}"]
540
+
541
+ if hts_fee_col and pd.notna(row.get(hts_fee_col)):
542
+ try:
543
+ customs_fee = float(row[hts_fee_col])
544
+ properties["Customs Clearance Fee"] = {"number": round(customs_fee, 2)}
545
+ log_parts.append(f"Customs Clearance Fee: {customs_fee:.2f}")
546
+ except (ValueError, TypeError):
547
+ logger.log("Warning: Invalid HTS Processing Fee value, skipped")
548
+
549
+ if total_col and pd.notna(row.get(total_col)):
550
+ try:
551
+ ar_amount = float(row[total_col])
552
+ properties["AR amount"] = {"number": round(ar_amount, 2)}
553
+ log_parts.append(f"AR amount: {ar_amount:.2f}")
554
+ except (ValueError, TypeError):
555
+ logger.log("Warning: Invalid Total value, skipped")
556
+
557
+ tasks.append((mawb, pages_to_update[mawb]["id"], properties, " | ".join(log_parts)))
558
 
559
+ elif mawb in all_mawbs:
560
+ logger.log("AR already checked, skipped")
 
561
  skipped.append(mawb)
562
+ else:
563
+ logger.log("Not found in Notion")
564
+ not_found.append(mawb)
 
 
 
 
 
 
 
565
 
566
+ logger.log(f"\nPrepared {len(tasks)} update tasks, starting concurrent PATCH (workers={MAX_CONCURRENT_WORKERS})...")
 
 
 
 
 
 
567
 
568
+ # -------- Phase 2:并发 PATCH --------
569
+ lock = threading.Lock()
 
 
 
 
 
570
 
571
+ def _patch_task(mawb, page_id, properties, log_suffix):
572
+ r = notion_patch(page_id, properties)
573
+ return mawb, r.status_code, r.text, log_suffix
 
 
 
 
 
 
 
 
574
 
575
+ with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_WORKERS) as executor:
576
+ futures = {
577
+ executor.submit(_patch_task, mawb, page_id, props, log_sfx): mawb
578
+ for mawb, page_id, props, log_sfx in tasks
579
+ }
580
+ for future in as_completed(futures):
581
+ mawb, status, text, log_sfx = future.result()
582
+ with lock:
583
+ if status == 200:
584
+ logger.log(f"MAWB {mawb} | {log_sfx}")
585
+ updated.append(mawb)
586
+ else:
587
+ logger.log(f"MAWB {mawb} update failed: {text}")
588
 
589
  logger.log("\n================ Process Completed ================")
590
  logger.log(f"AR checked: {len(updated)}")
 
694
  return mawb_dict, extra_fee_records, jfk_mawbs
695
 
696
 
697
+ def update_ap_in_notion(mawb_dict, extra_fee_records, existing_pages, isc_summary, jfk_mawbs, all_mawbs, logger):
698
  """更新 AP 到 Notion"""
 
699
  skipped_ap_mawbs = []
700
  not_found_mawbs = []
701
  total = len(mawb_dict)
702
 
703
  extra_fee_mawbs = {x.split(" - ")[0] for x in extra_fee_records}
704
 
705
+ # 统一时间戳(洛杉矶时区,同批次一致)
706
+ la_tz = ZoneInfo("America/Los_Angeles")
707
+ _now_la = datetime.now(la_tz)
708
+ completed_time = _now_la.strftime("%Y/%m/%d %H:%M") # 用于日志显示
709
+ completed_time_iso = _now_la.strftime("%Y-%m-%dT%H:%M:%S") # 用于 Notion 日期字段
710
+
711
+ # -------- Phase 1:预处理,构建更新任务列表 --------
712
+ tasks = [] # (mawb, page_id, properties, ap_amount)
713
+
714
  for current, (mawb, fees) in enumerate(mawb_dict.items(), 1):
715
+ logger.log(f"Preparing: {current}/{total} | MAWB: {mawb}")
716
 
717
  # 跳过含有额外费用的 MAWB
718
  if mawb in extra_fee_mawbs:
 
720
  skipped_ap_mawbs.append(f"{mawb} - Extra fee present")
721
  continue
722
 
723
+ # existing_pages 仅含 AP=false 记录;不在其中说明未找到或已勾选
724
  if mawb not in existing_pages:
725
+ if mawb in all_mawbs:
726
+ logger.log("AP already checked, skipped")
727
+ skipped_ap_mawbs.append(f"{mawb} - AP already checked")
728
+ else:
729
+ logger.log("MAWB not found in Notion, skip")
730
+ not_found_mawbs.append(mawb)
731
  continue
732
 
733
  page_info = existing_pages[mawb]
734
 
 
 
 
 
 
 
735
  # ===== CWT 前置强校验 =====
736
  cwt_value = page_info.get("CWT")
737
 
 
777
  for col_name in ["ISC Fee", "BreakDown Fee", "Bypass Fee", "CES Inspection Fee", "CES Transfer Fee"]:
778
  properties[col_name] = {"number": round(fees.get(col_name, 0.0), 2)}
779
 
 
780
  wt_fee_value = round(fees.get("WT Fee", 0.0), 2)
781
  properties["WT Fee"] = {"number": wt_fee_value}
782
 
 
783
  ap_amount = (
784
  fees.get("ISC Fee", 0.0) +
785
  fees.get("BreakDown Fee", 0.0) +
 
789
  wt_fee_value
790
  )
791
  properties["AP amount"] = {"number": round(ap_amount, 2)}
 
792
  properties["AP"] = {"checkbox": True}
793
+ properties["AP Completed Date"] = {"date": {"start": completed_time_iso, "time_zone": "America/Los_Angeles"}}
 
 
 
794
 
795
+ tasks.append((mawb, page_info["id"], properties, ap_amount))
 
 
 
 
 
796
 
797
+ logger.log(f"\nPrepared {len(tasks)} update tasks, starting concurrent PATCH (workers={MAX_CONCURRENT_WORKERS})...")
798
+
799
+ # -------- Phase 2:并发 PATCH --------
800
+ updated_count = 0
801
+ lock = threading.Lock()
802
+
803
+ def _patch_task(mawb, page_id, properties, ap_amount):
804
+ r = notion_patch(page_id, properties)
805
+ return mawb, r.status_code, r.text, ap_amount
806
+
807
+ with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_WORKERS) as executor:
808
+ futures = {
809
+ executor.submit(_patch_task, mawb, page_id, props, ap_amt): mawb
810
+ for mawb, page_id, props, ap_amt in tasks
811
+ }
812
+ for future in as_completed(futures):
813
+ mawb, status, text, ap_amt = future.result()
814
+ with lock:
815
+ if status == 200:
816
+ logger.log(f"MAWB {mawb} updated | AP checked, AP amount: {ap_amt:.2f}, AP Completed Date: {completed_time}")
817
+ updated_count += 1
818
+ else:
819
+ logger.log(f"MAWB {mawb} update failed: {text}")
820
 
821
  return updated_count, not_found_mawbs, skipped_ap_mawbs
822
 
 
843
  else:
844
  logger.log("No JFK invoices found, ISC fee validation not required")
845
 
846
+ # -------- 服务端过滤:只查 AP=false 的记录 --------
847
+ logger.log("Querying Notion (filter: AP=false)...")
848
+ ap_false_filter = {"property": "AP", "checkbox": {"equals": False}}
849
+ existing_pages = query_all_pages(extract_mawb_with_ap_cwt, filter_body=ap_false_filter)
850
+ logger.log(f"Records with AP unchecked: {len(existing_pages)}")
851
+
852
+ # 轻量查询:全部 MAWB,用于区分「未找到」与「已勾选 AP」
853
+ logger.log("Querying all MAWBs for existence check...")
854
+ all_mawbs = set(query_all_pages(extract_mawb_only).keys())
855
+ logger.log(f"Total MAWBs in Notion: {len(all_mawbs)}")
856
 
857
  # 更新 Notion
858
  updated_count, not_found_mawbs, skipped_ap_mawbs = update_ap_in_notion(
859
+ mawb_dict, extra_fee_records, existing_pages, isc_summary, jfk_mawbs, all_mawbs, logger
860
  )
861
 
862
  logger.log("\n================ Process Completed ================")
 
937
  log_parts.append(f"CWT={weight:.2f}")
938
 
939
  # ATA 仅在 Excel 中有值时才写入(不受 AR/AP 影响)
 
940
  if pd.notna(ata):
941
+ pod = page_info.get("pod", "")
942
+ tz_name = POD_TIMEZONE_MAP.get(pod.upper(), "America/Los_Angeles")
943
+ ata_start = ata.strftime("%Y-%m-%dT%H:%M:%S")
944
+ properties["ATA"] = {"date": {"start": ata_start, "time_zone": tz_name}}
945
+ log_parts.append(f"ATA={ata_start} ({tz_name})")
946
 
947
  # 没有任何属性需要更新,跳过
948
  if not properties:
 
1045
  logger.log(f"Progress: {idx}/{total} | Processing MAWB: {mawb}")
1046
 
1047
  # 解析 ETA,判断是否超过 3 个月
1048
+ eta_date_val = page["properties"].get("ETA", {}).get("date")
1049
+ if not eta_date_val or not eta_date_val.get("start"):
1050
  logger.log("ETA is empty, skipped")
1051
  skipped_eta.append(f"{mawb} - ETA empty")
1052
  continue
1053
 
1054
+ eta_start = eta_date_val["start"]
1055
+ try:
1056
+ eta_date = datetime.strptime(eta_start[:10], "%Y-%m-%d")
1057
+ except ValueError:
1058
+ logger.log(f"ETA parse failed: '{eta_start}', skipped")
1059
+ skipped_eta.append(f"{mawb} - ETA parse failed: {eta_start}")
 
 
 
 
 
1060
  continue
1061
 
1062
  if eta_date > cutoff_date:
1063
+ logger.log(f"ETA {eta_start[:10]} is after cutoff date, skipped")
1064
+ skipped_eta.append(f"{mawb} - ETA {eta_start[:10]} after cutoff date")
1065
  continue
1066
 
1067
  # 检查 WH-OUT Date 是否为空
 
1083
 
1084
  r_archive = notion_archive_page(page["id"])
1085
  if r_archive.status_code == 200:
1086
+ logger.log(f"Archived successfully (ETA: {eta_start[:10]})")
1087
  archived_count += 1
1088
  else:
1089
  logger.log(f"Delete from main DB failed: {r_archive.text}")