structured2graph 0.1.1__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 (41) hide show
  1. __init__.py +47 -0
  2. core/__init__.py +23 -0
  3. core/hygm/__init__.py +74 -0
  4. core/hygm/hygm.py +2351 -0
  5. core/hygm/models/__init__.py +82 -0
  6. core/hygm/models/graph_models.py +667 -0
  7. core/hygm/models/llm_models.py +229 -0
  8. core/hygm/models/operations.py +176 -0
  9. core/hygm/models/sources.py +68 -0
  10. core/hygm/models/user_operations.py +139 -0
  11. core/hygm/strategies/__init__.py +17 -0
  12. core/hygm/strategies/base.py +36 -0
  13. core/hygm/strategies/deterministic.py +262 -0
  14. core/hygm/strategies/llm.py +904 -0
  15. core/hygm/validation/__init__.py +38 -0
  16. core/hygm/validation/base.py +194 -0
  17. core/hygm/validation/graph_schema_validator.py +687 -0
  18. core/hygm/validation/memgraph_data_validator.py +991 -0
  19. core/migration_agent.py +1369 -0
  20. core/schema/spec.json +155 -0
  21. core/utils/meta_graph.py +108 -0
  22. database/__init__.py +36 -0
  23. database/adapters/__init__.py +11 -0
  24. database/adapters/memgraph.py +318 -0
  25. database/adapters/mysql.py +311 -0
  26. database/adapters/postgresql.py +335 -0
  27. database/analyzer.py +396 -0
  28. database/factory.py +219 -0
  29. database/models.py +209 -0
  30. main.py +518 -0
  31. query_generation/__init__.py +20 -0
  32. query_generation/cypher_generator.py +129 -0
  33. query_generation/schema_utilities.py +88 -0
  34. structured2graph-0.1.1.dist-info/METADATA +197 -0
  35. structured2graph-0.1.1.dist-info/RECORD +41 -0
  36. structured2graph-0.1.1.dist-info/WHEEL +4 -0
  37. structured2graph-0.1.1.dist-info/entry_points.txt +2 -0
  38. structured2graph-0.1.1.dist-info/licenses/LICENSE +21 -0
  39. utils/__init__.py +57 -0
  40. utils/config.py +235 -0
  41. utils/environment.py +404 -0
@@ -0,0 +1,229 @@
1
+ """
2
+ LLM-specific models for Hypothetical Graph Modeling (HyGM).
3
+
4
+ These models define the structure for LLM input/output and interactive
5
+ operations.
6
+ """
7
+
8
+ from enum import Enum
9
+ from typing import List, Literal, TYPE_CHECKING
10
+ from pydantic import BaseModel, Field
11
+
12
+ if TYPE_CHECKING:
13
+ from .graph_models import GraphModel
14
+
15
+
16
+ class GraphModelingStrategy(Enum):
17
+ """Graph modeling strategies available."""
18
+
19
+ DETERMINISTIC = "deterministic" # Rule-based graph creation
20
+ LLM_POWERED = "llm_powered" # LLM generates the graph model
21
+
22
+
23
+ class ModelingMode(Enum):
24
+ """Modeling modes available."""
25
+
26
+ AUTOMATIC = "automatic" # Generate model without user interaction
27
+ INTERACTIVE = "interactive" # Interactive mode with user feedback
28
+
29
+
30
+ # Structured output models for LLM graph generation
31
+ class LLMGraphNode(BaseModel):
32
+ """Node definition for LLM-generated graph models."""
33
+
34
+ name: str = Field(description="Unique identifier for the node")
35
+ labels: List[str] = Field(
36
+ description="Cypher labels for the node (e.g., ['User'], ['Product'])"
37
+ )
38
+ properties: List[str] = Field(
39
+ description="List of properties to include from source table"
40
+ )
41
+ primary_key: str = Field(description="Primary key property name")
42
+ indexes: List[str] = Field(description="Properties that should have indexes")
43
+ constraints: List[str] = Field(
44
+ description="Properties that should have uniqueness constraints"
45
+ )
46
+ source_table: str = Field(description="Source SQL table name")
47
+
48
+ class Config:
49
+ """Pydantic config for OpenAI structured output compatibility."""
50
+
51
+ extra = "forbid"
52
+
53
+
54
+ class LLMGraphRelationship(BaseModel):
55
+ """Relationship definition for LLM-generated graph models."""
56
+
57
+ name: str = Field(description="Relationship type name (e.g., 'OWNS', 'BELONGS_TO')")
58
+ type: Literal["one_to_many", "many_to_many", "one_to_one"] = Field(
59
+ description="Cardinality of the relationship"
60
+ )
61
+ from_node: str = Field(description="Source node name")
62
+ to_node: str = Field(description="Target node name")
63
+ properties: List[str] = Field(
64
+ description="Properties to include on the relationship (if any)", default=[]
65
+ )
66
+ directionality: Literal["directed", "undirected"] = Field(
67
+ description="Whether the relationship has direction"
68
+ )
69
+
70
+ class Config:
71
+ """Pydantic config for OpenAI structured output compatibility."""
72
+
73
+ extra = "forbid"
74
+
75
+
76
+ class LLMGraphModel(BaseModel):
77
+ """Complete graph model structure for LLM generation."""
78
+
79
+ nodes: List[LLMGraphNode] = Field(description="All nodes in the graph model")
80
+ relationships: List[LLMGraphRelationship] = Field(
81
+ description="All relationships in the graph model"
82
+ )
83
+
84
+ class Config:
85
+ """Pydantic config for OpenAI structured output compatibility."""
86
+
87
+ extra = "forbid"
88
+
89
+ def to_graph_model(self) -> "GraphModel":
90
+ """Convert LLMGraphModel to GraphModel for schema export."""
91
+ from .graph_models import (
92
+ GraphModel,
93
+ GraphNode,
94
+ GraphRelationship,
95
+ GraphProperty,
96
+ GraphIndex,
97
+ GraphConstraint,
98
+ )
99
+ from .sources import (
100
+ NodeSource,
101
+ RelationshipSource,
102
+ PropertySource,
103
+ IndexSource,
104
+ ConstraintSource,
105
+ )
106
+
107
+ # Convert nodes
108
+ graph_nodes = []
109
+ node_indexes = []
110
+ node_constraints = []
111
+
112
+ for llm_node in self.nodes:
113
+ # Create properties with proper GraphProperty objects
114
+ properties = []
115
+ for prop_name in llm_node.properties:
116
+ graph_prop = GraphProperty(
117
+ key=prop_name,
118
+ count=1,
119
+ filling_factor=100.0,
120
+ types=[{"type": "String", "count": 1, "examples": [""]}],
121
+ source=PropertySource(field=f"{llm_node.source_table}.{prop_name}"),
122
+ )
123
+ properties.append(graph_prop)
124
+
125
+ # Create node source
126
+ node_source = NodeSource(
127
+ type="table",
128
+ name=llm_node.source_table,
129
+ location=f"database.schema.{llm_node.source_table}",
130
+ mapping={
131
+ "labels": llm_node.labels,
132
+ "id_field": (f"{llm_node.source_table}.{llm_node.primary_key}"),
133
+ },
134
+ )
135
+
136
+ graph_node = GraphNode(
137
+ labels=llm_node.labels,
138
+ count=1,
139
+ properties=properties,
140
+ examples=[{"gid": 0}],
141
+ source=node_source,
142
+ )
143
+ graph_nodes.append(graph_node)
144
+
145
+ # Create indexes for this node
146
+ for index_prop in llm_node.indexes:
147
+ index_source = IndexSource(
148
+ origin="llm_recommendation",
149
+ reason=f"Index recommended by LLM for {llm_node.name}.{index_prop}",
150
+ created_by="ai_analysis",
151
+ index_name=None,
152
+ migrated_from=None,
153
+ )
154
+ graph_index = GraphIndex(
155
+ labels=llm_node.labels,
156
+ properties=[index_prop],
157
+ type="label+property",
158
+ source=index_source,
159
+ )
160
+ node_indexes.append(graph_index)
161
+
162
+ # Create constraints for this node
163
+ for constraint_prop in llm_node.constraints:
164
+ constraint_source = ConstraintSource(
165
+ origin="llm_recommendation",
166
+ constraint_name=f"ai_unique_constraint_{llm_node.name}_{constraint_prop}",
167
+ migrated_from="ai_analysis",
168
+ )
169
+ graph_constraint = GraphConstraint(
170
+ type="unique",
171
+ labels=llm_node.labels,
172
+ properties=[constraint_prop],
173
+ source=constraint_source,
174
+ )
175
+ node_constraints.append(graph_constraint)
176
+
177
+ # Convert relationships
178
+ graph_relationships = []
179
+ for llm_rel in self.relationships:
180
+ # Find source/target node labels
181
+ from_labels = []
182
+ to_labels = []
183
+ for node in self.nodes:
184
+ if node.name == llm_rel.from_node:
185
+ from_labels = node.labels
186
+ if node.name == llm_rel.to_node:
187
+ to_labels = node.labels
188
+
189
+ # Create relationship properties
190
+ rel_properties = []
191
+ for prop_name in llm_rel.properties:
192
+ rel_prop = GraphProperty(
193
+ key=prop_name,
194
+ count=1,
195
+ filling_factor=100.0,
196
+ types=[{"type": "String", "count": 1, "examples": [""]}],
197
+ )
198
+ rel_properties.append(rel_prop)
199
+
200
+ # Create relationship source
201
+ rel_source = RelationshipSource(
202
+ type="derived",
203
+ name=f"{llm_rel.from_node}_{llm_rel.name}_{llm_rel.to_node}",
204
+ location=f"derived.{llm_rel.name}",
205
+ mapping={
206
+ "start_node": llm_rel.from_node,
207
+ "end_node": llm_rel.to_node,
208
+ "edge_type": llm_rel.name,
209
+ },
210
+ )
211
+
212
+ graph_rel = GraphRelationship(
213
+ edge_type=llm_rel.name,
214
+ start_node_labels=from_labels,
215
+ end_node_labels=to_labels,
216
+ count=1,
217
+ properties=rel_properties,
218
+ examples=[{}],
219
+ source=rel_source,
220
+ directionality=llm_rel.directionality,
221
+ )
222
+ graph_relationships.append(graph_rel)
223
+
224
+ return GraphModel(
225
+ nodes=graph_nodes,
226
+ edges=graph_relationships,
227
+ node_indexes=node_indexes,
228
+ node_constraints=node_constraints,
229
+ )
@@ -0,0 +1,176 @@
1
+ """
2
+ Operation models for interactive graph model modifications.
3
+
4
+ These models define the structure for operations that can be applied to
5
+ graph models during interactive sessions.
6
+ """
7
+
8
+ from typing import List, Union, Literal
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class ModelOperation(BaseModel):
13
+ """Base class for model operations."""
14
+
15
+ operation_type: str = Field(description="Type of operation to perform")
16
+ description: str = Field(description="Human-readable description of the operation")
17
+
18
+ class Config:
19
+ """Pydantic config for OpenAI structured output compatibility."""
20
+
21
+ extra = "forbid"
22
+
23
+
24
+ class ChangeNodeLabelOperation(ModelOperation):
25
+ """Operation to change a node's label."""
26
+
27
+ operation_type: Literal["change_node_label"] = "change_node_label"
28
+ old_label: str = Field(description="Current node label")
29
+ new_label: str = Field(description="New node label")
30
+
31
+
32
+ class RenamePropertyOperation(ModelOperation):
33
+ """Operation to rename a property."""
34
+
35
+ operation_type: Literal["rename_property"] = "rename_property"
36
+ node_label: str = Field(description="Node label containing the property")
37
+ old_property: str = Field(description="Current property name")
38
+ new_property: str = Field(description="New property name")
39
+
40
+
41
+ class DropPropertyOperation(ModelOperation):
42
+ """Operation to drop a property."""
43
+
44
+ operation_type: Literal["drop_property"] = "drop_property"
45
+ node_label: str = Field(description="Node label containing the property")
46
+ property_name: str = Field(description="Property name to drop")
47
+
48
+
49
+ class AddPropertyOperation(ModelOperation):
50
+ """Operation to add a property."""
51
+
52
+ operation_type: Literal["add_property"] = "add_property"
53
+ node_label: str = Field(description="Node label to add property to")
54
+ property_name: str = Field(description="Property name to add")
55
+
56
+
57
+ class ChangeRelationshipNameOperation(ModelOperation):
58
+ """Operation to change a relationship name."""
59
+
60
+ operation_type: Literal["change_relationship_name"] = "change_relationship_name"
61
+ old_name: str = Field(description="Current relationship name")
62
+ new_name: str = Field(description="New relationship name")
63
+
64
+
65
+ class DropRelationshipOperation(ModelOperation):
66
+ """Operation to drop a relationship."""
67
+
68
+ operation_type: Literal["drop_relationship"] = "drop_relationship"
69
+ relationship_name: str = Field(description="Relationship name to drop")
70
+
71
+
72
+ class AddIndexOperation(ModelOperation):
73
+ """Operation to add an index."""
74
+
75
+ operation_type: Literal["add_index"] = "add_index"
76
+ node_label: str = Field(description="Node label for the index")
77
+ property_name: str = Field(description="Property name for the index")
78
+
79
+
80
+ class DropIndexOperation(ModelOperation):
81
+ """Operation to drop an index."""
82
+
83
+ operation_type: Literal["drop_index"] = "drop_index"
84
+ node_label: str = Field(description="Node label for the index")
85
+ property_name: str = Field(description="Property name for the index")
86
+
87
+
88
+ class AddConstraintOperation(ModelOperation):
89
+ """Operation to add a constraint."""
90
+
91
+ operation_type: Literal["add_constraint"] = "add_constraint"
92
+ node_label: str = Field(description="Node label for the constraint")
93
+ property_name: str = Field(description="Property name for the constraint")
94
+ constraint_type: Literal["unique", "existence", "data_type"] = Field(
95
+ description="Type of constraint (unique, existence, or data_type)"
96
+ )
97
+ data_type: str = Field(
98
+ default="", description="Data type for data_type constraints"
99
+ )
100
+
101
+
102
+ class DropConstraintOperation(ModelOperation):
103
+ """Operation to drop a constraint."""
104
+
105
+ operation_type: Literal["drop_constraint"] = "drop_constraint"
106
+ node_label: str = Field(description="Node label for the constraint")
107
+ property_name: str = Field(description="Property name for the constraint")
108
+ constraint_type: Literal["unique", "existence", "data_type"] = Field(
109
+ description="Type of constraint to drop"
110
+ )
111
+
112
+
113
+ class AddNodeOperation(ModelOperation):
114
+ """Operation to add a new node type."""
115
+
116
+ operation_type: Literal["add_node"] = "add_node"
117
+ node_label: str = Field(description="Label for the new node")
118
+ properties: List[str] = Field(
119
+ default_factory=list, description="List of property names for the new node"
120
+ )
121
+ source_table: str = Field(
122
+ default="", description="Source table name if mapping from database"
123
+ )
124
+
125
+
126
+ class DropNodeOperation(ModelOperation):
127
+ """Operation to drop a node type."""
128
+
129
+ operation_type: Literal["drop_node"] = "drop_node"
130
+ node_label: str = Field(description="Label of the node to drop")
131
+
132
+
133
+ class AddRelationshipOperation(ModelOperation):
134
+ """Operation to add a new relationship."""
135
+
136
+ operation_type: Literal["add_relationship"] = "add_relationship"
137
+ relationship_name: str = Field(description="Name of the new relationship")
138
+ start_node_label: str = Field(description="Label of the start node")
139
+ end_node_label: str = Field(description="Label of the end node")
140
+ properties: List[str] = Field(
141
+ default_factory=list, description="List of property names for the relationship"
142
+ )
143
+
144
+
145
+ # Union type for all operations with discriminator
146
+ OperationType = Union[
147
+ ChangeNodeLabelOperation,
148
+ RenamePropertyOperation,
149
+ DropPropertyOperation,
150
+ AddPropertyOperation,
151
+ ChangeRelationshipNameOperation,
152
+ DropRelationshipOperation,
153
+ AddIndexOperation,
154
+ DropIndexOperation,
155
+ AddConstraintOperation,
156
+ DropConstraintOperation,
157
+ AddNodeOperation,
158
+ DropNodeOperation,
159
+ AddRelationshipOperation,
160
+ ]
161
+
162
+
163
+ class ModelModifications(BaseModel):
164
+ """Container for multiple model operations."""
165
+
166
+ operations: List[OperationType] = Field(
167
+ description="List of operations to apply to the graph model"
168
+ )
169
+ reasoning: str = Field(
170
+ description="Explanation of why these changes improve the model"
171
+ )
172
+
173
+ class Config:
174
+ """Pydantic config for OpenAI structured output compatibility."""
175
+
176
+ extra = "forbid"
@@ -0,0 +1,68 @@
1
+ """
2
+ Source tracking models for Hypothetical Graph Modeling (HyGM).
3
+
4
+ These models track the origin and mapping of graph elements to their source data.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Dict, Any, Optional
9
+
10
+
11
+ @dataclass
12
+ class PropertySource:
13
+ """Source information for a property."""
14
+
15
+ field: str # Source field name (e.g., "users.name")
16
+ transformation: Optional[str] = None # Any transformation applied
17
+
18
+
19
+ @dataclass
20
+ class NodeSource:
21
+ """Source information for a node."""
22
+
23
+ type: str # "table", "view", "file", "api", "manual"
24
+ name: str # Source name (e.g., table name)
25
+ location: str # Full location path
26
+ mapping: Dict[str, Any] # Mapping details for labels, id_field, etc.
27
+
28
+
29
+ @dataclass
30
+ class RelationshipSource:
31
+ """Source information for a relationship."""
32
+
33
+ type: str # "table", "view", "many_to_many", "derived"
34
+ name: str # Source name
35
+ location: str # Full location path
36
+ mapping: Dict[str, Any] # Mapping for start_node, end_node, edge_type
37
+
38
+
39
+ @dataclass
40
+ class IndexSource:
41
+ """Source information for an index."""
42
+
43
+ origin: str # "source_database_index", "migration_requirement", etc.
44
+ reason: str # Why this index was created
45
+ created_by: str # Who/what created this index
46
+ index_name: Optional[str] = None # Original index name if applicable
47
+ migrated_from: Optional[str] = None # Source location
48
+
49
+
50
+ @dataclass
51
+ class ConstraintSource:
52
+ """Source information for a constraint."""
53
+
54
+ origin: str # "source_database_constraint", "migration_requirement", etc.
55
+ constraint_name: Optional[str] = None # Original constraint name
56
+ migrated_from: Optional[str] = None # Source location
57
+ reason: Optional[str] = None # Why this constraint exists
58
+ created_by: Optional[str] = None # Who/what created this constraint
59
+
60
+
61
+ @dataclass
62
+ class EnumSource:
63
+ """Source information for an enum."""
64
+
65
+ origin: str # "source_database_enum", "detected_from_data", etc.
66
+ enum_name: Optional[str] = None # Original enum name if applicable
67
+ migrated_from: Optional[str] = None # Source location
68
+ created_by: Optional[str] = None # Who/what created this enum
@@ -0,0 +1,139 @@
1
+ """
2
+ User operation tracking for preserving user intentions in graph modeling.
3
+
4
+ This module tracks user operations to ensure they are preserved when
5
+ LLM strategies regenerate models for validation fixes.
6
+ """
7
+
8
+ from typing import List, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from .operations import OperationType
12
+ else:
13
+ try:
14
+ from .operations import OperationType
15
+ except ImportError:
16
+ # Fallback for circular imports
17
+ pass
18
+
19
+
20
+ class UserOperationRecord:
21
+ """Record of a user operation."""
22
+
23
+ def __init__(self, operation: "OperationType"):
24
+ self.operation = operation
25
+
26
+
27
+ class UserOperationHistory:
28
+ """Container for tracking all user operations in a session."""
29
+
30
+ def __init__(self, session_id: str):
31
+ self.operations: List[UserOperationRecord] = []
32
+ self.session_id = session_id
33
+
34
+ def add_operation(self, operation: "OperationType") -> None:
35
+ """Add a new user operation to the history."""
36
+ record = UserOperationRecord(operation)
37
+ self.operations.append(record)
38
+
39
+ def get_operations_by_type(self, operation_type: str) -> List[UserOperationRecord]:
40
+ """Get all operations of a specific type."""
41
+ return [
42
+ op
43
+ for op in self.operations
44
+ if op.operation.operation_type == operation_type
45
+ ]
46
+
47
+ def has_label_changes(self) -> bool:
48
+ """Check if user has made any label changes."""
49
+ return len(self.get_operations_by_type("change_node_label")) > 0
50
+
51
+ def to_llm_context(self) -> str:
52
+ """Generate context string for LLM about user operations."""
53
+ if not self.operations:
54
+ return ""
55
+
56
+ context_parts = ["USER CHANGES TO PRESERVE:", ""]
57
+
58
+ for i, record in enumerate(self.operations, 1):
59
+ op = record.operation
60
+
61
+ if op.operation_type == "change_node_label":
62
+ old_label = getattr(op, "old_label", "unknown")
63
+ new_label = getattr(op, "new_label", "unknown")
64
+ context_parts.append(f"{i}. Node label: '{old_label}' → '{new_label}'")
65
+ elif op.operation_type == "rename_property":
66
+ node_label = getattr(op, "node_label", "unknown")
67
+ old_prop = getattr(op, "old_property", "unknown")
68
+ new_prop = getattr(op, "new_property", "unknown")
69
+ context_parts.append(
70
+ f"{i}. Property: {node_label}.{old_prop} → {new_prop}"
71
+ )
72
+ elif op.operation_type == "drop_property":
73
+ node_label = getattr(op, "node_label", "unknown")
74
+ prop_name = getattr(op, "property_name", "unknown")
75
+ context_parts.append(f"{i}. Remove: {node_label}.{prop_name}")
76
+ elif op.operation_type == "add_property":
77
+ node_label = getattr(op, "node_label", "unknown")
78
+ prop_name = getattr(op, "property_name", "unknown")
79
+ context_parts.append(f"{i}. Add: {node_label}.{prop_name}")
80
+ elif op.operation_type == "drop_constraint":
81
+ node_label = getattr(op, "node_label", "unknown")
82
+ prop_name = getattr(op, "property_name", "unknown")
83
+ constraint_type = getattr(op, "constraint_type", "unknown")
84
+ context_parts.append(
85
+ f"{i}. Drop constraint: {constraint_type.upper()} on "
86
+ f"{node_label}.{prop_name}"
87
+ )
88
+ elif op.operation_type == "add_constraint":
89
+ node_label = getattr(op, "node_label", "unknown")
90
+ prop_name = getattr(op, "property_name", "unknown")
91
+ constraint_type = getattr(op, "constraint_type", "unknown")
92
+ context_parts.append(
93
+ f"{i}. Add constraint: {constraint_type.upper()} on "
94
+ f"{node_label}.{prop_name}"
95
+ )
96
+ elif op.operation_type == "drop_index":
97
+ node_label = getattr(op, "node_label", "unknown")
98
+ prop_name = getattr(op, "property_name", "unknown")
99
+ context_parts.append(f"{i}. Drop index: {node_label}.{prop_name}")
100
+ elif op.operation_type == "add_index":
101
+ node_label = getattr(op, "node_label", "unknown")
102
+ prop_name = getattr(op, "property_name", "unknown")
103
+ context_parts.append(f"{i}. Add index: {node_label}.{prop_name}")
104
+ elif op.operation_type == "change_relationship_name":
105
+ old_name = getattr(op, "old_name", "unknown")
106
+ new_name = getattr(op, "new_name", "unknown")
107
+ context_parts.append(f"{i}. Relationship: {old_name} → {new_name}")
108
+ elif op.operation_type == "drop_relationship":
109
+ rel_name = getattr(op, "relationship_name", "unknown")
110
+ context_parts.append(f"{i}. Drop relationship: {rel_name}")
111
+ elif op.operation_type == "add_node":
112
+ node_label = getattr(op, "node_label", "unknown")
113
+ properties = getattr(op, "properties", [])
114
+ prop_list = ", ".join(properties) if properties else "no properties"
115
+ context_parts.append(f"{i}. Add node: {node_label} ({prop_list})")
116
+ elif op.operation_type == "drop_node":
117
+ node_label = getattr(op, "node_label", "unknown")
118
+ context_parts.append(f"{i}. Drop node: {node_label}")
119
+ elif op.operation_type == "add_relationship":
120
+ rel_name = getattr(op, "relationship_name", "unknown")
121
+ start_node = getattr(op, "start_node_label", "unknown")
122
+ end_node = getattr(op, "end_node_label", "unknown")
123
+ context_parts.append(
124
+ f"{i}. Add relationship: ({start_node})-[:{rel_name}]->({end_node})"
125
+ )
126
+
127
+ context_parts.extend(
128
+ ["", "PRESERVE these user changes exactly when improving the model."]
129
+ )
130
+
131
+ return "\n".join(context_parts)
132
+
133
+ def copy(self) -> "UserOperationHistory":
134
+ """Create a deep copy of the user operation history."""
135
+ new_history = UserOperationHistory(self.session_id)
136
+ new_history.operations = [
137
+ UserOperationRecord(op.operation) for op in self.operations
138
+ ]
139
+ return new_history
@@ -0,0 +1,17 @@
1
+ """
2
+ Modeling strategies for Hypothetical Graph Modeling (HyGM).
3
+
4
+ This package contains different strategies for creating graph models:
5
+ - DeterministicStrategy: Rule-based deterministic modeling
6
+ - LLMStrategy: AI-powered modeling using language models
7
+ """
8
+
9
+ from .base import BaseModelingStrategy
10
+ from .deterministic import DeterministicStrategy
11
+ from .llm import LLMStrategy
12
+
13
+ __all__ = [
14
+ "BaseModelingStrategy",
15
+ "DeterministicStrategy",
16
+ "LLMStrategy",
17
+ ]
@@ -0,0 +1,36 @@
1
+ """
2
+ Base strategy interface for Hypothetical Graph Modeling (HyGM).
3
+
4
+ This module defines the base interface that all modeling strategies must implement.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Dict, Any, Optional, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from ..models.graph_models import GraphModel
12
+
13
+
14
+ class BaseModelingStrategy(ABC):
15
+ """Base class for all graph modeling strategies."""
16
+
17
+ @abstractmethod
18
+ def create_model(
19
+ self, database_structure: Dict[str, Any], domain_context: Optional[str] = None
20
+ ) -> "GraphModel":
21
+ """
22
+ Create a graph model from database structure.
23
+
24
+ Args:
25
+ database_structure: Analyzed database structure
26
+ domain_context: Optional domain-specific context
27
+
28
+ Returns:
29
+ GraphModel: Generated graph model
30
+ """
31
+ pass
32
+
33
+ @abstractmethod
34
+ def get_strategy_name(self) -> str:
35
+ """Return the name of this strategy."""
36
+ pass