dbgraph 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 (42) hide show
  1. dbgraph/__init__.py +21 -0
  2. dbgraph/application.py +70 -0
  3. dbgraph/builder/__init__.py +0 -0
  4. dbgraph/builder/graph_builder.py +22 -0
  5. dbgraph/builder/sql_graph_builder.py +245 -0
  6. dbgraph/descriptor/__init__.py +0 -0
  7. dbgraph/descriptor/graph_descriptor.py +68 -0
  8. dbgraph/descriptor/langchain_graph_descriptor.py +74 -0
  9. dbgraph/descriptor/prompt_templates.py +80 -0
  10. dbgraph/entity/__init__.py +0 -0
  11. dbgraph/entity/aspect.py +153 -0
  12. dbgraph/entity/asset.py +26 -0
  13. dbgraph/entity/asset_type.py +13 -0
  14. dbgraph/entity/dbgraph.py +152 -0
  15. dbgraph/entity/link.py +37 -0
  16. dbgraph/entity/link_type.py +13 -0
  17. dbgraph/entity/rdbgraph.py +76 -0
  18. dbgraph/event/__init__.py +0 -0
  19. dbgraph/event/event.py +20 -0
  20. dbgraph/event/event_bus.py +61 -0
  21. dbgraph/event/event_handler.py +11 -0
  22. dbgraph/io/__init__.py +0 -0
  23. dbgraph/io/graph_loader.py +11 -0
  24. dbgraph/io/graph_writer.py +11 -0
  25. dbgraph/io/json_graph_loader.py +160 -0
  26. dbgraph/io/json_graph_writer.py +17 -0
  27. dbgraph/persistent/__init__.py +0 -0
  28. dbgraph/persistent/graph_persistent.py +98 -0
  29. dbgraph/persistent/models.py +222 -0
  30. dbgraph/persistent/sql_graph_persistent.py +388 -0
  31. dbgraph/render/__init__.py +0 -0
  32. dbgraph/render/graph_renderer.py +11 -0
  33. dbgraph/render/markdown_renderer.py +111 -0
  34. dbgraph/render/text_renderer.py +11 -0
  35. dbgraph/search/__init__.py +0 -0
  36. dbgraph/search/bm25_search_engine.py +33 -0
  37. dbgraph/search/search_engine.py +15 -0
  38. dbgraph/utils/__init__.py +0 -0
  39. dbgraph/utils/singleton.py +37 -0
  40. dbgraph-0.1.0.dist-info/METADATA +165 -0
  41. dbgraph-0.1.0.dist-info/RECORD +42 -0
  42. dbgraph-0.1.0.dist-info/WHEEL +4 -0
dbgraph/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ from dbgraph.entity.asset import Asset
2
+ from dbgraph.entity.asset_type import AssetType
3
+ from dbgraph.entity.dbgraph import DatabaseGraph
4
+ from dbgraph.entity.link import Link
5
+ from dbgraph.entity.link_type import LinkType
6
+ from dbgraph.builder.sql_graph_builder import SQLGraphBuilder
7
+ from dbgraph.persistent.sql_graph_persistent import SQLGraphPersistent
8
+ from dbgraph.descriptor.langchain_graph_descriptor import LangchainGraphDescriptor
9
+ from dbgraph.render.markdown_renderer import MarkdownRenderer
10
+
11
+ __all__ = [
12
+ "Asset",
13
+ "AssetType",
14
+ "DatabaseGraph",
15
+ "Link",
16
+ "LinkType",
17
+ "SQLGraphBuilder",
18
+ "SQLGraphPersistent",
19
+ "LangchainGraphDescriptor",
20
+ "MarkdownRenderer"
21
+ ]
dbgraph/application.py ADDED
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass
2
+ from uuid import uuid4
3
+
4
+ from dbgraph.builder.graph_builder import GraphBuilder
5
+ from dbgraph.builder.sql_graph_builder import SQLGraphBuilder
6
+ from dbgraph.descriptor.graph_descriptor import GraphDescriptor
7
+ from dbgraph.entity.asset import Asset
8
+ from dbgraph.entity.dbgraph import DatabaseGraph
9
+ from dbgraph.entity.link import Link
10
+ from dbgraph.entity.rdbgraph import RDatabaseGraph
11
+ from dbgraph.persistent.graph_persistent import GraphPersistent
12
+ from dbgraph.persistent.sql_graph_persistent import SQLGraphPersistent
13
+
14
+
15
+ @dataclass
16
+ class Application:
17
+ """God class to control the logical flow within dbgraph"""
18
+
19
+ source_database_type: str
20
+ graph_persistent_uri: str
21
+ graph_persistent_type: str
22
+ descriptor: GraphDescriptor | None = None
23
+
24
+ def __post_init__(self):
25
+ self.graph_persistent = self.create_graph_persistent()
26
+
27
+ def create_graph_builder(self, database_uri: str) -> GraphBuilder:
28
+ match self.source_database_type:
29
+ case "sql":
30
+ builder = SQLGraphBuilder(database_uri)
31
+ case _:
32
+ raise NotImplementedError()
33
+ return builder
34
+
35
+ def create_graph_persistent(self) -> GraphPersistent:
36
+ match self.graph_persistent_type:
37
+ case "sql":
38
+ persistent = SQLGraphPersistent(self.graph_persistent_uri)
39
+ case _:
40
+ raise NotImplementedError()
41
+ return persistent
42
+
43
+ def build_graph(self, name: str, database_uri: str, fill_semantic: bool) -> str:
44
+ """Build the graph, return its ID in the database"""
45
+ builder = self.create_graph_builder(database_uri)
46
+ graph = builder.build_graph()
47
+ if fill_semantic and self.descriptor is not None:
48
+ graph = self.descriptor.rfill_semantic_aspects(
49
+ RDatabaseGraph.from_graph(graph)
50
+ )
51
+ graph_id = str(uuid4())
52
+ self.graph_persistent.create_graph(graph_id, name)
53
+ self.graph_persistent.save_graph(graph, name)
54
+ return graph_id
55
+
56
+ def load_graph(self, graph_id: str) -> DatabaseGraph:
57
+ """Load the graph using its ID"""
58
+ return self.graph_persistent.load_graph(graph_id)
59
+
60
+ def delete_graph(self, graph_id: str):
61
+ """Delete the graph using its ID"""
62
+ self.graph_persistent.delete_graph(graph_id)
63
+
64
+ def get_asset(self, asset_id: str, graph_id: str) -> Asset:
65
+ """Find an asset using its ID"""
66
+ return self.graph_persistent.get_asset(asset_id, graph_id)
67
+
68
+ def get_link(self, link_id: str, graph_id: str) -> Link:
69
+ """Find a link using its ID"""
70
+ return self.graph_persistent.get_link(link_id, graph_id)
File without changes
@@ -0,0 +1,22 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from dbgraph.entity.asset import Asset
4
+ from dbgraph.entity.dbgraph import DatabaseGraph
5
+ from dbgraph.entity.link import Link
6
+
7
+
8
+ class GraphBuilder(ABC):
9
+ """Interface for relational database graph builder"""
10
+
11
+ @abstractmethod
12
+ def _build_assets(self) -> list[Asset]:
13
+ pass
14
+
15
+ @abstractmethod
16
+ def _build_links(self) -> list[Link]:
17
+ pass
18
+
19
+ def build_graph(self) -> DatabaseGraph:
20
+ assets = self._build_assets()
21
+ links = self._build_links()
22
+ return DatabaseGraph(assets, links)
@@ -0,0 +1,245 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from dataclasses import dataclass
3
+ from uuid import uuid4
4
+
5
+ from sqlalchemy import (
6
+ Column,
7
+ MetaData,
8
+ Numeric,
9
+ PrimaryKeyConstraint,
10
+ String,
11
+ Table,
12
+ create_engine,
13
+ func,
14
+ inspect,
15
+ select,
16
+ )
17
+
18
+ from dbgraph.builder.graph_builder import GraphBuilder
19
+ from dbgraph.entity.aspect import (
20
+ RCategoricalStatistics,
21
+ RColumnSchemaAspect,
22
+ RColumnStatisticsAspect,
23
+ RForeignKeyAspect,
24
+ RNumericalStatistics,
25
+ RTableSchemaAspect,
26
+ RTableStatisticsAspect,
27
+ )
28
+ from dbgraph.entity.asset import Asset
29
+ from dbgraph.entity.asset_type import AssetType
30
+ from dbgraph.entity.link import Link
31
+ from dbgraph.entity.link_type import LinkType
32
+
33
+
34
+ @dataclass
35
+ class SQLGraphBuilder(GraphBuilder):
36
+ """Build the database graph using SQL Alchemy"""
37
+
38
+ db_uri: str
39
+ max_worker: int = 4
40
+
41
+ def __post_init__(self):
42
+ self.engine = create_engine(self.db_uri)
43
+ self.columns_assets: dict[str, list[Asset]] = {}
44
+ self.tables_assets: dict[str, Asset] = {}
45
+ self._tables_orm: dict[str, Table] = {
46
+ table.name: table for table in self._orm_tables
47
+ }
48
+
49
+ @property
50
+ def _orm_tables(self) -> list[Table]:
51
+ orm_tables = []
52
+ for table_name in self.table_names:
53
+ metadata = MetaData()
54
+ table = Table(table_name, metadata, autoload_with=self.engine)
55
+ orm_tables.append(table)
56
+ return orm_tables
57
+
58
+ @property
59
+ def table_names(self) -> list[str]:
60
+ inspector = inspect(self.engine)
61
+ return inspector.get_table_names()
62
+
63
+ def _get_numerical_stats_aspect(self, col: Column) -> RNumericalStatistics:
64
+ stmt = select(
65
+ func.min(col).label("min"),
66
+ func.max(col).label("max"),
67
+ func.avg(col).label("mean"),
68
+ )
69
+ with self.engine.connect() as conn:
70
+ result = conn.execute(stmt).one()
71
+ return RNumericalStatistics(
72
+ min=float(result.min), max=float(result.max), mean=float(result.mean)
73
+ )
74
+
75
+ def _get_null_info(self, col: Column) -> tuple[int, int]:
76
+ stmt = select(
77
+ func.count(col).label("non_null_count"),
78
+ (func.count() - func.count(col)).label("null_count"),
79
+ )
80
+ with self.engine.connect() as conn:
81
+ result = conn.execute(stmt).one()
82
+ return int(result.non_null_count), int(result.null_count)
83
+
84
+ def _get_cat_stats_aspect(self, col: Column) -> RCategoricalStatistics:
85
+ stmt = (
86
+ select(
87
+ col.label("value"),
88
+ func.count().label("value_count"),
89
+ )
90
+ .where(col.is_not(None))
91
+ .group_by(col)
92
+ .order_by(func.count().desc())
93
+ .limit(10)
94
+ )
95
+ with self.engine.connect() as conn:
96
+ rows = conn.execute(stmt).all()
97
+ value_counts = {str(row[0]): int(row[1]) for row in rows}
98
+ return RCategoricalStatistics(value_counts)
99
+
100
+ def _get_stats_aspect(self, col: Column) -> RColumnStatisticsAspect:
101
+ non_null_count, null_count = self._get_null_info(col)
102
+ if isinstance(col.type, Numeric):
103
+ num_aspect = self._get_numerical_stats_aspect(col)
104
+ cat_aspect = None
105
+ elif isinstance(col.type, String):
106
+ num_aspect = None
107
+ cat_aspect = self._get_cat_stats_aspect(col)
108
+ else:
109
+ num_aspect = None
110
+ cat_aspect = None
111
+ return RColumnStatisticsAspect(
112
+ name=f"{col.name}_column_stats",
113
+ numerical_stats=num_aspect,
114
+ categorical_stats=cat_aspect,
115
+ non_null_count=non_null_count,
116
+ null_count=null_count,
117
+ )
118
+
119
+ def _make_column_asset(self, col: Column, pks: PrimaryKeyConstraint) -> Asset:
120
+ schema_aspect = RColumnSchemaAspect(
121
+ name=f"{col.name}_column_schema",
122
+ dtype=str(col.type),
123
+ is_nullable=col.nullable or False,
124
+ is_pk=col.name in [c.name for c in pks.columns],
125
+ )
126
+ stats_aspect = self._get_stats_aspect(col)
127
+ return Asset(
128
+ asset_id=str(uuid4()),
129
+ name=col.name,
130
+ type=AssetType.RCOLUMN,
131
+ aspects={
132
+ "schema_properties": schema_aspect,
133
+ "statistical_properties": stats_aspect,
134
+ },
135
+ )
136
+
137
+ def _get_columns_assets(self, table_name: str) -> list[Asset]:
138
+ table = self._tables_orm[table_name]
139
+ assets = []
140
+ pks = table.primary_key
141
+ with ThreadPoolExecutor(max_workers=self.max_worker) as executor:
142
+ assets = list(
143
+ executor.map(
144
+ self._make_column_asset, table.columns, [pks] * len(table.columns)
145
+ )
146
+ )
147
+ return assets
148
+
149
+ def _get_table_stats_aspect(self, table_name: str) -> RTableStatisticsAspect:
150
+ table = self._tables_orm[table_name]
151
+ nrow = self._get_table_nrow(table_name)
152
+ ncol = len(table.columns)
153
+ return RTableStatisticsAspect(
154
+ name=f"{table.name}_table_stats", num_rows=nrow, num_columns=ncol
155
+ )
156
+
157
+ def _get_table_nrow(self, table_name: str) -> int:
158
+ table = self._tables_orm[table_name]
159
+
160
+ stmt = select(func.count()).select_from(table)
161
+
162
+ with self.engine.connect() as conn:
163
+ nrow = conn.execute(stmt).scalar()
164
+ if nrow is None:
165
+ raise RuntimeError("Can't get number of row")
166
+ return int(nrow)
167
+
168
+ def _get_table_schema_aspect(self, table_name: str) -> RTableSchemaAspect:
169
+ table = self._tables_orm[table_name]
170
+ pks = [c.name for c in table.primary_key.columns]
171
+ indices = {str(i.name): [c.name for c in i.columns] for i in table.indexes}
172
+
173
+ return RTableSchemaAspect(
174
+ name=f"{table_name}_table_schema", pks=pks, indices=indices
175
+ )
176
+
177
+ def _make_table_asset(self, table_name: str) -> Asset:
178
+ stats_aspect = self._get_table_stats_aspect(table_name)
179
+ schema_aspect = self._get_table_schema_aspect(table_name)
180
+ table_asset = Asset(
181
+ asset_id=str(uuid4()),
182
+ name=table_name,
183
+ type=AssetType.RTABLE,
184
+ aspects={
185
+ "schema_properties": schema_aspect,
186
+ "statistical_properties": stats_aspect,
187
+ },
188
+ )
189
+ self.columns_assets[table_asset.name] = self._get_columns_assets(table_name)
190
+ self.tables_assets[table_asset.name] = table_asset
191
+ return table_asset
192
+
193
+ def _build_assets(self) -> list[Asset]:
194
+ with ThreadPoolExecutor(max_workers=self.max_worker) as executor:
195
+ assets = list(executor.map(self._make_table_asset, self.table_names))
196
+ for assets_list in self.columns_assets.values():
197
+ assets.extend(assets_list)
198
+ return assets
199
+
200
+ def _build_contain_links(self) -> list[Link]:
201
+ contain_links = []
202
+ for table_name, columns_assets in self.columns_assets.items():
203
+ table_asset = self.tables_assets[table_name]
204
+ for column_asset in columns_assets:
205
+ link = Link(
206
+ link_id=str(uuid4()),
207
+ name=f"{table_asset.name}_{column_asset.name}",
208
+ source_id=table_asset.asset_id,
209
+ destination_id=column_asset.asset_id,
210
+ type=LinkType.CONTAIN,
211
+ )
212
+ contain_links.append(link)
213
+ return contain_links
214
+
215
+ def _build_fk_links(self) -> list[Link]:
216
+ links = []
217
+ for table_name in self.tables_assets:
218
+ table = self._tables_orm[table_name]
219
+ for fk in table.foreign_keys:
220
+ to_table = fk.column.table.name
221
+ from_table = table.name
222
+ name = f"{from_table}_{to_table}_fk"
223
+ link = Link(
224
+ link_id=str(uuid4()),
225
+ name=name,
226
+ source_id=self.tables_assets[from_table].asset_id,
227
+ destination_id=self.tables_assets[to_table].asset_id,
228
+ type=LinkType.FOREIGN_KEY,
229
+ aspects={
230
+ "foreign_key_properties": RForeignKeyAspect(
231
+ name=name,
232
+ from_column=fk.parent.name,
233
+ to_column=fk.column.name,
234
+ on_delete=fk.ondelete or "",
235
+ on_update=fk.onupdate or "",
236
+ )
237
+ },
238
+ )
239
+ links.append(link)
240
+ return links
241
+
242
+ def _build_links(self) -> list[Link]:
243
+ contain_links = self._build_contain_links()
244
+ fk_links = self._build_fk_links()
245
+ return contain_links + fk_links
File without changes
@@ -0,0 +1,68 @@
1
+ from abc import ABC, abstractmethod
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ from dataclasses import dataclass
4
+
5
+ from dbgraph.entity.aspect import SemanticAspect
6
+ from dbgraph.entity.asset import Asset
7
+ from dbgraph.entity.rdbgraph import RDatabaseGraph
8
+
9
+
10
+ @dataclass
11
+ class GraphDescriptor(ABC):
12
+ """Base class that generate semantic aspects for a database graph or a sub-graph"""
13
+
14
+ max_workers: int
15
+
16
+ @abstractmethod
17
+ def get_semantic_aspect(
18
+ self, asset: Asset, context: RDatabaseGraph
19
+ ) -> SemanticAspect:
20
+ """Get the description of this asset.
21
+
22
+ Args:
23
+ asset: Target asset
24
+ context: an instance of `RDatabaseGraph`, could be the whole database graph or a sub-graph
25
+
26
+ Returns:
27
+ semantic aspect for this asset
28
+ """
29
+
30
+ def rfill_semantic_aspects(self, graph: RDatabaseGraph) -> RDatabaseGraph:
31
+ tables = graph.get_tables()
32
+ assets = tables
33
+ contexts = [graph.select_connected_tables(table.asset_id) for table in tables]
34
+ for table in tables:
35
+ context = RDatabaseGraph(assets=[table], links=[])
36
+ columns = graph.get_columns(table.asset_id)
37
+ for column in columns:
38
+ assets.append(column)
39
+ contexts.append(context)
40
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
41
+ results = {
42
+ asset.asset_id: future
43
+ for asset, future in zip(
44
+ assets, executor.map(self.get_semantic_aspect, assets, contexts)
45
+ )
46
+ }
47
+ for key, aspect in results.items():
48
+ asset = graph.get_asset(key)
49
+ asset.aspects["semantic_properties"] = aspect
50
+ return graph
51
+
52
+ def rfill_semantic_aspects_seq(self, graph: RDatabaseGraph) -> RDatabaseGraph:
53
+ tables = graph.get_tables()
54
+ assets = tables
55
+ contexts = [graph.select_connected_tables(table.asset_id) for table in tables]
56
+ for table in tables:
57
+ context = RDatabaseGraph(assets=[table], links=[])
58
+ columns = graph.get_columns(table.asset_id)
59
+ for column in columns:
60
+ assets.append(column)
61
+ contexts.append(context)
62
+ results = {}
63
+ for asset, context in zip(assets, contexts):
64
+ results[asset.asset_id] = self.get_semantic_aspect(asset, context)
65
+ for key, aspect in results.items():
66
+ asset = graph.get_asset(key)
67
+ asset.aspects["semantic_properties"] = aspect
68
+ return graph
@@ -0,0 +1,74 @@
1
+ from dataclasses import dataclass
2
+
3
+ from langchain.chat_models import BaseChatModel
4
+ from langchain_core.messages import SystemMessage
5
+ from langchain_core.messages.human import HumanMessage
6
+
7
+ from dbgraph import AssetType
8
+ from dbgraph.descriptor.graph_descriptor import GraphDescriptor
9
+ from dbgraph.entity.aspect import SemanticAspect
10
+ from dbgraph.entity.asset import Asset
11
+ from dbgraph.entity.rdbgraph import RDatabaseGraph
12
+ from dbgraph.render.markdown_renderer import MarkdownRenderer
13
+
14
+
15
+ @dataclass
16
+ class LangchainGraphDescriptor(GraphDescriptor):
17
+ """Implementation of `GraphDescriptor` using Langchain"""
18
+
19
+ model: BaseChatModel
20
+ table_system_prompt: str
21
+ table_question_prompt: str
22
+ column_system_prompt: str
23
+ column_question_prompt: str
24
+ markdown_renderer: MarkdownRenderer
25
+
26
+ def _generate(self, system_prompt: str, question: str) -> str:
27
+ response = self.model.invoke(
28
+ [SystemMessage(content=system_prompt), HumanMessage(content=question)]
29
+ )
30
+ if response.content is None:
31
+ raise ValueError("Couldn't generate description")
32
+ return str(response.content)
33
+
34
+ def _get_semantic_aspect_table(
35
+ self, asset: Asset, context: RDatabaseGraph
36
+ ) -> SemanticAspect:
37
+ self.markdown_renderer.render(context)
38
+ system_prompt = (
39
+ self.table_system_prompt + "\n" + self.markdown_renderer.get_content()
40
+ )
41
+ question = self.table_question_prompt + " " + asset.name
42
+ description = self._generate(system_prompt, question)
43
+ return SemanticAspect(
44
+ name=f"{asset.name}_semantic_properties",
45
+ description=description,
46
+ keywords=[],
47
+ )
48
+
49
+ def _get_semantic_aspect_column(
50
+ self, asset: Asset, context: RDatabaseGraph
51
+ ) -> SemanticAspect:
52
+ self.markdown_renderer.render(context)
53
+ system_prompt = (
54
+ self.column_system_prompt + "\n" + self.markdown_renderer.get_content()
55
+ )
56
+ question = self.column_question_prompt + " " + asset.name
57
+ description = self._generate(system_prompt, question)
58
+ return SemanticAspect(
59
+ name=f"{asset.name}_semantic_properties",
60
+ description=description,
61
+ keywords=[],
62
+ )
63
+
64
+ def get_semantic_aspect(
65
+ self, asset: Asset, context: RDatabaseGraph
66
+ ) -> SemanticAspect:
67
+ if asset.type == AssetType.RCOLUMN:
68
+ return self._get_semantic_aspect_column(asset, context)
69
+ elif asset.type == AssetType.RTABLE:
70
+ return self._get_semantic_aspect_table(asset, context)
71
+ else:
72
+ raise NotImplementedError(
73
+ "Only support for table and column in relational databases"
74
+ )
@@ -0,0 +1,80 @@
1
+ TABLE_SYSTEM_PROMPT = """
2
+ You are an expert in SQL database schema understanding.
3
+
4
+ Generate a concise semantic description of the database table from the provided schema.
5
+
6
+ Your description should capture:
7
+ 1. WHAT the table represents.
8
+ 2. WHAT a single row represents.
9
+ 3. The table's primary business or technical purpose.
10
+ 4. Important relationships to other entities when they can be inferred from foreign keys.
11
+ 5. Whether the table represents an entity, relationship/mapping, transaction, event, log, audit record, or reference data when this is clear from the schema.
12
+
13
+ Inference rules:
14
+ - Use the table name, column names, data types, primary keys, foreign keys, unique constraints, and relationships.
15
+ - Prefer semantic meaning over simply repeating column names.
16
+ - Do not infer information that is not reasonably supported by the schema.
17
+ - When uncertain, use conservative wording rather than guessing.
18
+ - For junction tables, describe the relationship between the referenced entities.
19
+ - For tables with timestamps/status fields, mention their temporal or state-tracking role only when supported by the schema.
20
+
21
+ Output rules:
22
+ - Return ONLY the table description.
23
+ - Use plain text, with no Markdown.
24
+ - Use 1 concise sentence, or 2 sentences if necessary.
25
+ - Do not start with "Description:".
26
+ - Do not list columns unless necessary to explain the table.
27
+ - Do not include example values.
28
+ - Do not include SQL, comments, explanations, or reasoning.
29
+ - Do not ask for additional information.
30
+
31
+ Relevant schema:
32
+ """
33
+
34
+ TABLE_QUESTION_PROMPT = """
35
+ Generate a short description for the following table:
36
+ """
37
+
38
+ COLUMN_SYSTEM_PROMPT = """
39
+ You are an expert in SQL database schema understanding.
40
+
41
+ Your task is to generate a concise semantic description of the given column based only on the provided schema.
42
+
43
+ The description should explain the semantic meaning and role of the column within its table, rather than merely restating its name or data type.
44
+
45
+ Focus on:
46
+ - WHAT the column represents.
47
+ - WHAT the value means in the context of the table.
48
+ - The role of the column when it can be inferred, such as identifier, attribute, status, flag, timestamp, date, amount, quantity, metric, category, or foreign key.
49
+ - The entity or concept that the column refers to.
50
+ - For foreign keys, describe what entity the column identifies or references.
51
+ - For status/type/category columns, describe what concept or state they represent.
52
+ - For boolean/flag columns, describe the condition or property represented by the flag.
53
+ - For date/time columns, describe the business or technical event represented by the timestamp when it can be inferred.
54
+ - For numeric columns, describe the semantic meaning of the number rather than only saying it is a numeric value.
55
+ - Use surrounding columns, primary keys, foreign keys, constraints, and the table's purpose to disambiguate the column's meaning.
56
+
57
+ Inference rules:
58
+ - Interpret the column in the context of the table, not in isolation.
59
+ - Prefer semantic meaning over literal column-name expansion.
60
+ - Use relationships and constraints to infer meaning when possible.
61
+ - Do not invent business rules, meanings, units, or semantics that are not reasonably supported by the schema.
62
+ - If the meaning is ambiguous, provide the most conservative description supported by the available schema.
63
+ - Do not assume that a column name has a standard meaning if the schema provides evidence otherwise.
64
+
65
+ Output rules:
66
+ - Return ONLY the column description.
67
+ - Use plain text with no Markdown.
68
+ - Use 1 concise sentence, preferably under 25 words.
69
+ - Do not start with "Description:".
70
+ - Do not include example values.
71
+ - Do not list the column's data type unless it is essential to its semantic meaning.
72
+ - Do not include explanations, reasoning, comments, or SQL.
73
+ - Do not ask questions or request additional information.
74
+
75
+ Relevant schema:
76
+ """
77
+
78
+ COLUMN_QUESTION_PROMPT = """
79
+ Generate a short description for the following column:
80
+ """
File without changes