import os, re, sys, time
from supabase import create_client
def log(msg):
print(msg, flush=True)
url = os.environ.get("SUPABASE_URL")
key = os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
supabase = create_client(url, key)
# --- 扩展后的字段规则字典 (同步 JSON 补齐字段) ---
FIELD_RULES = {
'latin_name': {'min': 5, 'max': 80},
'common_name': {'min': 2, 'max': 100},
'family': {'min': 3, 'max': 50},
'title': {'min': 10, 'max': 200},
'usda_hardiness': {'min': 1, 'max': 15},
'known_hazards': {'min': 4, 'max': 2000},
'habitats': {'min': 5, 'max': 1000},
'range': {'min': 5, 'max': 1000},
'physical_characteristics': {'min': 20, 'max': 5000},
'edible_uses': {'min': 10, 'max': 5000},
'medicinal_uses': {'min': 10, 'max': 5000},
'other_uses': {'min': 10, 'max': 5000},
'cultivation_details': {'min': 20, 'max': 8000},
'propagation': {'min': 20, 'max': 4000},
# --- 新增补齐字段 ---
'weed_potential': {'min': 2, 'max': 50},
'found_in': {'min': 5, 'max': 5000},
'conservation_status': {'min': 5, 'max': 500},
'special_uses': {'min': 5, 'max': 1000},
'author': {'min': 1, 'max': 50}
}
def analyze_value_pro(field_name, val):
"""深度检测逻辑"""
raw_s = str(val or "")
s = raw_s.strip()
is_empty = not s or s.startswith("[Empty") or s.lower() == "none"
if is_empty:
return True, False, 0
# 1. 乱码检测
has_garbage = bool(re.search(r'[ÂÃÅÐÑÒÓÔÕÖר]', s))
# 2. 离群值检测
is_outlier = False
length = len(s)
if field_name in FIELD_RULES:
rule = FIELD_RULES[field_name]
if length < rule['min'] or length > rule['max']:
is_outlier = True
# 结构异常判定:HTML残留或异常空白
if " " in raw_s or "
" in raw_s.lower():
is_outlier = True
return False, (has_garbage or is_outlier), length
def run_bs4_audit():
target_columns = list(FIELD_RULES.keys())
stats = {col: {'filled': 0, 'empty': 0, 'outlier': 0, 'len_sum': 0, 'score_sum': 0} for col in target_columns}
log(f"\n[{time.strftime('%H:%M:%S')}] 🛡️ 启动全字段深度扫描 (含新增字段)...")
offset = 0
limit = 200
while True:
# 动态获取字段
res = supabase.table("bs4_plants").select(",".join(target_columns)).range(offset, offset + limit - 1).execute()
if not res.data: break
for row in res.data:
for col in target_columns:
is_emp, is_bad, length = analyze_value_pro(col, row.get(col))
if is_emp:
stats[col]['empty'] += 1
else:
stats[col]['filled'] += 1
stats[col]['len_sum'] += length
if is_bad:
stats[col]['outlier'] += 1
# 质量评分
score = 0 if is_emp else (50 if is_bad else 100)
stats[col]['score_sum'] += score
offset += len(res.data)
# --- 输出可视化表格 ---
log("\n" + "="*100)
log(f"{'字段名称':<25} | {'填充':<6} | {'异常/离群':<10} | {'空缺':<6} | {'均长':<6} | {'健康分'}")
log("-" * 100)
report_batch = []
sorted_cols = sorted(stats.items(), key=lambda x: (x[1]['score_sum']/offset if offset>0 else 0))
for col, data in sorted_cols:
avg_len = int(data['len_sum'] / data['filled']) if data['filled'] > 0 else 0
total_score = round(data['score_sum'] / offset, 2)
log(f"{col:<25} | {data['filled']:<6} | {data['outlier']:<10} | {data['empty']:<6} | {avg_len:<6} | {total_score}")
report_batch.append({
"field_name": col,
"total_records": offset,
"filled_count": data['filled'],
"empty_count": data['empty'],
"dirty_count": data['outlier'],
"avg_char_count": avg_len,
"quality_score": total_score,
"updated_at": "now()"
})
# 回写到统计表
supabase.table("bs4_field_quality_report").upsert(report_batch).execute()
log("="*100)
log(f"✅ 巡检完成。共处理 {offset} 条记录。")
if __name__ == "__main__":
# 保持之前商定的逻辑:启动时运行一遍。
# 如果想手动触发,重启 Space 即可。
try:
run_bs4_audit()
except Exception as e:
log(f"❌ 运行报错: {e}")