schema.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. from __future__ import annotations
  2. from typing import Any, Literal, TypedDict
  3. RelationKind = Literal[
  4. "single-event",
  5. "event-sequence",
  6. "business-record",
  7. "aggregate",
  8. "calculated",
  9. "missing",
  10. ]
  11. Completeness = Literal["complete", "partial", "missing"]
  12. class CardDataSource(TypedDict, total=False):
  13. kind: Literal["database", "runtime-event", "artifact", "calculation", "log-anchor"]
  14. label: str
  15. ref: str
  16. fieldPath: str
  17. class CardDataField(TypedDict):
  18. id: str
  19. label: str
  20. value: Any
  21. source: CardDataSource
  22. relation: Literal[
  23. "direct-input",
  24. "previous-result",
  25. "standing-constraint",
  26. "explicit-basis",
  27. "available-upstream",
  28. "persisted-output",
  29. "run-output",
  30. ]
  31. completeness: Completeness
  32. class RuntimeUnit(TypedDict, total=False):
  33. id: str
  34. label: str
  35. status: str
  36. summary: str
  37. input: Any
  38. output: Any
  39. durationMs: int | None
  40. eventRef: str
  41. completeness: Completeness
  42. startedAt: str | None
  43. endedAt: str | None
  44. def field(
  45. field_id: str,
  46. label: str,
  47. value: Any,
  48. *,
  49. source_kind: str,
  50. source_label: str,
  51. source_ref: str | None = None,
  52. field_path: str | None = None,
  53. relation: str,
  54. completeness: Completeness | None = None,
  55. ) -> CardDataField:
  56. missing = value is None or value == "" or value == [] or value == {}
  57. source: CardDataSource = {"kind": source_kind, "label": source_label} # type: ignore[typeddict-item]
  58. if source_ref:
  59. source["ref"] = source_ref
  60. if field_path:
  61. source["fieldPath"] = field_path
  62. return {
  63. "id": field_id,
  64. "label": label,
  65. "value": value,
  66. "source": source,
  67. "relation": relation, # type: ignore[typeddict-item]
  68. "completeness": completeness or ("missing" if missing else "complete"),
  69. }
  70. def payload(
  71. card_kind: str,
  72. relation_kind: RelationKind,
  73. *,
  74. inputs: list[CardDataField] | None = None,
  75. units: list[RuntimeUnit] | None = None,
  76. outputs: list[CardDataField] | None = None,
  77. runtime_summary: str = "",
  78. calculation: str | None = None,
  79. notices: list[dict[str, str]] | None = None,
  80. business_modules: list[dict[str, Any]] | None = None,
  81. card_fields: list[dict[str, Any]] | None = None,
  82. completeness: Completeness | None = None,
  83. ) -> dict[str, Any]:
  84. inputs = inputs or []
  85. units = units or []
  86. outputs = outputs or []
  87. all_fields = [*inputs, *outputs]
  88. available = [item for item in all_fields if item["completeness"] != "missing"]
  89. if completeness is None:
  90. if not available and not units:
  91. completeness = "missing"
  92. elif any(item["completeness"] != "complete" for item in all_fields) or any(
  93. item.get("completeness") != "complete" for item in units
  94. ):
  95. completeness = "partial"
  96. else:
  97. completeness = "complete"
  98. if business_modules is None:
  99. business_modules = [
  100. {"id": "business-inputs", "title": "业务输入", "sourceIds": [item["id"] for item in inputs]},
  101. {"id": "runtime", "title": "运行过程 / I/O", "sourceIds": [item["id"] for item in all_fields]},
  102. {"id": "business-outputs", "title": "业务输出", "sourceIds": [item["id"] for item in outputs]},
  103. ]
  104. if card_fields is None:
  105. headline = next((item for item in outputs if item["completeness"] != "missing"), None)
  106. if headline is None:
  107. headline = next((item for item in inputs if item["completeness"] != "missing"), None)
  108. card_fields = [] if headline is None else [{
  109. "key": "headline",
  110. "label": headline["label"],
  111. "sourceIds": [headline["id"]],
  112. "transform": "使用结构化业务字段;卡片仅做视觉截行",
  113. }]
  114. used = {
  115. source_id
  116. for item in [*business_modules, *card_fields]
  117. for source_id in item.get("sourceIds", [])
  118. }
  119. runtime: dict[str, Any] = {"summary": runtime_summary, "units": units}
  120. if calculation:
  121. runtime["calculation"] = calculation
  122. return {
  123. "cardKind": card_kind,
  124. "relationKind": relation_kind,
  125. "completeness": completeness,
  126. "businessInputs": inputs,
  127. "runtime": runtime,
  128. "businessOutputs": outputs,
  129. "displayUse": {
  130. "businessModules": business_modules,
  131. "cardFields": card_fields,
  132. "unusedSourceIds": [item["id"] for item in all_fields if item["id"] not in used],
  133. },
  134. "notices": notices or [],
  135. }