@yorha2b-lab/autodev 3.5.1 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/scripts/analyze/bot_detect.py +43 -0
- package/.github/scripts/analyze/cluster.py +40 -0
- package/.github/scripts/collect/traffic.py +66 -0
- package/.github/scripts/deploy/publish.py +70 -0
- package/.github/scripts/render/dashboard.py +235 -0
- package/.github/workflows/bunker-intelligence.yml +64 -0
- package/example/example.json +47 -30
- package/package.json +1 -1
- package/src/core/context.js +45 -5
- package/src/core/react-compiler.js +3 -1
- package/templates/react/handlebars/hookBlock.hbs +2 -2
- package/templates/react/index.hbs +1 -1
- package/.github/workflows/main.yml +0 -207
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def plot_bot_composition():
|
|
7
|
+
print("🤖 Pod 042: 正在绘制‘肉机成分分析图’...")
|
|
8
|
+
df_c = pd.read_csv('ghrs-data/clones_ledger.csv')
|
|
9
|
+
df_v = pd.read_csv('ghrs-data/views_ledger.csv')
|
|
10
|
+
df = pd.merge(df_c, df_v, on='date', how='inner')
|
|
11
|
+
|
|
12
|
+
# 算法:Shadow Ratio > 5 判定为机器
|
|
13
|
+
df['is_bot'] = (df['clones'] / df['views'].replace(0, 1)) > 5
|
|
14
|
+
bot_clones = df[df['is_bot']]['clones'].sum()
|
|
15
|
+
total_clones = df['clones'].sum()
|
|
16
|
+
human_clones = total_clones - bot_clones
|
|
17
|
+
|
|
18
|
+
# 💡 绘图:地堡级甜甜圈图
|
|
19
|
+
plt.style.use('dark_background')
|
|
20
|
+
fig, ax = plt.subplots(figsize=(8, 8))
|
|
21
|
+
|
|
22
|
+
data = [human_clones, bot_clones]
|
|
23
|
+
labels = ['Human Commanders', 'Machine Lifeforms']
|
|
24
|
+
colors = ['#33cc33', '#444444'] # 地堡绿 vs 灰
|
|
25
|
+
|
|
26
|
+
wedges, texts, autotexts = ax.pie(data, labels=labels, autopct='%1.1f%%',
|
|
27
|
+
startangle=90, colors=colors, pctdistance=0.85,
|
|
28
|
+
explode=(0.05, 0), textprops={'color': "w"})
|
|
29
|
+
|
|
30
|
+
# 画个中间的黑洞,变成甜甜圈
|
|
31
|
+
centre_circle = plt.Circle((0, 0), 0.70, fc='#0d1117')
|
|
32
|
+
fig.gca().add_artist(centre_circle)
|
|
33
|
+
|
|
34
|
+
ax.set_title("Operational Composition: Meat vs Machine",
|
|
35
|
+
color='#33cc33', fontsize=14)
|
|
36
|
+
plt.tight_layout()
|
|
37
|
+
os.makedirs('plots', exist_ok=True)
|
|
38
|
+
plt.savefig('plots/bot_composition.png', dpi=100)
|
|
39
|
+
print("✅ 甜甜圈图封存成功。")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
plot_bot_composition()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import seaborn as sns
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def plot_commander_profiles():
|
|
7
|
+
print("🤖 Pod 042: 正在绘制‘指挥官军衔分布图’...")
|
|
8
|
+
df_c = pd.read_csv('ghrs-data/clones_ledger.csv')
|
|
9
|
+
df_c['intensity'] = df_c['clones'] / df_c['uniques'].replace(0, 1)
|
|
10
|
+
|
|
11
|
+
# 定义军衔逻辑
|
|
12
|
+
def get_rank(row):
|
|
13
|
+
if row['intensity'] > 5:
|
|
14
|
+
return 'Industrial (CI/CD)'
|
|
15
|
+
if row['intensity'] > 1.5:
|
|
16
|
+
return 'Veteran (Hardcore)'
|
|
17
|
+
return 'Recruit (Fresh)'
|
|
18
|
+
|
|
19
|
+
df_c['rank'] = df_c.apply(get_rank, axis=1)
|
|
20
|
+
rank_counts = df_c['rank'].value_counts()
|
|
21
|
+
|
|
22
|
+
# 💡 绘图:战力梯度图
|
|
23
|
+
plt.style.use('dark_background')
|
|
24
|
+
plt.figure(figsize=(10, 6))
|
|
25
|
+
|
|
26
|
+
# 用 seaborn 画出更有质感的柱状图
|
|
27
|
+
ax = sns.barplot(x=rank_counts.index,
|
|
28
|
+
y=rank_counts.values, palette='viridis')
|
|
29
|
+
|
|
30
|
+
plt.title("Commander Hierarchy Distribution", color='#0066ff', fontsize=14)
|
|
31
|
+
plt.ylabel("Node Count", color='#888')
|
|
32
|
+
plt.grid(axis='y', color='#333', linestyle='--', alpha=0.5)
|
|
33
|
+
|
|
34
|
+
plt.tight_layout()
|
|
35
|
+
plt.savefig('plots/commander_profiles.png', dpi=100)
|
|
36
|
+
print("✅ 军衔分布图封存成功。")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
plot_commander_profiles()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
REPO = os.getenv('REPO')
|
|
7
|
+
TOKEN = os.getenv('STATS_TOKEN')
|
|
8
|
+
HEADERS = {'Authorization': f'token {TOKEN}',
|
|
9
|
+
'Accept': 'application/vnd.github.v3+json'}
|
|
10
|
+
API_BASE = f"https://api.github.com/repos/{REPO}"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def fetch_api(url, fallback):
|
|
14
|
+
resp = requests.get(url, headers=HEADERS)
|
|
15
|
+
return resp.json() if resp.status_code == 200 else fallback
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def merge_ledger(data, ledger_path, col_name):
|
|
19
|
+
os.makedirs('ghrs-data', exist_ok=True)
|
|
20
|
+
new_df = pd.DataFrame(data)
|
|
21
|
+
if new_df.empty:
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
new_df['date'] = new_df['timestamp'].str[:10]
|
|
25
|
+
new_df = new_df.rename(columns={'count': col_name})[
|
|
26
|
+
['date', col_name, 'uniques']]
|
|
27
|
+
|
|
28
|
+
if os.path.exists(ledger_path):
|
|
29
|
+
old_df = pd.read_csv(ledger_path)
|
|
30
|
+
# 💡 物理去重对账逻辑
|
|
31
|
+
combined = pd.concat([old_df, new_df]).drop_duplicates(
|
|
32
|
+
subset=['date'], keep='last')
|
|
33
|
+
else:
|
|
34
|
+
combined = new_df
|
|
35
|
+
combined.sort_values('date', ascending=False).to_csv(
|
|
36
|
+
ledger_path, index=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
print("🤖 Pod 042: Executing Data Mining Protocol...")
|
|
41
|
+
|
|
42
|
+
# 抓取原始数据
|
|
43
|
+
clones = fetch_api(f"{API_BASE}/traffic/clones", {"clones": []})
|
|
44
|
+
views = fetch_api(f"{API_BASE}/traffic/views", {"views": []})
|
|
45
|
+
repo_info = fetch_api(API_BASE, {"stargazers_count": 0, "forks_count": 0})
|
|
46
|
+
referrers = fetch_api(f"{API_BASE}/traffic/popular/referrers", [])
|
|
47
|
+
|
|
48
|
+
# 物理焊接账本
|
|
49
|
+
merge_ledger(clones['clones'], 'ghrs-data/clones_ledger.csv', 'clones')
|
|
50
|
+
merge_ledger(views['views'], 'ghrs-data/views_ledger.csv', 'views')
|
|
51
|
+
|
|
52
|
+
# 物理封存 Referrers 用于后续发布
|
|
53
|
+
with open('raw_referrers.json', 'w') as f:
|
|
54
|
+
json.dump(referrers, f)
|
|
55
|
+
|
|
56
|
+
# 战绩结算
|
|
57
|
+
c_df = pd.read_csv('ghrs-data/clones_ledger.csv')
|
|
58
|
+
total_clones = int(c_df['clones'].sum())
|
|
59
|
+
stats = {
|
|
60
|
+
"total_clones": total_clones,
|
|
61
|
+
"stars": repo_info['stargazers_count'],
|
|
62
|
+
"forks": repo_info['forks_count']
|
|
63
|
+
}
|
|
64
|
+
with open('bunker-stats.json', 'w') as f:
|
|
65
|
+
json.dump(stats, f)
|
|
66
|
+
print(f"✅ Ledger synced. Total Clones: {total_clones}")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import datetime
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def assemble_readme():
|
|
7
|
+
with open('bunker-stats.json', 'r') as f:
|
|
8
|
+
stats = json.load(f)
|
|
9
|
+
|
|
10
|
+
with open('raw_referrers.json', 'r') as f:
|
|
11
|
+
referrers = json.load(f)
|
|
12
|
+
|
|
13
|
+
ref_table = "| Referrer | Views | Uniques |\n| :--- | :--- | :--- |\n"
|
|
14
|
+
if referrers:
|
|
15
|
+
for r in referrers:
|
|
16
|
+
ref_table += f"| {r['referrer']} | {r['count']} | {r['uniques']} |\n"
|
|
17
|
+
else:
|
|
18
|
+
ref_table += "| No Signal | - | - |\n"
|
|
19
|
+
|
|
20
|
+
report = f"""# 🛰️ 地堡终极运行报告 (Sovereign Dashboard v8.5)
|
|
21
|
+
## 📊 核心战力指标
|
|
22
|
+
- **累计物理克隆**: `{stats['total_clones']}` 次
|
|
23
|
+
- **社群声望**: ⭐ `{stats['stars']}` / 🍴 `{stats['forks']}`
|
|
24
|
+
- **地堡协议防护**: AGPL-3.0 生效中
|
|
25
|
+
|
|
26
|
+
### 🖥️ 1. 实时作战看板 (Visual Radar)
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
### 🔍 2. 战略情报大屏 (Intelligence Grid)
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
### 📡 3. 深度作战审计 (Deep Dive Recon)
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
### 🧠 4. 深度情报审计 (Intelligence Intelligence)
|
|
36
|
+
| 成分分析 (Meat vs Machine) | 战力梯度 (Hierarchy) |
|
|
37
|
+
| :---: | :---: |
|
|
38
|
+
|  |  |
|
|
39
|
+
|
|
40
|
+
#### 🛰️ 流量来源实时追踪
|
|
41
|
+
{ref_table}
|
|
42
|
+
|
|
43
|
+
> 📡 物理封存点: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} CST | Glory to Mankind.
|
|
44
|
+
"""
|
|
45
|
+
with open('README.md', 'w') as f:
|
|
46
|
+
f.write(report)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def git_push():
|
|
50
|
+
with open('bunker-stats.json', 'r') as f:
|
|
51
|
+
stats = json.load(f)
|
|
52
|
+
# 执行物理封存 Git 指令
|
|
53
|
+
try:
|
|
54
|
+
subprocess.run(["git", "config", "user.name",
|
|
55
|
+
"github-actions"], check=True)
|
|
56
|
+
subprocess.run(["git", "config", "user.email",
|
|
57
|
+
"github-actions@github.com"], check=True)
|
|
58
|
+
subprocess.run(["git", "add", "-A"], check=True)
|
|
59
|
+
subprocess.run(
|
|
60
|
+
["git", "commit", "-m", f"chore: sovereign modular update [total {stats['total_clones']}]"], check=True)
|
|
61
|
+
subprocess.run(
|
|
62
|
+
["git", "push", "origin", "github-repo-stats", "--force"], check=True)
|
|
63
|
+
print("✅ Data sealed and pushed to cloud.")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"⚠️ Git push skipped: {e}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
assemble_readme()
|
|
70
|
+
git_push()
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import seaborn as sns
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import matplotlib.dates as mdates
|
|
7
|
+
|
|
8
|
+
def peak_recon():
|
|
9
|
+
df = pd.read_csv('ghrs-data/clones_ledger.csv')
|
|
10
|
+
df['date'] = pd.to_datetime(df['date'])
|
|
11
|
+
df = df.sort_values('date').reset_index(drop=True)
|
|
12
|
+
fig3, axes3 = plt.subplots(3, 1, figsize=(16, 14))
|
|
13
|
+
# 3.1 时间序列图
|
|
14
|
+
axA = axes3[0]
|
|
15
|
+
axA.fill_between(df['date'], df['clones'],
|
|
16
|
+
alpha=0.3, color='#3498db', label='Clones')
|
|
17
|
+
axA.plot(df['date'], df['clones'], color='#3498db', linewidth=1.2)
|
|
18
|
+
axA_twin = axA.twinx()
|
|
19
|
+
axA_twin.plot(df['date'], df['uniques'],
|
|
20
|
+
color='#e74c3c', linewidth=1.2, label='Visitors')
|
|
21
|
+
axA_twin.fill_between(
|
|
22
|
+
df['date'], df['uniques'], alpha=0.15, color='#e74c3c')
|
|
23
|
+
axA.set_ylabel('Clones', color='#3498db', fontsize=12)
|
|
24
|
+
axA_twin.set_ylabel('Visitors', color='#e74c3c', fontsize=12)
|
|
25
|
+
axA.set_title('Bunker Signal Intensity (Peak Detection Mode)',
|
|
26
|
+
fontsize=14, fontweight='bold')
|
|
27
|
+
|
|
28
|
+
peak_clone_idx = df['clones'].idxmax()
|
|
29
|
+
axA.annotate(f'LEGEND PEAK: {df["clones"].max()}',
|
|
30
|
+
xy=(df.loc[peak_clone_idx, 'date'],
|
|
31
|
+
df.loc[peak_clone_idx, 'clones']),
|
|
32
|
+
xytext=(10, 20), textcoords='offset points',
|
|
33
|
+
arrowprops=dict(arrowstyle='->', color='yellow'),
|
|
34
|
+
fontsize=10, color='yellow', fontweight='bold')
|
|
35
|
+
|
|
36
|
+
# 3.2 每周汇总
|
|
37
|
+
axB = axes3[1]
|
|
38
|
+
weekly_agg = df.assign(W=df['date'].dt.strftime(
|
|
39
|
+
'%Y-W%W')).groupby('W')[['clones', 'uniques']].sum().tail(12)
|
|
40
|
+
weekly_agg.plot(kind='bar', ax=axB, alpha=0.8)
|
|
41
|
+
axB.set_title('Weekly Operational Summary',
|
|
42
|
+
fontsize=14, fontweight='bold')
|
|
43
|
+
|
|
44
|
+
# 3.3 比值分析
|
|
45
|
+
axC = axes3[2]
|
|
46
|
+
df['ratio'] = df['clones'] / df['uniques'].replace(0, 1)
|
|
47
|
+
colors3 = ['#2ecc71' if r < 3 else '#f39c12' if r <
|
|
48
|
+
5 else '#e74c3c' for r in df['ratio']]
|
|
49
|
+
axC.bar(df['date'], df['ratio'], color=colors3, alpha=0.7)
|
|
50
|
+
axC.axhline(y=df['ratio'].mean(), color='#9b59b6',
|
|
51
|
+
ls='--', label=f'Avg: {df["ratio"].mean():.2f}')
|
|
52
|
+
axC.set_title('Clones/Visitor Ratio (High Ratio = CI/Automation)',
|
|
53
|
+
fontsize=14, fontweight='bold')
|
|
54
|
+
axC.legend()
|
|
55
|
+
|
|
56
|
+
plt.tight_layout()
|
|
57
|
+
plt.savefig('plots/bunker_peak_recon.png', dpi=100)
|
|
58
|
+
|
|
59
|
+
def intelligence_grid():
|
|
60
|
+
df = pd.read_csv('ghrs-data/clones_ledger.csv')
|
|
61
|
+
df['date'] = pd.to_datetime(df['date'])
|
|
62
|
+
df = df.sort_values('date').reset_index(drop=True)
|
|
63
|
+
df['week'] = df['date'].dt.isocalendar().week
|
|
64
|
+
df['year'] = df['date'].dt.isocalendar().year
|
|
65
|
+
df['week_key'] = df['year'].astype(
|
|
66
|
+
str) + '-W' + df['week'].astype(str).str.zfill(2)
|
|
67
|
+
weekly = df.groupby('week_key').agg({
|
|
68
|
+
'clones': 'sum',
|
|
69
|
+
'uniques': 'sum',
|
|
70
|
+
'date': ['min', 'max']
|
|
71
|
+
}).reset_index()
|
|
72
|
+
weekly.columns = ['week', 'clones',
|
|
73
|
+
'uniques', 'start_date', 'end_date']
|
|
74
|
+
weekly = weekly.sort_values('start_date').reset_index(drop=True)
|
|
75
|
+
|
|
76
|
+
fig2, axes = plt.subplots(3, 2, figsize=(16, 14))
|
|
77
|
+
fig2.suptitle('Bunker Strategic Analytics - Intelligence Grid',
|
|
78
|
+
color='#33cc33', fontsize=20)
|
|
79
|
+
|
|
80
|
+
# 1. Daily Clone Trend
|
|
81
|
+
ax1 = axes[0, 0]
|
|
82
|
+
ax1.plot(df['date'], df['clones'],
|
|
83
|
+
color='#2E86AB', linewidth=1.5, alpha=0.8)
|
|
84
|
+
ax1.fill_between(df['date'], df['clones'],
|
|
85
|
+
alpha=0.3, color='#2E86AB')
|
|
86
|
+
ax1.axhline(y=df['clones'].mean(), color='red', linestyle='--',
|
|
87
|
+
alpha=0.7, label=f'Avg: {df["clones"].mean():.0f}')
|
|
88
|
+
ax1.set_title('Daily Clone Trend', fontsize=12, fontweight='bold')
|
|
89
|
+
ax1.set_ylabel('Clones')
|
|
90
|
+
ax1.legend()
|
|
91
|
+
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
|
|
92
|
+
ax1.xaxis.set_major_locator(mdates.WeekdayLocator(interval=2))
|
|
93
|
+
ax1.tick_params(axis='x', rotation=45)
|
|
94
|
+
ax1.grid(True, alpha=0.3)
|
|
95
|
+
|
|
96
|
+
# 2. Daily Unique Visitor Trend
|
|
97
|
+
ax2 = axes[0, 1]
|
|
98
|
+
ax2.plot(df['date'], df['uniques'],
|
|
99
|
+
color='#A23B72', linewidth=1.5, alpha=0.8)
|
|
100
|
+
ax2.fill_between(df['date'], df['uniques'],
|
|
101
|
+
alpha=0.3, color='#A23B72')
|
|
102
|
+
ax2.axhline(y=df['uniques'].mean(), color='red', linestyle='--',
|
|
103
|
+
alpha=0.7, label=f'Avg: {df["uniques"].mean():.0f}')
|
|
104
|
+
ax2.set_title('Daily Unique Commanders',
|
|
105
|
+
fontsize=12, fontweight='bold')
|
|
106
|
+
ax2.set_ylabel('Unique Visitors')
|
|
107
|
+
ax2.legend()
|
|
108
|
+
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
|
|
109
|
+
ax2.xaxis.set_major_locator(mdates.WeekdayLocator(interval=2))
|
|
110
|
+
ax2.tick_params(axis='x', rotation=45)
|
|
111
|
+
ax2.grid(True, alpha=0.3)
|
|
112
|
+
|
|
113
|
+
# 3. Clones vs Visitors Scatter
|
|
114
|
+
ax3 = axes[1, 0]
|
|
115
|
+
ax3.scatter(df['uniques'], df['clones'], alpha=0.6,
|
|
116
|
+
c=df['date'].map(mdates.date2num), cmap='viridis', s=50)
|
|
117
|
+
ax3.set_xlabel('Unique Visitors')
|
|
118
|
+
ax3.set_ylabel('Clones')
|
|
119
|
+
ax3.set_title('Clones vs Visitors (Color=Time)',
|
|
120
|
+
fontsize=12, fontweight='bold')
|
|
121
|
+
z = np.polyfit(df['uniques'], df['clones'], 1)
|
|
122
|
+
p = np.poly1d(z)
|
|
123
|
+
ax3.plot(df['uniques'].sort_values(), p(df['uniques'].sort_values(
|
|
124
|
+
)), "r--", alpha=0.7, label=f'Trend: y={z[0]:.1f}x+{z[1]:.1f}')
|
|
125
|
+
ax3.legend()
|
|
126
|
+
ax3.grid(True, alpha=0.3)
|
|
127
|
+
|
|
128
|
+
# 4. Clones Per User Distribution
|
|
129
|
+
ax4 = axes[1, 1]
|
|
130
|
+
df['clones_per_unique'] = df['clones'] / df['uniques'].replace(0, 1)
|
|
131
|
+
ax4.hist(df['clones_per_unique'], bins=25,
|
|
132
|
+
color='#F18F01', alpha=0.7, edgecolor='white')
|
|
133
|
+
ax4.axvline(x=df['clones_per_unique'].mean(
|
|
134
|
+
), color='red', linestyle='--', label=f'Avg: {df["clones_per_unique"].mean():.2f}')
|
|
135
|
+
ax4.set_xlabel('Clones Per Unit')
|
|
136
|
+
ax4.set_ylabel('Days')
|
|
137
|
+
ax4.set_title('Clones Per User Distribution',
|
|
138
|
+
fontsize=12, fontweight='bold')
|
|
139
|
+
ax4.legend()
|
|
140
|
+
ax4.grid(True, alpha=0.3)
|
|
141
|
+
|
|
142
|
+
# 5. Weekly Throughput
|
|
143
|
+
ax5 = axes[2, 0]
|
|
144
|
+
weekly_sorted = weekly.sort_values('start_date').tail(12)
|
|
145
|
+
colors = ['#2E86AB' if c < 300 else '#F18F01' if c <
|
|
146
|
+
600 else '#E63946' for c in weekly_sorted['clones']]
|
|
147
|
+
bars = ax5.bar(range(len(weekly_sorted)),
|
|
148
|
+
weekly_sorted['clones'], color=colors, alpha=0.8, edgecolor='white')
|
|
149
|
+
ax5.set_xticks(range(len(weekly_sorted)))
|
|
150
|
+
ax5.set_xticklabels(weekly_sorted['week'].str[-2:], rotation=45)
|
|
151
|
+
ax5.set_xlabel('Week Index')
|
|
152
|
+
ax5.set_ylabel('Total Clones')
|
|
153
|
+
ax5.set_title('Weekly Throughput (Last 12 Weeks)',
|
|
154
|
+
fontsize=12, fontweight='bold')
|
|
155
|
+
ax5.grid(True, alpha=0.3, axis='y')
|
|
156
|
+
for bar, val in zip(bars, weekly_sorted['clones']):
|
|
157
|
+
ax5.text(bar.get_x() + bar.get_width()/2, bar.get_height() +
|
|
158
|
+
10, str(val), ha='center', va='bottom', fontsize=8)
|
|
159
|
+
|
|
160
|
+
# 6. 7-Day Moving Average
|
|
161
|
+
ax6 = axes[2, 1]
|
|
162
|
+
df['clones_ma7'] = df['clones'].rolling(
|
|
163
|
+
window=7, min_periods=1).mean()
|
|
164
|
+
df['uniques_ma7'] = df['uniques'].rolling(
|
|
165
|
+
window=7, min_periods=1).mean()
|
|
166
|
+
ax6.plot(df['date'], df['clones_ma7'], color='#2E86AB',
|
|
167
|
+
linewidth=2, label='Clones 7D MA')
|
|
168
|
+
ax6.plot(df['date'], df['uniques_ma7'], color='#A23B72',
|
|
169
|
+
linewidth=2, label='Visitors 7D MA')
|
|
170
|
+
ax6.set_title('7-Day Moving Average Trend',
|
|
171
|
+
fontsize=12, fontweight='bold')
|
|
172
|
+
ax6.set_ylabel('Frequency')
|
|
173
|
+
ax6.legend()
|
|
174
|
+
ax6.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
|
|
175
|
+
ax6.xaxis.set_major_locator(mdates.WeekdayLocator(interval=2))
|
|
176
|
+
ax6.tick_params(axis='x', rotation=45)
|
|
177
|
+
ax6.grid(True, alpha=0.3)
|
|
178
|
+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
|
179
|
+
plt.savefig('plots/bunker_intelligence_grid.png', dpi=100)
|
|
180
|
+
|
|
181
|
+
def load_clean(path, col):
|
|
182
|
+
df = pd.read_csv(path)
|
|
183
|
+
df['date'] = pd.to_datetime(df['date'], errors='coerce')
|
|
184
|
+
df = df.dropna(subset=['date']).copy()
|
|
185
|
+
df[col] = pd.to_numeric(df[col.lower()], errors='coerce').fillna(0)
|
|
186
|
+
df['uniques'] = pd.to_numeric(df['uniques'], errors='coerce').fillna(0)
|
|
187
|
+
return df[['date', col, 'uniques']]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main_dashboard(df_c, df_v):
|
|
191
|
+
df = pd.merge(df_c, df_v, on='date', how='outer').fillna(
|
|
192
|
+
0).sort_values('date')
|
|
193
|
+
df['cumulative'] = df['Clones'].cumsum()
|
|
194
|
+
df['ratio'] = df['Clones'] / df['uniques'].replace(0, 1)
|
|
195
|
+
df['clones_ma7'] = df['Clones'].rolling(window=7, min_periods=1).mean()
|
|
196
|
+
|
|
197
|
+
fig1 = plt.figure(figsize=(12, 10))
|
|
198
|
+
gs1 = fig1.add_gridspec(3, 1)
|
|
199
|
+
ax1 = fig1.add_subplot(gs1[0])
|
|
200
|
+
ax1.plot(df['date'].tail(30), df['Views'].tail(
|
|
201
|
+
30), alpha=0.5, ls='--', label='Views (Web)')
|
|
202
|
+
ax1.plot(df['date'].tail(30), df['Clones'].tail(30),
|
|
203
|
+
color='#33cc33', lw=2, label='Clones (Terminal)')
|
|
204
|
+
ax1.set_title(
|
|
205
|
+
'Strategic Recon: Views vs Clones (Last 30D)', color='#33cc33')
|
|
206
|
+
ax1.legend(loc='upper left', frameon=True, fontsize='small')
|
|
207
|
+
ax2 = fig1.add_subplot(gs1[1])
|
|
208
|
+
ax2.fill_between(df['date'], df['cumulative'],
|
|
209
|
+
color='#0066ff', alpha=0.2)
|
|
210
|
+
ax2.plot(df['date'], df['cumulative'], color='#0066ff', lw=3)
|
|
211
|
+
ax2.set_title('Bunker Glory: Total Cumulative Growth', color='#0066ff')
|
|
212
|
+
ax3 = fig1.add_subplot(gs1[2])
|
|
213
|
+
pivot = df.assign(week=df['date'].dt.isocalendar().week, day=df['date'].dt.day_name()).pivot_table(index='day', columns='week',
|
|
214
|
+
values='Clones', aggfunc='sum').fillna(0).reindex(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])
|
|
215
|
+
sns.heatmap(pivot, cmap='Greens', ax=ax3,
|
|
216
|
+
cbar=False, lw=2, linecolor='#1a1a1a')
|
|
217
|
+
ax3.set_title('Deployment Matrix: All-Time Activity', color='#33cc33')
|
|
218
|
+
plt.tight_layout()
|
|
219
|
+
plt.savefig('plots/bunker_main_v7.png', dpi=120)
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
print("🤖 Pod 042: Rendering Strategic Dashboards...")
|
|
223
|
+
plt.style.use('dark_background')
|
|
224
|
+
os.makedirs('plots', exist_ok=True)
|
|
225
|
+
|
|
226
|
+
# 数据准备
|
|
227
|
+
df_c = load_clean('ghrs-data/clones_ledger.csv', 'Clones')
|
|
228
|
+
df_v = load_clean('ghrs-data/views_ledger.csv',
|
|
229
|
+
'Views').rename(columns={'uniques': 'uv'})
|
|
230
|
+
|
|
231
|
+
peak_recon()
|
|
232
|
+
intelligence_grid()
|
|
233
|
+
main_dashboard(df_c, df_v)
|
|
234
|
+
|
|
235
|
+
print("✅ Dashboards rendered.")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Bunker Sovereign Intelligence (v8.6)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '45 19 * * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
10
|
+
STATS_TOKEN: ${{ secrets.STATS_TOKEN }}
|
|
11
|
+
REPO: ${{ github.repository }}
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
bunker-operation:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: write
|
|
18
|
+
steps:
|
|
19
|
+
# 💡 步骤 1:先拉取 main 分支,确保我们拿到了所有的 Python 脚本
|
|
20
|
+
- name: Checkout Scripts (Main)
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
path: scripts_repo # 把 main 放到这个子目录
|
|
24
|
+
|
|
25
|
+
# 💡 步骤 2:拉取数据分支,这是我们要工作的地方
|
|
26
|
+
- name: Checkout Data (Stats)
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
ref: github-repo-stats
|
|
30
|
+
path: data_repo # 把数据分支放到这个子目录
|
|
31
|
+
|
|
32
|
+
- name: Setup Python
|
|
33
|
+
uses: actions/setup-python@v4
|
|
34
|
+
with:
|
|
35
|
+
python-version: '3.10'
|
|
36
|
+
|
|
37
|
+
- name: Install Dependencies
|
|
38
|
+
run: pip install matplotlib pandas seaborn scipy numpy requests
|
|
39
|
+
|
|
40
|
+
# 💡 步骤 3:运行脚本,注意现在的相对路径!
|
|
41
|
+
# 我们在当前根目录运行,调用 scripts_repo 里的脚本,去处理 data_repo 里的数据
|
|
42
|
+
- name: "🛰️ Step 1: Data Collection"
|
|
43
|
+
run: |
|
|
44
|
+
cd data_repo
|
|
45
|
+
python ../scripts_repo/.github/scripts/collect/traffic.py
|
|
46
|
+
|
|
47
|
+
- name: "🧠 Step 2: Intelligence Analysis"
|
|
48
|
+
run: |
|
|
49
|
+
cd data_repo
|
|
50
|
+
# 执行“肉机比”扫描与“军衔”聚类分析
|
|
51
|
+
python ../scripts_repo/.github/scripts/analyze/bot_detect.py
|
|
52
|
+
python ../scripts_repo/.github/scripts/analyze/cluster.py
|
|
53
|
+
|
|
54
|
+
- name: "📈 Step 3: Visualization Rendering"
|
|
55
|
+
run: |
|
|
56
|
+
cd data_repo
|
|
57
|
+
# 渲染核心看板
|
|
58
|
+
python ../scripts_repo/.github/scripts/render/dashboard.py
|
|
59
|
+
|
|
60
|
+
- name: "💾 Step 4: Deployment & Sealing"
|
|
61
|
+
run: |
|
|
62
|
+
cd data_repo
|
|
63
|
+
# 物理封存至 README
|
|
64
|
+
python ../scripts_repo/.github/scripts/deploy/publish.py
|
package/example/example.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"columns": [
|
|
22
22
|
{
|
|
23
23
|
"title": "职业",
|
|
24
|
-
"dataIndex": "
|
|
24
|
+
"dataIndex": "profession",
|
|
25
25
|
"type": "text"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"title": "职业类型",
|
|
64
|
-
"dataIndex": "
|
|
64
|
+
"dataIndex": "professionType",
|
|
65
65
|
"type": "tag"
|
|
66
66
|
},
|
|
67
67
|
{
|
|
@@ -70,9 +70,6 @@
|
|
|
70
70
|
"type": "date"
|
|
71
71
|
}
|
|
72
72
|
],
|
|
73
|
-
"rowSelection": true,
|
|
74
|
-
"expandable": true,
|
|
75
|
-
"pagination": true,
|
|
76
73
|
"operation": [
|
|
77
74
|
{
|
|
78
75
|
"label": "查看",
|
|
@@ -86,12 +83,15 @@
|
|
|
86
83
|
"label": "删除",
|
|
87
84
|
"action": "deleteByRecord"
|
|
88
85
|
}
|
|
89
|
-
]
|
|
86
|
+
],
|
|
87
|
+
"pagination": true,
|
|
88
|
+
"expandable": true,
|
|
89
|
+
"rowSelection": true
|
|
90
90
|
},
|
|
91
91
|
"formItems": [
|
|
92
92
|
{
|
|
93
93
|
"label": "转职职业",
|
|
94
|
-
"name": "
|
|
94
|
+
"name": "transferProfession",
|
|
95
95
|
"type": "select"
|
|
96
96
|
},
|
|
97
97
|
{
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"label": "职业类型",
|
|
104
|
-
"name": "
|
|
104
|
+
"name": "professionType",
|
|
105
105
|
"type": "select"
|
|
106
106
|
},
|
|
107
107
|
{
|
|
@@ -115,38 +115,55 @@
|
|
|
115
115
|
"type": "daterange"
|
|
116
116
|
}
|
|
117
117
|
],
|
|
118
|
-
"staticInfo": {
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
{
|
|
122
|
-
"label": "选择转职",
|
|
123
|
-
"value": "selectJob"
|
|
124
|
-
}
|
|
125
|
-
],
|
|
126
|
-
"weaponTypeOptions": [],
|
|
127
|
-
"jobTypeOptions": []
|
|
118
|
+
"staticInfo": {
|
|
119
|
+
"has": false,
|
|
120
|
+
"text": ""
|
|
128
121
|
},
|
|
129
|
-
"
|
|
122
|
+
"optionList": [
|
|
130
123
|
{
|
|
131
|
-
"
|
|
132
|
-
"
|
|
124
|
+
"name": "weaponTypeOptions",
|
|
125
|
+
"options": [
|
|
126
|
+
{
|
|
127
|
+
"label": "光剑",
|
|
128
|
+
"value": "lightSword"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"label": "太刀",
|
|
132
|
+
"value": "katana"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"label": "巨剑",
|
|
136
|
+
"value": "greatSword"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
133
139
|
},
|
|
134
140
|
{
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
|
|
141
|
+
"name": "professionTypeOptions",
|
|
142
|
+
"options": [
|
|
143
|
+
{
|
|
144
|
+
"label": "物理",
|
|
145
|
+
"value": "physical"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"label": "魔法",
|
|
149
|
+
"value": "magical"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"pageStruct": [
|
|
155
|
+
"MySearchForm",
|
|
156
|
+
"FunctionButtonsBlock",
|
|
157
|
+
"MyTable"
|
|
158
|
+
],
|
|
159
|
+
"functionButton": [
|
|
138
160
|
{
|
|
139
161
|
"btn": "批量删除",
|
|
140
162
|
"action": "batchDeleteBySelected"
|
|
141
163
|
},
|
|
142
164
|
{
|
|
143
165
|
"btn": "批量导出",
|
|
144
|
-
"action": "
|
|
166
|
+
"action": "batchExportBySelected"
|
|
145
167
|
}
|
|
146
|
-
],
|
|
147
|
-
"pageStruct": [
|
|
148
|
-
"MySearchForm",
|
|
149
|
-
"FunctionButtonsBlock",
|
|
150
|
-
"MyTable"
|
|
151
168
|
]
|
|
152
169
|
}
|
package/package.json
CHANGED
package/src/core/context.js
CHANGED
|
@@ -45,16 +45,56 @@ module.exports = {
|
|
|
45
45
|
}
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const cliCtx = {
|
|
49
|
+
options,
|
|
50
|
+
template,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const llmCtx = {
|
|
54
|
+
generateMock,
|
|
55
|
+
recognizePage,
|
|
56
|
+
alignResponseFields
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const utilsCtx = {
|
|
60
|
+
menus,
|
|
61
|
+
config,
|
|
62
|
+
language,
|
|
63
|
+
unwrapSignal,
|
|
64
|
+
isQuerySignal,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handlerCtx = {
|
|
68
|
+
apiHandler,
|
|
69
|
+
pageHandler,
|
|
70
|
+
partHandler
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const compileCtx = {
|
|
74
|
+
index,
|
|
75
|
+
resource
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const promptCtx = {
|
|
52
79
|
pagePrompt: require(`../prompts/${template}/watch-page.js`),
|
|
53
80
|
partPrompt: require(`../prompts/${template}/watch-part.js`),
|
|
54
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const templateCtx = {
|
|
55
84
|
indexTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8')),
|
|
56
85
|
resourceTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8')),
|
|
57
86
|
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
instance = {
|
|
90
|
+
...cliCtx,
|
|
91
|
+
...llmCtx,
|
|
92
|
+
...utilsCtx,
|
|
93
|
+
...promptCtx,
|
|
94
|
+
...handlerCtx,
|
|
95
|
+
...compileCtx,
|
|
96
|
+
...templateCtx,
|
|
97
|
+
}
|
|
58
98
|
return instance
|
|
59
99
|
},
|
|
60
100
|
|
|
@@ -46,6 +46,7 @@ const resource = ({ pageConfig, resourceTpl }) => {
|
|
|
46
46
|
const index = ({ config, fileName, indexTpl, pageConfig }) => {
|
|
47
47
|
|
|
48
48
|
const hasTabs = pageConfig.tabs?.length > 0
|
|
49
|
+
const hasPagination = pageConfig.table.pagination
|
|
49
50
|
const hasFormItems = pageConfig.formItems?.length > 0
|
|
50
51
|
const hasOperate = pageConfig.table?.operation?.length > 0
|
|
51
52
|
const hasImageColumn = pageConfig.table?.columns?.some(item => item.type === 'image')
|
|
@@ -69,17 +70,18 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
|
|
|
69
70
|
columnsValue,
|
|
70
71
|
hasFormItems,
|
|
71
72
|
renderAction,
|
|
73
|
+
hasPagination,
|
|
72
74
|
hasImageColumn,
|
|
73
75
|
functionButtons,
|
|
74
76
|
needRenderAction,
|
|
75
77
|
tabs: pageConfig.tabs,
|
|
76
78
|
responseSuccess: config.responseSuccess,
|
|
77
79
|
hasExpandable: pageConfig.table.expandable,
|
|
78
|
-
hasPagination: pageConfig.table.pagination,
|
|
79
80
|
staticInfoText: pageConfig.staticInfo?.text,
|
|
80
81
|
operations: pageConfig.table.operation || [],
|
|
81
82
|
hasRowSelection: pageConfig.table.rowSelection,
|
|
82
83
|
formItems: hasFormItems ? (hasTabs ? 'formItems[activeKey]' : 'formItems') : '[]',
|
|
84
|
+
initParams: `{ ${hasTabs ? 'type: tabs[0].key ,' : ''}${hasPagination ? 'pageNo: 1 , pageSize: 10' : ''}}`,
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
const bodyCode = indexTpl(viewData)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const initialParams = {{
|
|
1
|
+
const initialParams = {{initParams}}
|
|
2
2
|
|
|
3
3
|
const query = formatQuery(Object.fromEntries(new URLSearchParams(location.search).entries()), {{formItems}})
|
|
4
4
|
|
|
5
5
|
const { total, columns, loading, dataSource, search, setSearch, setDataSource, refresh } = useTableQuery({
|
|
6
6
|
api: async params => await request('/api/{{fileName}}', { method: 'POST', body: params }),
|
|
7
|
-
initialParams: {
|
|
7
|
+
initialParams: { ...initialParams, ...query },
|
|
8
8
|
cols:tableColumns,
|
|
9
9
|
formatResponse: response => {
|
|
10
10
|
return {
|
|
@@ -21,7 +21,7 @@ export default props => {
|
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<Card {{#if hasTabs}}tabList={tabs} activeTabKey={activeKey} onTabChange={onTabChange}{{/if}}>
|
|
24
|
-
{{
|
|
24
|
+
{{~#if hasImageColumn}}{img.visible && <MyImage img={img} setImg={setImg}/>}{{~/if}}
|
|
25
25
|
{modal.visible && <MyModalForm {...modal} submit={modalSubmit} setModal={setModal} />}
|
|
26
26
|
{{#each pageStruct as |partialName|}}
|
|
27
27
|
{{> (partialName) @root }}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
name: Bunker Sovereign Intelligence (v7.0)
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
schedule:
|
|
5
|
-
- cron: '45 19 * * *' # 北京时间凌晨 03:45
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
|
|
8
|
-
env:
|
|
9
|
-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
all-seeing-eye:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
permissions:
|
|
15
|
-
contents: write
|
|
16
|
-
steps:
|
|
17
|
-
- name: Checkout Data Branch
|
|
18
|
-
uses: actions/checkout@v4
|
|
19
|
-
with:
|
|
20
|
-
ref: github-repo-stats
|
|
21
|
-
token: ${{ secrets.STATS_TOKEN }}
|
|
22
|
-
fetch-depth: 0
|
|
23
|
-
|
|
24
|
-
- name: Setup Plotting Environment
|
|
25
|
-
run: |
|
|
26
|
-
python -m pip install --upgrade pip
|
|
27
|
-
pip install matplotlib pandas seaborn
|
|
28
|
-
|
|
29
|
-
# 2. 全频道数据搜刮协议 [逻辑升级:专业 JSON 构筑]
|
|
30
|
-
- name: Full Spectrum Data Mining
|
|
31
|
-
run: |
|
|
32
|
-
echo "🤖 Pod 042: 启动‘全领域信号搜刮协议’..."
|
|
33
|
-
AUTH="-H 'Authorization: token ${{ secrets.STATS_TOKEN }}' -H 'Accept: application/vnd.github.v3+json'"
|
|
34
|
-
API="https://api.github.com/repos/${{ github.repository }}"
|
|
35
|
-
|
|
36
|
-
eval curl -s $AUTH $API/traffic/clones > raw_clones.json
|
|
37
|
-
eval curl -s $AUTH $API/traffic/popular/referrers > raw_referrers.json
|
|
38
|
-
eval curl -s $AUTH $API/traffic/views > raw_views.json
|
|
39
|
-
eval curl -s $AUTH $API > raw_repo.json
|
|
40
|
-
|
|
41
|
-
mkdir -p ghrs-data
|
|
42
|
-
|
|
43
|
-
# --- 物理焊接:Views 账本 ---
|
|
44
|
-
echo "date,views,uniques" > /tmp/new_views.csv
|
|
45
|
-
jq -r '.views[] | "\(.timestamp[0:10]),\(.count),\(.uniques)"' raw_views.json >> /tmp/new_views.csv
|
|
46
|
-
|
|
47
|
-
V_LEDGER="ghrs-data/views_ledger.csv"
|
|
48
|
-
if [ ! -f "$V_LEDGER" ]; then
|
|
49
|
-
cp /tmp/new_views.csv "$V_LEDGER"
|
|
50
|
-
else
|
|
51
|
-
(tail -n +2 "$V_LEDGER"; tail -n +2 /tmp/new_views.csv) | grep -v "date" | \
|
|
52
|
-
sed 's/\//-/g' | awk -F',' '!a[$1]++' | sort -r > /tmp/final_v_data.csv
|
|
53
|
-
echo "date,views,uniques" > "$V_LEDGER"
|
|
54
|
-
cat /tmp/final_v_data.csv >> "$V_LEDGER"
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
# --- 物理焊接:Clones 账本 ---
|
|
58
|
-
echo "date,clones,uniques" > /tmp/new_clones.csv
|
|
59
|
-
jq -r '.clones[] | "\(.timestamp[0:10]),\(.count),\(.uniques)"' raw_clones.json >> /tmp/new_clones.csv
|
|
60
|
-
|
|
61
|
-
C_LEDGER="ghrs-data/clones_ledger.csv"
|
|
62
|
-
if [ ! -f "$C_LEDGER" ]; then
|
|
63
|
-
cp /tmp/new_clones.csv "$C_LEDGER"
|
|
64
|
-
else
|
|
65
|
-
(tail -n +2 "$C_LEDGER"; tail -n +2 /tmp/new_clones.csv) | grep -v "date" | \
|
|
66
|
-
sed 's/\//-/g' | awk -F',' '!a[$1]++' | sort -r > /tmp/final_c_data.csv
|
|
67
|
-
echo "date,clones,uniques" > "$C_LEDGER"
|
|
68
|
-
cat /tmp/final_c_data.csv >> "$C_LEDGER"
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
# --- 战绩核算 (使用高级变量防护) ---
|
|
72
|
-
TOTAL_CLONES=$(grep -v "date" "$C_LEDGER" | awk -F',' '{sum+=$2} END {print sum}')
|
|
73
|
-
TOTAL_CLONES=${TOTAL_CLONES:-0}
|
|
74
|
-
STARS=$(jq '.stargazers_count // 0' raw_repo.json)
|
|
75
|
-
FORKS=$(jq '.forks_count // 0' raw_repo.json)
|
|
76
|
-
|
|
77
|
-
# 💡 偷师点:用 jq 物理合成 JSON,彻底杜绝语法错误
|
|
78
|
-
jq -n \
|
|
79
|
-
--arg tc "$TOTAL_CLONES" \
|
|
80
|
-
--arg st "$STARS" \
|
|
81
|
-
--arg fk "$FORKS" \
|
|
82
|
-
'{total_clones: ($tc|tonumber), stars: ($st|tonumber), forks: ($fk|tonumber)}' > bunker-stats.json
|
|
83
|
-
|
|
84
|
-
echo "✅ 地堡 7.4 核心战绩封存达成:$TOTAL_CLONES"
|
|
85
|
-
|
|
86
|
-
# 3. 🧬 【地堡 7.2 终极修复版】:带强力类型转换的看板构筑
|
|
87
|
-
- name: Construct Sovereign Dashboard
|
|
88
|
-
run: |
|
|
89
|
-
echo "🤖 Pod 042: 执行‘全频道看板构筑协议 [类型强转版]’..."
|
|
90
|
-
python3 <<EOF
|
|
91
|
-
import pandas as pd
|
|
92
|
-
import matplotlib.pyplot as plt
|
|
93
|
-
import seaborn as sns
|
|
94
|
-
import os
|
|
95
|
-
|
|
96
|
-
# 💡 偷师点:定义一个“暴力清洗”函数,确保数据类型绝对纯净
|
|
97
|
-
def load_hard_clean_csv(path, col_name):
|
|
98
|
-
df = pd.read_csv(path)
|
|
99
|
-
# 1. 强制转换日期,指定格式并踢走非日期行
|
|
100
|
-
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d', errors='coerce')
|
|
101
|
-
df = df.dropna(subset=['date'])
|
|
102
|
-
|
|
103
|
-
# 2. 🔥 核心修正:强制将数据列转换为数字,脏数据变 NaN 然后填 0
|
|
104
|
-
target_col = col_name.lower()
|
|
105
|
-
if target_col in df.columns:
|
|
106
|
-
df[col_name] = pd.to_numeric(df[target_col], errors='coerce').fillna(0)
|
|
107
|
-
|
|
108
|
-
return df[['date', col_name]]
|
|
109
|
-
|
|
110
|
-
try:
|
|
111
|
-
# 分别读取并清洗两个账本
|
|
112
|
-
df_c = load_hard_clean_csv('ghrs-data/clones_ledger.csv', 'Clones')
|
|
113
|
-
df_v = load_hard_clean_csv('ghrs-data/views_ledger.csv', 'Views')
|
|
114
|
-
|
|
115
|
-
# 物理对齐与合并(按日期合并)
|
|
116
|
-
df = pd.merge(df_c, df_v, on='date', how='outer').fillna(0)
|
|
117
|
-
df = df.sort_values('date')
|
|
118
|
-
|
|
119
|
-
# 确保合并后的数据也是 float 类型,防止绘图器罢工
|
|
120
|
-
df['Clones'] = df['Clones'].astype(float)
|
|
121
|
-
df['Views'] = df['Views'].astype(float)
|
|
122
|
-
df['cumulative'] = df['Clones'].cumsum()
|
|
123
|
-
|
|
124
|
-
# --- 开始绘图 ---
|
|
125
|
-
plt.style.use('dark_background')
|
|
126
|
-
fig = plt.figure(figsize=(12, 10))
|
|
127
|
-
gs = fig.add_gridspec(3, 1)
|
|
128
|
-
|
|
129
|
-
# 子图 1:Views vs Clones
|
|
130
|
-
ax1 = fig.add_subplot(gs[0])
|
|
131
|
-
plot_df = df.tail(30)
|
|
132
|
-
ax1.plot(plot_df['date'], plot_df['Views'], color='#ffffff', label='Views (Web)', alpha=0.5, linestyle='--')
|
|
133
|
-
ax1.plot(plot_df['date'], plot_df['Clones'], color='#33cc33', label='Clones (Terminal)', linewidth=2)
|
|
134
|
-
ax1.fill_between(plot_df['date'], plot_df['Clones'], color='#33cc33', alpha=0.1)
|
|
135
|
-
ax1.set_title('Strategic Recon: Web Visitors vs Terminal Commandos (Last 30 Days)', color='#33cc33', pad=15)
|
|
136
|
-
ax1.legend()
|
|
137
|
-
ax1.grid(color='#444', linestyle='--', alpha=0.3)
|
|
138
|
-
|
|
139
|
-
# 子图 2:累计荣光抛物线
|
|
140
|
-
ax2 = fig.add_subplot(gs[1])
|
|
141
|
-
ax2.fill_between(df['date'], df['cumulative'], color='#0066ff', alpha=0.2)
|
|
142
|
-
ax2.plot(df['date'], df['cumulative'], color='#0066ff', linewidth=3)
|
|
143
|
-
ax2.set_title('Bunker Glory: Total Cumulative Clones', color='#0066ff', pad=15)
|
|
144
|
-
ax2.grid(color='#444', linestyle=':', alpha=0.3)
|
|
145
|
-
|
|
146
|
-
# 子图 3:作战热力图
|
|
147
|
-
ax3 = fig.add_subplot(gs[2])
|
|
148
|
-
df['week'] = df['date'].dt.isocalendar().week
|
|
149
|
-
df['day'] = df['date'].dt.day_name()
|
|
150
|
-
pivot = df.pivot_table(index='day', columns='week', values='Clones', aggfunc='sum').fillna(0)
|
|
151
|
-
days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
|
152
|
-
pivot = pivot.reindex(days_order)
|
|
153
|
-
sns.heatmap(pivot, cmap='Greens', ax=ax3, cbar=False, annot=False, linewidths=2, linecolor='#1a1a1a')
|
|
154
|
-
ax3.set_title('Deployment Matrix: All-Time Activity', color='#33cc33', pad=15)
|
|
155
|
-
ax3.set_xlabel('Weeks of the Year')
|
|
156
|
-
ax3.set_ylabel('')
|
|
157
|
-
|
|
158
|
-
os.makedirs('plots', exist_ok=True)
|
|
159
|
-
plt.tight_layout()
|
|
160
|
-
plt.savefig('plots/bunker_dashboard.png', dpi=120)
|
|
161
|
-
print(f"✅ 视觉矩阵重构成功!当前全量战绩:{df['cumulative'].max()}")
|
|
162
|
-
except Exception as e:
|
|
163
|
-
print(f"❌ 构筑失败:检测到物理层逻辑冲突: {e}")
|
|
164
|
-
import traceback
|
|
165
|
-
traceback.print_exc()
|
|
166
|
-
exit(1)
|
|
167
|
-
EOF
|
|
168
|
-
|
|
169
|
-
# 4. [报表装修]
|
|
170
|
-
- name: Assemble Sovereignty Report
|
|
171
|
-
run: |
|
|
172
|
-
STATS="bunker-stats.json"
|
|
173
|
-
TOTAL=$(jq '.total_clones' $STATS)
|
|
174
|
-
STARS=$(jq '.stars' $STATS)
|
|
175
|
-
FORKS=$(jq '.forks' $STATS)
|
|
176
|
-
|
|
177
|
-
echo "# 🛰️ 地堡终极运行报告 (Sovereign Dashboard)" > README.md
|
|
178
|
-
echo "## 📊 核心战力指标" >> README.md
|
|
179
|
-
echo "- **物理克隆总计**: \`$TOTAL\` 次" >> README.md
|
|
180
|
-
echo "- **当前社群声望**: ⭐ \`$STARS\` / 🍴 \`$FORKS\`" >> README.md
|
|
181
|
-
echo "- **地堡协议防护**: AGPL-3.0 生效中" >> README.md
|
|
182
|
-
echo "" >> README.md
|
|
183
|
-
|
|
184
|
-
echo "### 🖥️ 实时作战看板 (Visual Radar)" >> README.md
|
|
185
|
-
echo "" >> README.md
|
|
186
|
-
echo "" >> README.md
|
|
187
|
-
|
|
188
|
-
echo "#### 📡 全球流量来源 (Top Referrers)" >> README.md
|
|
189
|
-
echo "| 来源站点 | 访问量 | 独立指挥官 |" >> README.md
|
|
190
|
-
echo "| :--- | :--- | :--- |" >> README.md
|
|
191
|
-
if [ -s raw_referrers.json ]; then
|
|
192
|
-
jq -r '.[] | "| \(.referrer) | \(.count) | \(.uniques) |"' raw_referrers.json >> README.md
|
|
193
|
-
else
|
|
194
|
-
echo "| 信号屏蔽中 | - | - |" >> README.md
|
|
195
|
-
fi
|
|
196
|
-
echo "" >> README.md
|
|
197
|
-
echo "> 📡 数据已物理封存:$(date +'%Y-%m-%d %H:%M:%S') CST | Glory to Mankind." >> README.md
|
|
198
|
-
|
|
199
|
-
# 5. 封存推送
|
|
200
|
-
- name: Final Sealing
|
|
201
|
-
run: |
|
|
202
|
-
git config user.name github-actions
|
|
203
|
-
git config user.email github-actions@github.com
|
|
204
|
-
git add -A
|
|
205
|
-
TOTAL=$(jq '.total_clones' bunker-stats.json)
|
|
206
|
-
git commit -m "chore: sovereign update v7.0 - total $TOTAL" || echo "no changes"
|
|
207
|
-
git push origin github-repo-stats --force
|