setup_cloud.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/env python3
  2. """云端准备脚本(开发工具,不属于前端交付物)。
  3. 做的事:
  4. 1. 补齐字段:KfArtifact.layer(四层归属)、KfProject.layer、KfArtifact.sourceIdx(原材料层样例指针)
  5. 2. 补数:残留的 KfProbe 探针 -> 改名为标准的 KfSource(原材料层样例);
  6. 原文证据为空、但 evidenceLevel 标为 verified 的成果 -> 降为 supported(避免"无原文却称已验证")
  7. 3. 重建归纳:按 derivedFrom(-card + source 混合引用)重新计算 derivedArtifacts / derivedSources /
  8. owners / citedWith / evidenceQuotes / evidenceUnaligned / mentionCount
  9. 4. 验证 PARSE_MASTER_KEY(环境变量)有效
  10. 运行时:PARSE_MASTER_KEY=... python3 tools/setup_cloud.py
  11. --dry-run 只打印不写。
  12. """
  13. import json
  14. import os
  15. import sys
  16. import urllib.error
  17. import urllib.request
  18. BASE = "https://kangshifu.fmode.cn/parse"
  19. APP_ID = "FKieRbIoGt_main"
  20. MK = os.environ.get("PARSE_MASTER_KEY", "")
  21. DRY = "--dry-run" in sys.argv
  22. H = {"X-Parse-Application-Id": APP_ID, "Content-Type": "application/json"}
  23. if MK:
  24. H["X-Parse-Master-Key"] = MK
  25. def call(method, path, body=None):
  26. data = json.dumps(body, ensure_ascii=False).encode() if body is not None else None
  27. req = urllib.request.Request(BASE + path, data=data, headers=H, method=method)
  28. try:
  29. with urllib.request.urlopen(req, timeout=30) as r:
  30. return json.loads(r.read().decode() or "{}")
  31. except urllib.error.HTTPError as e:
  32. return {"_error": e.code, "_body": e.read().decode()[:400]}
  33. def fetch(cls):
  34. return call("GET", f"/classes/{cls}?limit=1000&order=createdAt").get("results", [])
  35. def ensure_fields():
  36. for cls, fields in [
  37. ("KfArtifact", {"layer": {"type": "String"}, "sourceIdx": {"type": "Number"}}),
  38. ("KfProject", {"layer": {"type": "String"}}),
  39. ]:
  40. if DRY:
  41. print("[dry] schema", cls, list(fields))
  42. continue
  43. r = call("PUT", f"/schemas/{cls}", {"className": cls, "fields": fields})
  44. print("schema", cls, "ok" if "fields" in r else r)
  45. def cleanup_probes():
  46. probes = fetch("KfProbe")
  47. for p in probes:
  48. title = "原材料层样例 · 探针记录"
  49. body = {
  50. "layer": "raw",
  51. "sourceType": "probe",
  52. "title": title,
  53. "summary": "开发期连通性探针,已转为标准原材料层样例记录。",
  54. "rawText": p.get("note", ""),
  55. "sources": [],
  56. "tags": [],
  57. "payload": {},
  58. "meta": {"migratedFrom": "KfProbe", "kfProbeId": p["objectId"]},
  59. }
  60. if DRY:
  61. print("[dry] probe -> KfSource", p["objectId"])
  62. continue
  63. r = call("POST", "/classes/KfSource", body)
  64. if "objectId" in r:
  65. call("DELETE", f"/classes/KfProbe/{p['objectId']}")
  66. print("probe", p["objectId"], "->", r["objectId"])
  67. def fix_evidence_levels():
  68. for a in fetch("KfArtifact"):
  69. if not a.get("sources") and a.get("evidenceLevel") == "verified":
  70. if DRY:
  71. print("[dry] downgrade", a["title"])
  72. continue
  73. call("PUT", f"/classes/KfArtifact/{a['objectId']}", {"evidenceLevel": "supported"})
  74. print("downgraded verified->supported:", a["title"])
  75. def rebuild_insights():
  76. arts = {a["objectId"]: a for a in fetch("KfArtifact")}
  77. srcs = {s["objectId"]: s for s in fetch("KfSource")}
  78. for ins in fetch("KfInsight"):
  79. pointed = ins.get("derivedFrom") or []
  80. art_ids = [i for i in pointed if i in arts]
  81. src_ids = [i for i in pointed if i in srcs]
  82. owners = sorted({arts[i].get("ownerName") for i in art_ids if arts[i].get("ownerName")})
  83. lvls = sorted({arts[i].get("evidenceLevel") for i in art_ids if arts[i].get("evidenceLevel")})
  84. cited_with = sorted({ins["objectId"] for a in arts.values()
  85. if set(a.get("sources") or []) & set(art_ids)} - {ins["objectId"]})
  86. upd = {
  87. "layer": "sediment",
  88. "derivedArtifacts": art_ids,
  89. "derivedSources": src_ids,
  90. "owners": owners,
  91. "ownerCount": len(owners),
  92. "crossRefCount": ins.get("crossRefCount", 0),
  93. "mentionCount": ins.get("crossRefCount", 0),
  94. "evidenceQuoteCount": len(art_ids),
  95. "evidenceUnaligned": "verified" not in lvls,
  96. "citedWith": cited_with,
  97. }
  98. if DRY:
  99. print("[dry] insight", ins["title"], upd)
  100. continue
  101. r = call("PUT", f"/classes/KfInsight/{ins['objectId']}", upd)
  102. print("insight", ins["title"], "ok" if "updatedAt" in r else r)
  103. if __name__ == "__main__":
  104. if not MK:
  105. sys.exit("PARSE_MASTER_KEY 未设置")
  106. print("masterKey auth:", "ok" if "results" in call("GET", "/classes/KfProject?limit=1") else "FAILED")
  107. ensure_fields()
  108. cleanup_probes()
  109. fix_evidence_levels()
  110. rebuild_insights()