Skip to content

Commit 9803673

Browse files
KaoruhaKaoruclaude
authored
refactor: data sync 三分支汇总格式统一 (SyncStats) (#6429)
抽取 SyncStats 三态计数器(success/skipped/errors)+ 统一汇总格式, day/tick/adjustfactor 三分支共用,消除格式漂移。 - day 分支已有 skipped_count,tick/adjustfactor 仅二态 → 统一三态 - adjustfactor records_added=0 的 no-data 情况原仅 warn 不计数, 现计 skipped(对齐 day 分支语义) - summary 格式统一: {Type} sync completed. Success: N, Skipped: S, Errors: M Co-authored-by: Kaoru <sun159753@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 945e64e commit 9803673

2 files changed

Lines changed: 92 additions & 21 deletions

File tree

src/ginkgo/client/data_cli.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,36 @@ def _normalize_stock_code(code: str) -> str:
4040
return code
4141

4242

43+
class SyncStats:
44+
"""#6054: data sync 三态计数器 + 统一汇总格式。
45+
46+
day/tick/adjustfactor 三分支共用,避免每分支独立维护 success/error[/skipped]
47+
计数与拼接字符串导致格式漂移(day 加 skipped_count 时 tick/adjustfactor 未同步,
48+
adjustfactor 的 no-data 情况漏计数——均是不抽象的代价)。
49+
"""
50+
51+
def __init__(self) -> None:
52+
self.success = 0
53+
self.skipped = 0
54+
self.errors = 0
55+
56+
def record_success(self) -> None:
57+
self.success += 1
58+
59+
def record_skipped(self) -> None:
60+
self.skipped += 1
61+
62+
def record_error(self) -> None:
63+
self.errors += 1
64+
65+
def summary(self, type_name: str) -> str:
66+
"""统一汇总行:``{Type} sync completed. Success: N, Skipped: S, Errors: M``。"""
67+
return (
68+
f"{type_name} sync completed. "
69+
f"Success: {self.success}, Skipped: {self.skipped}, Errors: {self.errors}"
70+
)
71+
72+
4373
@app.command()
4474
def get(
4575
data_type: str = typer.Argument(..., help="Data type to get (stockinfo/day/tick/adjustfactor/sources) \\[planned: calendar]"),
@@ -591,9 +621,7 @@ def sync(
591621
raise typer.Exit(1)
592622

593623
try:
594-
success_count = 0
595-
error_count = 0
596-
skipped_count = 0
624+
stats = SyncStats()
597625

598626
for current_code in codes:
599627
try:
@@ -621,22 +649,22 @@ def sync(
621649
except (AttributeError, TypeError, ValueError):
622650
records_added = 0
623651
if records_added > 0:
624-
success_count += 1
652+
stats.record_success()
625653
console.print(f":white_check_mark: {current_code} sync completed ({records_added} records)")
626654
else:
627-
skipped_count += 1
655+
stats.record_skipped()
628656
console.print(f":warning: {current_code} — no data available from source")
629657
else:
630-
error_count += 1
658+
stats.record_error()
631659
error_msg = result.message if hasattr(result, 'message') else str(result.error) if hasattr(result, 'error') else 'Unknown error'
632660
console.print(f":x: {current_code} sync failed: {error_msg}")
633661

634662
except Exception as e:
635-
error_count += 1
663+
stats.record_error()
636664
console.print(f":x: Error syncing {current_code}: {str(e)}")
637665
continue
638666

639-
console.print(f":information: Day sync completed. Success: {success_count}, Skipped: {skipped_count}, Errors: {error_count}")
667+
console.print(f":information: {stats.summary('Day')}")
640668

641669
except Exception as e:
642670
console.print(f":x: Error in day sync process: {e}")
@@ -666,8 +694,7 @@ def sync(
666694
raise typer.Exit(1)
667695

668696
try:
669-
success_count = 0
670-
error_count = 0
697+
stats = SyncStats()
671698

672699
for current_code in codes:
673700
try:
@@ -689,19 +716,19 @@ def sync(
689716
result = tick_service.sync_smart(current_code)
690717

691718
if result and result.is_success():
692-
success_count += 1
719+
stats.record_success()
693720
console.print(f":white_check_mark: {current_code} sync completed")
694721
else:
695-
error_count += 1
722+
stats.record_error()
696723
error_msg = result.message if hasattr(result, 'message') else str(result.error) if hasattr(result, 'error') else 'Unknown error'
697724
console.print(f":x: {current_code} sync failed: {error_msg}")
698725

699726
except Exception as e:
700-
error_count += 1
727+
stats.record_error()
701728
console.print(f":x: Error syncing {current_code}: {str(e)}")
702729
continue
703730

704-
console.print(f":information: Tick sync completed. Success: {success_count}, Errors: {error_count}")
731+
console.print(f":information: {stats.summary('Tick')}")
705732

706733
except Exception as e:
707734
console.print(f":x: Error in tick sync process: {e}")
@@ -730,8 +757,7 @@ def sync(
730757
raise typer.Exit(1)
731758

732759
try:
733-
success_count = 0
734-
error_count = 0
760+
stats = SyncStats()
735761

736762
for current_code in codes:
737763
try:
@@ -770,10 +796,11 @@ def sync(
770796
except (AttributeError, TypeError, ValueError):
771797
records_added = 0
772798
if records_added > 0:
773-
success_count += 1
799+
stats.record_success()
774800
console.print(f":white_check_mark: {current_code} sync completed ({records_added} records)")
775801
else:
776-
# service.sync 成功但源端无数据:不算成功计数,避免误导(#6053)
802+
# service.sync 成功但源端无数据:计 skipped(#6053/#6054 统一三态)
803+
stats.record_skipped()
777804
console.print(f":warning: {current_code} — no adjustfactor data available from source")
778805

779806
# 同步完成后立即计算该股票的复权因子
@@ -786,19 +813,19 @@ def sync(
786813
if hasattr(calc_result, 'error') and calc_result.error:
787814
console.print(f" Error: {calc_result.error}")
788815
else:
789-
error_count += 1
816+
stats.record_error()
790817
console.print(f":x: {current_code} sync failed")
791818
if hasattr(result, 'error') and result.error:
792819
console.print(f" Error: {result.error}")
793820
elif hasattr(result, 'message') and result.message:
794821
console.print(f" Message: {result.message}")
795822

796823
except Exception as e:
797-
error_count += 1
824+
stats.record_error()
798825
console.print(f":x: Error syncing {current_code}: {str(e)}")
799826
continue
800827

801-
console.print(f":information: Adjustfactor sync completed. Success: {success_count}, Errors: {error_count}")
828+
console.print(f":information: {stats.summary('Adjustfactor')}")
802829

803830
except Exception as e:
804831
console.print(f":x: Error in adjustfactor sync process: {e}")
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""#6054: SyncStats 三态计数器 + 统一汇总格式。
2+
3+
data_cli.sync 的 day/tick/adjustfactor 三分支各自维护 success_count/error_count
4+
(day 另有 skipped_count),汇总格式不一致(day 三态 vs tick/adjustfactor 二态),
5+
且 adjustfactor 的 no-data 情况漏计数。SyncStats 收敛「三态计数 + 格式化」到一处,
6+
三分支共用,杜绝漂移(见 issue #6054 简报)。
7+
"""
8+
import os
9+
10+
os.environ["GINKGO_SKIP_DEBUG_CHECK"] = "1"
11+
12+
import pytest
13+
14+
from ginkgo.client.data_cli import SyncStats
15+
16+
17+
class TestSyncStats:
18+
"""SyncStats 纯逻辑:三态计数 + 统一 summary 格式。"""
19+
20+
def test_empty_summary_format(self):
21+
"""零计数 → ``{Type} sync completed. Success: 0, Skipped: 0, Errors: 0``。"""
22+
stats = SyncStats()
23+
assert stats.summary("Day") == (
24+
"Day sync completed. Success: 0, Skipped: 0, Errors: 0"
25+
)
26+
27+
def test_three_state_counts(self):
28+
"""record_success/skipped/error 累加 → summary 反映正确计数。"""
29+
stats = SyncStats()
30+
stats.record_success()
31+
stats.record_success()
32+
stats.record_skipped()
33+
stats.record_error()
34+
stats.record_error()
35+
stats.record_error()
36+
assert stats.summary("Tick") == (
37+
"Tick sync completed. Success: 2, Skipped: 1, Errors: 3"
38+
)
39+
40+
def test_type_name_interpolated(self):
41+
"""type_name 原样插到汇总行开头(day/tick/adjustfactor 三分支复用)。"""
42+
stats = SyncStats()
43+
stats.record_success()
44+
assert stats.summary("Adjustfactor").startswith("Adjustfactor sync completed.")

0 commit comments

Comments
 (0)