pretensor 0.1.0__py3-none-any.whl

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.
Files changed (198) hide show
  1. pretensor/__init__.py +50 -0
  2. pretensor/benchmark/__init__.py +54 -0
  3. pretensor/benchmark/cli.py +294 -0
  4. pretensor/benchmark/fixtures.py +84 -0
  5. pretensor/benchmark/l1/__init__.py +23 -0
  6. pretensor/benchmark/l1/metrics.py +141 -0
  7. pretensor/benchmark/l1/pipeline.py +188 -0
  8. pretensor/benchmark/l1/runner.py +245 -0
  9. pretensor/benchmark/l2/__init__.py +27 -0
  10. pretensor/benchmark/l2/gold.py +236 -0
  11. pretensor/benchmark/l2/metrics.py +146 -0
  12. pretensor/benchmark/l2/pipeline.py +124 -0
  13. pretensor/benchmark/l2/runner.py +530 -0
  14. pretensor/benchmark/l3/__init__.py +73 -0
  15. pretensor/benchmark/l3/agent.py +316 -0
  16. pretensor/benchmark/l3/db.py +188 -0
  17. pretensor/benchmark/l3/gold.py +85 -0
  18. pretensor/benchmark/l3/llm_client.py +395 -0
  19. pretensor/benchmark/l3/mcp_client.py +357 -0
  20. pretensor/benchmark/l3/pretensor_runner.py +456 -0
  21. pretensor/benchmark/l3/prompt.py +132 -0
  22. pretensor/benchmark/l3/runner.py +358 -0
  23. pretensor/benchmark/l3/sql_equivalence.py +176 -0
  24. pretensor/benchmark/release_gate.py +448 -0
  25. pretensor/benchmark/results.py +298 -0
  26. pretensor/benchmark/runner.py +109 -0
  27. pretensor/cli/__init__.py +1 -0
  28. pretensor/cli/commands/_source_runner.py +147 -0
  29. pretensor/cli/commands/analyze.py +201 -0
  30. pretensor/cli/commands/connections/__init__.py +7 -0
  31. pretensor/cli/commands/connections/add_remove.py +126 -0
  32. pretensor/cli/commands/connections/register.py +12 -0
  33. pretensor/cli/commands/export.py +131 -0
  34. pretensor/cli/commands/index.py +559 -0
  35. pretensor/cli/commands/list.py +76 -0
  36. pretensor/cli/commands/quickstart.py +207 -0
  37. pretensor/cli/commands/reindex.py +646 -0
  38. pretensor/cli/commands/semantic.py +190 -0
  39. pretensor/cli/commands/serve.py +144 -0
  40. pretensor/cli/commands/sync_grants.py +149 -0
  41. pretensor/cli/commands/validate.py +176 -0
  42. pretensor/cli/config_file.py +442 -0
  43. pretensor/cli/constants.py +10 -0
  44. pretensor/cli/dbt_enrichment.py +96 -0
  45. pretensor/cli/main.py +109 -0
  46. pretensor/cli/paths.py +43 -0
  47. pretensor/cli/plugin.py +52 -0
  48. pretensor/config.py +226 -0
  49. pretensor/connectors/__init__.py +29 -0
  50. pretensor/connectors/base.py +165 -0
  51. pretensor/connectors/bigquery.py +468 -0
  52. pretensor/connectors/inspect.py +321 -0
  53. pretensor/connectors/lineage_sqlglot.py +97 -0
  54. pretensor/connectors/models.py +130 -0
  55. pretensor/connectors/mysql.py +402 -0
  56. pretensor/connectors/pg_array_parse.py +53 -0
  57. pretensor/connectors/postgres.py +938 -0
  58. pretensor/connectors/registry.py +93 -0
  59. pretensor/connectors/snapshot.py +244 -0
  60. pretensor/connectors/snowflake.py +908 -0
  61. pretensor/core/__init__.py +1 -0
  62. pretensor/core/builder.py +307 -0
  63. pretensor/core/dsn_crypto.py +51 -0
  64. pretensor/core/graph_schema_manager.py +246 -0
  65. pretensor/core/graph_store.py +1226 -0
  66. pretensor/core/ids.py +101 -0
  67. pretensor/core/portable_export.py +276 -0
  68. pretensor/core/query_runner.py +67 -0
  69. pretensor/core/registry.py +209 -0
  70. pretensor/core/schema.py +473 -0
  71. pretensor/core/secure_io.py +93 -0
  72. pretensor/core/store.py +469 -0
  73. pretensor/enrichment/__init__.py +1 -0
  74. pretensor/enrichment/analyze/__init__.py +0 -0
  75. pretensor/enrichment/analyze/classify.py +49 -0
  76. pretensor/enrichment/analyze/extract_python.py +196 -0
  77. pretensor/enrichment/analyze/parse.py +141 -0
  78. pretensor/enrichment/analyze/pipeline.py +195 -0
  79. pretensor/enrichment/analyze/summary.py +38 -0
  80. pretensor/enrichment/analyze/walker.py +98 -0
  81. pretensor/enrichment/analyze/writers.py +214 -0
  82. pretensor/enrichment/dbt/__init__.py +30 -0
  83. pretensor/enrichment/dbt/lineage.py +100 -0
  84. pretensor/enrichment/dbt/manifest.py +300 -0
  85. pretensor/enrichment/dbt/metadata.py +263 -0
  86. pretensor/enrichment/dbt/pipeline.py +77 -0
  87. pretensor/enrichment/dbt/resolution.py +101 -0
  88. pretensor/enrichment/dbt/signals.py +305 -0
  89. pretensor/entities/__init__.py +27 -0
  90. pretensor/entities/builder.py +63 -0
  91. pretensor/entities/classifier.py +383 -0
  92. pretensor/entities/llm_extract.py +66 -0
  93. pretensor/errors.py +35 -0
  94. pretensor/graph_models/__init__.py +17 -0
  95. pretensor/graph_models/base.py +11 -0
  96. pretensor/graph_models/consumer.py +71 -0
  97. pretensor/graph_models/edge.py +35 -0
  98. pretensor/graph_models/entity.py +21 -0
  99. pretensor/graph_models/node.py +79 -0
  100. pretensor/graph_models/relationship.py +33 -0
  101. pretensor/integrations/__init__.py +42 -0
  102. pretensor/integrations/_base.py +138 -0
  103. pretensor/integrations/google_adk.py +49 -0
  104. pretensor/integrations/langchain.py +55 -0
  105. pretensor/integrations/llamaindex.py +53 -0
  106. pretensor/intelligence/__init__.py +33 -0
  107. pretensor/intelligence/cluster_labeler.py +425 -0
  108. pretensor/intelligence/clustering.py +168 -0
  109. pretensor/intelligence/combining.py +32 -0
  110. pretensor/intelligence/discovery.py +114 -0
  111. pretensor/intelligence/embeddings.py +317 -0
  112. pretensor/intelligence/graph_export.py +200 -0
  113. pretensor/intelligence/heuristic.py +544 -0
  114. pretensor/intelligence/join_paths/__init__.py +130 -0
  115. pretensor/intelligence/join_paths/on_demand.py +516 -0
  116. pretensor/intelligence/join_paths/storage.py +70 -0
  117. pretensor/intelligence/llm_infer.py +78 -0
  118. pretensor/intelligence/llm_runtime.py +62 -0
  119. pretensor/intelligence/metric_templates.py +193 -0
  120. pretensor/intelligence/pipeline.py +364 -0
  121. pretensor/intelligence/role_exemplars.py +263 -0
  122. pretensor/intelligence/schema_classification.py +360 -0
  123. pretensor/intelligence/scoring.py +76 -0
  124. pretensor/intelligence/semantic.py +240 -0
  125. pretensor/intelligence/shadow_alias.py +101 -0
  126. pretensor/intelligence/statistical.py +50 -0
  127. pretensor/intelligence/steps.py +191 -0
  128. pretensor/intelligence/steps_embedding.py +168 -0
  129. pretensor/introspection/__init__.py +6 -0
  130. pretensor/introspection/inspector.py +5 -0
  131. pretensor/introspection/models/__init__.py +0 -0
  132. pretensor/introspection/models/base.py +5 -0
  133. pretensor/introspection/models/config.py +237 -0
  134. pretensor/introspection/models/dsn.py +550 -0
  135. pretensor/introspection/models/plan.py +116 -0
  136. pretensor/introspection/models/schema.py +10 -0
  137. pretensor/introspection/models/semantic.py +121 -0
  138. pretensor/introspection/models/validation.py +116 -0
  139. pretensor/introspection/snapshot.py +46 -0
  140. pretensor/mcp/__init__.py +16 -0
  141. pretensor/mcp/config_json.py +24 -0
  142. pretensor/mcp/payload_types.py +274 -0
  143. pretensor/mcp/resources/__init__.py +17 -0
  144. pretensor/mcp/resources/markdown.py +314 -0
  145. pretensor/mcp/server.py +285 -0
  146. pretensor/mcp/service.py +49 -0
  147. pretensor/mcp/service_context.py +142 -0
  148. pretensor/mcp/service_registry.py +294 -0
  149. pretensor/mcp/store_cache.py +43 -0
  150. pretensor/mcp/tool_registry.py +136 -0
  151. pretensor/mcp/tools/__init__.py +1 -0
  152. pretensor/mcp/tools/_rank.py +244 -0
  153. pretensor/mcp/tools/_timed.py +26 -0
  154. pretensor/mcp/tools/compile_metric.py +144 -0
  155. pretensor/mcp/tools/consumers.py +161 -0
  156. pretensor/mcp/tools/context.py +1121 -0
  157. pretensor/mcp/tools/cypher.py +509 -0
  158. pretensor/mcp/tools/detect_changes.py +254 -0
  159. pretensor/mcp/tools/impact.py +271 -0
  160. pretensor/mcp/tools/list.py +131 -0
  161. pretensor/mcp/tools/schema.py +170 -0
  162. pretensor/mcp/tools/search.py +316 -0
  163. pretensor/mcp/tools/semantic_search.py +282 -0
  164. pretensor/mcp/tools/traverse.py +1027 -0
  165. pretensor/mcp/tools/validate_sql.py +150 -0
  166. pretensor/observability.py +203 -0
  167. pretensor/py.typed +0 -0
  168. pretensor/quickstart/README.md +29 -0
  169. pretensor/quickstart/__init__.py +6 -0
  170. pretensor/quickstart/docker-compose.yml +18 -0
  171. pretensor/quickstart/pagila_data.sql +63 -0
  172. pretensor/quickstart/pagila_ddl.sql +92 -0
  173. pretensor/search/__init__.py +6 -0
  174. pretensor/search/base.py +80 -0
  175. pretensor/search/index.py +435 -0
  176. pretensor/semantic/__init__.py +24 -0
  177. pretensor/semantic/base.py +123 -0
  178. pretensor/semantic/compiler.py +487 -0
  179. pretensor/semantic/yaml_layer.py +180 -0
  180. pretensor/skills/__init__.py +5 -0
  181. pretensor/skills/generator.py +235 -0
  182. pretensor/staleness/__init__.py +15 -0
  183. pretensor/staleness/graph_patcher.py +355 -0
  184. pretensor/staleness/impact_analyzer.py +162 -0
  185. pretensor/staleness/snapshot_store.py +38 -0
  186. pretensor/validation/__init__.py +9 -0
  187. pretensor/validation/query_validator.py +436 -0
  188. pretensor/visibility/__init__.py +23 -0
  189. pretensor/visibility/config.py +126 -0
  190. pretensor/visibility/filter.py +143 -0
  191. pretensor/visibility/kuzu_helpers.py +32 -0
  192. pretensor/visibility/runtime.py +36 -0
  193. pretensor/visibility/sync_grants.py +188 -0
  194. pretensor-0.1.0.dist-info/METADATA +251 -0
  195. pretensor-0.1.0.dist-info/RECORD +198 -0
  196. pretensor-0.1.0.dist-info/WHEEL +4 -0
  197. pretensor-0.1.0.dist-info/entry_points.txt +2 -0
  198. pretensor-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+ from io import StringIO
5
+ from typing import Literal
6
+
7
+ from pydantic import Field
8
+ from ruamel.yaml import YAML
9
+
10
+ from .base import PretensorModel
11
+
12
+
13
+ class AttributeRole(StrEnum):
14
+ IDENTIFIER = "identifier"
15
+ DIMENSION = "dimension"
16
+ MEASURE = "measure"
17
+ TIME_DIMENSION = "time_dimension"
18
+
19
+
20
+ class MetricType(StrEnum):
21
+ COUNT = "count"
22
+ COUNT_DISTINCT = "count_distinct"
23
+ SUM = "sum"
24
+ AVERAGE = "average"
25
+ DERIVED = "derived"
26
+
27
+
28
+ class FormatConfig(PretensorModel):
29
+ """Per-attribute or per-metric format override."""
30
+
31
+ format_type: Literal["number", "currency", "percentage", "date"] | None = None
32
+ decimals: int | None = None
33
+ symbol: str | None = None
34
+ position: Literal["prefix", "suffix"] | None = None
35
+ suffix: str | None = None
36
+ thousands_separator: str | None = None
37
+ format_string: str | None = None
38
+
39
+
40
+ class Attribute(PretensorModel):
41
+ name: str
42
+ description: str
43
+ role: AttributeRole
44
+ source_column: str
45
+ format_config: FormatConfig | None = None
46
+
47
+
48
+ class Metric(PretensorModel):
49
+ name: str
50
+ description: str
51
+ type: MetricType
52
+ field: str | None = None
53
+ expression: str | None = None
54
+ time_dimension: str | None = None
55
+ granularity: str | None = None
56
+ format_config: FormatConfig | None = None
57
+ filters: list[str] = Field(default_factory=list)
58
+
59
+
60
+ class Entity(PretensorModel):
61
+ name: str
62
+ description: str
63
+ source_table: str
64
+ attributes: list[Attribute] = Field(default_factory=list)
65
+ metrics: list[Metric] = Field(default_factory=list)
66
+
67
+
68
+ class Domain(PretensorModel):
69
+ name: str
70
+ description: str
71
+ entities: list[Entity] = Field(default_factory=list)
72
+
73
+
74
+ class NumberFormatDefaults(PretensorModel):
75
+ decimals: int = 0
76
+ thousands_separator: str = ","
77
+
78
+
79
+ class CurrencyFormatDefaults(PretensorModel):
80
+ symbol: str = "$"
81
+ position: Literal["prefix", "suffix"] = "prefix"
82
+ decimals: int = 2
83
+
84
+
85
+ class PercentageFormatDefaults(PretensorModel):
86
+ decimals: int = 1
87
+ suffix: str = "%"
88
+
89
+
90
+ class DateFormatDefaults(PretensorModel):
91
+ format_string: str = "YYYY-MM-DD"
92
+
93
+
94
+ class FormatDefaults(PretensorModel):
95
+ number: NumberFormatDefaults = Field(default_factory=NumberFormatDefaults)
96
+ currency: CurrencyFormatDefaults = Field(default_factory=CurrencyFormatDefaults)
97
+ percentage: PercentageFormatDefaults = Field(
98
+ default_factory=PercentageFormatDefaults
99
+ )
100
+ date: DateFormatDefaults = Field(default_factory=DateFormatDefaults)
101
+ chart_preferences: dict[str, str] = Field(default_factory=dict)
102
+
103
+
104
+ class SemanticLayer(PretensorModel):
105
+ connection_name: str
106
+ domains: list[Domain] = Field(default_factory=list)
107
+ format_defaults: FormatDefaults = Field(default_factory=FormatDefaults)
108
+
109
+ def to_yaml(self) -> str:
110
+ data = self.model_dump(mode="json")
111
+ yaml = YAML()
112
+ yaml.default_flow_style = False
113
+ buf = StringIO()
114
+ yaml.dump(data, buf)
115
+ return buf.getvalue()
116
+
117
+ @classmethod
118
+ def from_yaml(cls, yaml_str: str) -> SemanticLayer:
119
+ yaml = YAML()
120
+ data = yaml.load(yaml_str)
121
+ return cls.model_validate(data)
@@ -0,0 +1,116 @@
1
+ """Validation report models — shared across all validation layers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from typing import Literal
7
+
8
+ from pydantic import Field
9
+
10
+ from .base import PretensorModel
11
+
12
+ __all__ = [
13
+ "Severity",
14
+ "RepairState",
15
+ "ValidationIssue",
16
+ "ComponentScore",
17
+ "LayerResult",
18
+ "AnalyticalQualityResult",
19
+ "ValidationReport",
20
+ "LayerStatus",
21
+ ]
22
+
23
+ LayerStatus = Literal["pass", "fail", "repaired", "not_evaluated"]
24
+
25
+
26
+ class Severity(StrEnum):
27
+ ERROR = "error"
28
+ WARNING = "warning"
29
+ INFO = "info"
30
+
31
+
32
+ class RepairState(StrEnum):
33
+ PASS = "pass"
34
+ FAIL = "fail"
35
+ REPAIRED = "repaired"
36
+ NOT_EVALUATED = "not_evaluated"
37
+
38
+
39
+ class ValidationIssue(PretensorModel):
40
+ model_config = PretensorModel.model_config.copy()
41
+ model_config["frozen"] = False
42
+
43
+ severity: Severity
44
+ layer: str
45
+ path: str
46
+ message: str
47
+
48
+
49
+ class LayerResult(PretensorModel):
50
+ model_config = PretensorModel.model_config.copy()
51
+ model_config["frozen"] = False
52
+
53
+ layer_name: str
54
+ status: LayerStatus
55
+ repair_state: RepairState = RepairState.PASS
56
+ issues: list[ValidationIssue] = Field(default_factory=list)
57
+
58
+ @property
59
+ def error_count(self) -> int:
60
+ return sum(1 for i in self.issues if i.severity == Severity.ERROR)
61
+
62
+ @property
63
+ def warning_count(self) -> int:
64
+ return sum(1 for i in self.issues if i.severity == Severity.WARNING)
65
+
66
+
67
+ class ComponentScore(PretensorModel):
68
+ """Per-chart quality score produced by the L4 analyst review."""
69
+
70
+ model_config = PretensorModel.model_config.copy()
71
+ model_config["frozen"] = False
72
+
73
+ score: int
74
+ answers_main_question: bool
75
+ priority_rank: int
76
+ current_position: int
77
+ note: str
78
+ subtracting: str | None = None
79
+ suggested_action: str | None = None
80
+
81
+
82
+ class AnalyticalQualityResult(LayerResult):
83
+ """Extended LayerResult for L4, carrying per-chart ComponentScore breakdown."""
84
+
85
+ model_config = LayerResult.model_config.copy()
86
+
87
+ overall_score: int = 0
88
+ overall_summary: str = ""
89
+ question_answered: bool = False
90
+ components: dict[str, ComponentScore] = Field(default_factory=dict)
91
+ repair_tier: int = 1
92
+
93
+
94
+ class ValidationReport(PretensorModel):
95
+ model_config = PretensorModel.model_config.copy()
96
+ model_config["frozen"] = False
97
+
98
+ dashboard_name: str
99
+ layers: list[LayerResult] = Field(default_factory=list)
100
+ repair_actions_taken: list[str] = Field(default_factory=list)
101
+
102
+ @property
103
+ def passed(self) -> bool:
104
+ return all(r.status == "pass" for r in self.layers)
105
+
106
+ @property
107
+ def all_issues(self) -> list[ValidationIssue]:
108
+ return [issue for layer in self.layers for issue in layer.issues]
109
+
110
+ @property
111
+ def error_count(self) -> int:
112
+ return sum(r.error_count for r in self.layers)
113
+
114
+ @property
115
+ def warning_count(self) -> int:
116
+ return sum(r.warning_count for r in self.layers)
@@ -0,0 +1,46 @@
1
+ """Snapshot diff — re-exported from ``pretensor.connectors.snapshot``."""
2
+
3
+ from pathlib import Path
4
+
5
+ from pretensor.connectors.models import SchemaSnapshot
6
+ from pretensor.connectors.snapshot import (
7
+ ChangeTarget,
8
+ ChangeType,
9
+ SchemaChange,
10
+ diff_snapshots,
11
+ )
12
+
13
+
14
+ def save_snapshot(snapshot: SchemaSnapshot, base_path: Path) -> Path:
15
+ """Serialize a snapshot to YAML and write it to disk.
16
+
17
+ Writes to ``{base_path}/{connection_name}.yaml``.
18
+
19
+ Returns:
20
+ The path to the written file.
21
+ """
22
+ base_path.mkdir(parents=True, exist_ok=True)
23
+ file_path = base_path / f"{snapshot.connection_name}.yaml"
24
+ file_path.write_text(snapshot.to_yaml())
25
+ return file_path
26
+
27
+
28
+ def load_snapshot(path: Path) -> SchemaSnapshot:
29
+ """Load a schema snapshot from a YAML file.
30
+
31
+ Raises:
32
+ FileNotFoundError: If the file does not exist.
33
+ """
34
+ if not path.exists():
35
+ raise FileNotFoundError(f"Snapshot file not found: {path}")
36
+ return SchemaSnapshot.from_yaml(path.read_text())
37
+
38
+
39
+ __all__ = [
40
+ "ChangeTarget",
41
+ "ChangeType",
42
+ "SchemaChange",
43
+ "diff_snapshots",
44
+ "load_snapshot",
45
+ "save_snapshot",
46
+ ]
@@ -0,0 +1,16 @@
1
+ """MCP server and service helpers for Pretensor Graph."""
2
+
3
+ from pretensor.mcp.server import (
4
+ create_server,
5
+ print_mcp_config,
6
+ run_server,
7
+ )
8
+ from pretensor.mcp.tool_registry import McpTool, McpToolRegistry
9
+
10
+ __all__ = [
11
+ "create_server",
12
+ "print_mcp_config",
13
+ "run_server",
14
+ "McpTool",
15
+ "McpToolRegistry",
16
+ ]
@@ -0,0 +1,24 @@
1
+ """MCP client config snippet (Cursor / Claude Desktop)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+
9
+ def mcp_config_json(graph_dir: Path) -> str:
10
+ """Claude Desktop / Cursor style ``mcpServers`` block for stdio transport."""
11
+ cmd = "pretensor"
12
+ graph_dir_resolved = str(graph_dir.resolve())
13
+ block = {
14
+ "mcpServers": {
15
+ "pretensor": {
16
+ "command": cmd,
17
+ "args": ["serve", "--state-dir", graph_dir_resolved],
18
+ }
19
+ }
20
+ }
21
+ return json.dumps(block, indent=2)
22
+
23
+
24
+ __all__ = ["mcp_config_json"]
@@ -0,0 +1,274 @@
1
+ """Typed payloads and small helpers for MCP tool responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ from typing import Any, TypedDict
7
+
8
+ from pretensor.config import GraphConfig
9
+ from pretensor.mcp.service_context import get_effective_graph_config
10
+
11
+ DB_TYPE_POSTGRES = "postgresql"
12
+
13
+ DIALECT_TO_DB_TYPE: dict[str, str] = {
14
+ "postgres": "postgresql",
15
+ "mysql": "mysql",
16
+ "snowflake": "snowflake",
17
+ "bigquery": "bigquery",
18
+ }
19
+
20
+
21
+ class DatabaseListItem(TypedDict, total=False):
22
+ """One row for ``list_databases`` and the databases resource."""
23
+
24
+ name: str
25
+ db_type: str
26
+ table_count: int
27
+ column_count: int
28
+ row_count: int
29
+ last_indexed: str
30
+ is_stale: bool
31
+ staleness_days: int
32
+ stale_warning: str
33
+ graph_path: str
34
+ database: str
35
+ schemas: list[str]
36
+ # Trinary capability state: "not_attempted" | "empty" | "present".
37
+ # "not_attempted" means the indexer never ran this pass, so absence of signal
38
+ # is not authoritative. "empty" means the pass ran but found nothing.
39
+ has_dbt_manifest: str
40
+ has_llm_enrichment: str
41
+ has_external_consumers: str
42
+ # Unified capability list derived from the trinary flags plus always-on
43
+ # features. Additive convenience over the individual ``has_*`` fields;
44
+ # scan this to decide whether a graph supports a feature at a glance.
45
+ capabilities: list[str]
46
+
47
+
48
+ class QueryHit(TypedDict, total=False):
49
+ """One search hit for the ``query`` tool."""
50
+
51
+ node_type: str
52
+ name: str
53
+ database_name: str
54
+ connection_name: str
55
+ description: str
56
+ snippet: str
57
+ score: float
58
+
59
+
60
+ class SemanticHit(TypedDict, total=False):
61
+ """One search hit for the ``semantic_search`` tool (cosine over ``SchemaTable.embedding``)."""
62
+
63
+ node_type: str
64
+ name: str
65
+ database_name: str
66
+ connection_name: str
67
+ description: str
68
+ snippet: str
69
+ score: float
70
+ cluster_id: str | None
71
+
72
+
73
+ class ColumnInfo(TypedDict, total=False):
74
+ """Column metadata for ``context`` (from ``SchemaColumn`` nodes when present).
75
+
76
+ ``column_name`` is the canonical field.
77
+ """
78
+
79
+ column_name: str
80
+ data_type: str
81
+ nullable: bool
82
+ is_primary_key: bool
83
+ is_foreign_key: bool
84
+ description: str
85
+ is_indexed: bool
86
+ check_constraints: list[str]
87
+ most_common_values: list[str]
88
+ histogram_bounds: list[str]
89
+ stats_correlation: float
90
+
91
+
92
+ class RelationshipInfo(TypedDict, total=False):
93
+ """FK or inferred join from a table to another table.
94
+
95
+ For composite FKs, ``source_columns`` / ``target_columns`` carry the
96
+ ordered column lists and ``constraint_name`` identifies the constraint.
97
+ Single-column FKs still set ``source_column`` / ``target_column`` for
98
+ backward compatibility.
99
+ """
100
+
101
+ target_table: str
102
+ rel_type: str
103
+ source_column: str
104
+ target_column: str
105
+ source_columns: list[str]
106
+ target_columns: list[str]
107
+ constraint_name: str | None
108
+ source: str | None
109
+ confidence: float | None
110
+ reasoning: str | None
111
+
112
+
113
+ class LineageRef(TypedDict, total=False):
114
+ """One table-level LINEAGE hop for ``context``."""
115
+
116
+ table: str
117
+ lineage_type: str
118
+ confidence: float
119
+ source: str
120
+
121
+
122
+ class ClusterInfo(TypedDict, total=False):
123
+ """Domain cluster assignment for ``context``."""
124
+
125
+ cluster_id: str
126
+ label: str
127
+ description: str
128
+ cohesion_score: float
129
+ schema_pattern: str
130
+ stale: bool
131
+ stale_warning: str
132
+
133
+
134
+ class SimilarTable(TypedDict, total=False):
135
+ """One cross-cluster nearest neighbor in ``context.similar_tables``."""
136
+
137
+ table_id: str
138
+ qualified_name: str
139
+ score: float
140
+ cluster_id: str | None
141
+
142
+
143
+ class ContextPayload(TypedDict, total=False):
144
+ """Structured ``context`` tool response."""
145
+
146
+ connection_name: str
147
+ database: str
148
+ schema_name: str
149
+ table_name: str
150
+ table_type: str | None
151
+ qualified_name: str
152
+ description: str
153
+ tags: list[str]
154
+ has_external_consumers: bool
155
+ test_count: int
156
+ staleness_status: str
157
+ staleness_as_of: str
158
+ row_count: int | None
159
+ entity_type: str | None
160
+ entity_name: str | None
161
+ entity_description: str | None
162
+ role: str | None
163
+ role_confidence: float | None
164
+ classification_signals: list[str]
165
+ classification_summary: str
166
+ cluster: ClusterInfo | None
167
+ columns: list[ColumnInfo]
168
+ relationships: list[RelationshipInfo]
169
+ lineage_in: list[LineageRef]
170
+ lineage_out: list[LineageRef]
171
+ lineage_markdown: str
172
+ aliases: list[str]
173
+ shadow_of: str | None
174
+ deprecation_signal: str | None
175
+ staleness_warning: str | None
176
+ detail: str
177
+ usage_stats: dict[str, int]
178
+ partition: dict[str, object]
179
+ grants: list[dict[str, str]]
180
+ access_patterns: dict[str, object]
181
+ similar_tables: list[SimilarTable]
182
+ similar_reason: str
183
+
184
+
185
+ class TraverseStepPayload(TypedDict, total=False):
186
+ """One hop in ``traverse`` output."""
187
+
188
+ from_table: str
189
+ to_table: str
190
+ from_column: str
191
+ to_column: str
192
+ edge_type: str
193
+ from_db: str
194
+ to_db: str
195
+ via: str
196
+ join_columns: str | None
197
+ constraint_name: str | None
198
+
199
+
200
+ class TraversePathPayload(TypedDict, total=False):
201
+ """One ranked path in ``traverse`` output."""
202
+
203
+ confidence: float
204
+ ambiguous: bool
205
+ semantic_label: str
206
+ steps: list[TraverseStepPayload]
207
+ sql_hint: str
208
+ stale: bool
209
+ stale_warning: str
210
+
211
+
212
+ class ImpactItemPayload(TypedDict, total=False):
213
+ """Single dependent in ``impact`` output."""
214
+
215
+ type: str
216
+ name: str
217
+ via: str
218
+ confidence: float
219
+ hop: int
220
+ # Additive: external code consumers of this table (empty when none). Old
221
+ # callers that ignore this key are unaffected.
222
+ consumers: list[dict[str, Any]]
223
+
224
+
225
+ def utc_now() -> datetime:
226
+ return datetime.now(timezone.utc)
227
+
228
+
229
+ def iso_format(dt: datetime) -> str:
230
+ if dt.tzinfo is None:
231
+ dt = dt.replace(tzinfo=timezone.utc)
232
+ return dt.astimezone(timezone.utc).isoformat()
233
+
234
+
235
+ def stale_threshold_days(config: GraphConfig | None = None) -> int:
236
+ return get_effective_graph_config(config).stale_index_warning_days
237
+
238
+
239
+ def staleness_days(indexed_at: datetime, now: datetime | None = None) -> int:
240
+ reference = now or utc_now()
241
+ if indexed_at.tzinfo is None:
242
+ indexed_at = indexed_at.replace(tzinfo=timezone.utc)
243
+ delta = reference - indexed_at.astimezone(timezone.utc)
244
+ return max(0, int(delta.total_seconds() // 86400))
245
+
246
+
247
+ def snippet(text: str, max_len: int = 200) -> str:
248
+ t = text.strip()
249
+ if len(t) <= max_len:
250
+ return t
251
+ return t[: max_len - 3] + "..."
252
+
253
+
254
+ __all__ = [
255
+ "ClusterInfo",
256
+ "ColumnInfo",
257
+ "ContextPayload",
258
+ "DatabaseListItem",
259
+ "ImpactItemPayload",
260
+ "LineageRef",
261
+ "QueryHit",
262
+ "RelationshipInfo",
263
+ "SemanticHit",
264
+ "SimilarTable",
265
+ "TraversePathPayload",
266
+ "TraverseStepPayload",
267
+ "DB_TYPE_POSTGRES",
268
+ "DIALECT_TO_DB_TYPE",
269
+ "iso_format",
270
+ "snippet",
271
+ "staleness_days",
272
+ "stale_threshold_days",
273
+ "utc_now",
274
+ ]
@@ -0,0 +1,17 @@
1
+ """MCP resource payloads (markdown)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pretensor.mcp.resources.markdown import (
6
+ clusters_resource_markdown,
7
+ databases_resource_markdown,
8
+ db_overview_resource_markdown,
9
+ metrics_resource_markdown,
10
+ )
11
+
12
+ __all__ = [
13
+ "clusters_resource_markdown",
14
+ "databases_resource_markdown",
15
+ "db_overview_resource_markdown",
16
+ "metrics_resource_markdown",
17
+ ]