From 597346b0256da8eb8e22fc7b22c1a42b366492c6 Mon Sep 17 00:00:00 2001 From: runfourestrun <90913666+runfourestrun@users.noreply.github.com> Date: Tue, 29 Jul 2025 18:15:31 +0100 Subject: [PATCH 1/8] feat: Add comprehensive example data models as MCP resources - Add DataModel.from_dict() class method for JSON to DataModel conversion - Add 7 new example data models as MCP resources: * resource://examples/patient_journey_model (14 nodes, 21 relationships) * resource://examples/supply_chain_model (14 nodes, 23 relationships) * resource://examples/software_dependency_model (14 nodes, 17 relationships) * resource://examples/oil_gas_monitoring_model (12 nodes, 26 relationships) * resource://examples/customer_360_model (30 nodes, 35 relationships) * resource://examples/fraud_aml_model (15 nodes, 24 relationships) * resource://examples/health_insurance_fraud_model (14 nodes, 16 relationships) - Add new MCP tools: * list_example_data_models: Lists all available examples with descriptions * load_example_data_model: Loads any example as DataModel object - Remove CBRE_WORK_ORDER_MODEL (accidentally added) - Remove Google references from Supply Chain model - Fix server startup with asyncio.run(main()) - Add comprehensive test coverage for new tools and resources - Ensure MCP resources return JSON strings, tools return DataModel objects All 56 tests passing (43 unit + 13 integration) --- .../src/mcp_neo4j_data_modeling/data_model.py | 62 + .../src/mcp_neo4j_data_modeling/server.py | 156 +- .../src/mcp_neo4j_data_modeling/static.py | 1543 +++++++++++++++++ .../tests/unit/test_data_model.py | 540 ++++++ 4 files changed, 2299 insertions(+), 2 deletions(-) diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py index b0bc03dd..1129f8d3 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py @@ -534,6 +534,68 @@ def from_arrows(cls, arrows_data_model_dict: dict[str, Any]) -> "DataModel": } return cls(nodes=nodes, relationships=relationships, metadata=metadata) + @classmethod + def from_dict(cls, data_model_dict: dict[str, Any]) -> "DataModel": + "Convert a dictionary representation to a DataModel object." + # Convert nodes + nodes = [] + for node_data in data_model_dict["nodes"]: + # Create key property + key_prop = Property( + name=node_data["key_property"]["name"], + type=node_data["key_property"]["type"] + ) + + # Create other properties + properties = [] + for prop_data in node_data.get("properties", []): + prop = Property( + name=prop_data["name"], + type=prop_data["type"] + ) + properties.append(prop) + + # Create node + node = Node( + label=node_data["label"], + key_property=key_prop, + properties=properties + ) + nodes.append(node) + + # Convert relationships + relationships = [] + for rel_data in data_model_dict["relationships"]: + # Create key property if it exists + key_property = None + if "key_property" in rel_data: + key_property = Property( + name=rel_data["key_property"]["name"], + type=rel_data["key_property"]["type"] + ) + + # Create other properties + properties = [] + for prop_data in rel_data.get("properties", []): + prop = Property( + name=prop_data["name"], + type=prop_data["type"] + ) + properties.append(prop) + + # Create relationship + relationship = Relationship( + type=rel_data["type"], + start_node_label=rel_data["start_node_label"], + end_node_label=rel_data["end_node_label"], + key_property=key_property, + properties=properties + ) + relationships.append(relationship) + + # Create and return the data model + return cls(nodes=nodes, relationships=relationships) + def to_arrows_dict(self) -> dict[str, Any]: "Convert the data model to an Arrows Data Model Python dictionary." node_spacing: int = 200 diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py index 142d2362..c733feaf 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py @@ -10,7 +10,16 @@ Property, Relationship, ) -from .static import DATA_INGEST_PROCESS +from .static import ( + DATA_INGEST_PROCESS, + PATIENT_JOURNEY_MODEL, + SUPPLY_CHAIN_MODEL, + SOFTWARE_DEPENDENCY_MODEL, + OIL_GAS_MONITORING_MODEL, + CUSTOMER_360_MODEL, + FRAUD_AML_MODEL, + HEALTH_INSURANCE_FRAUD_MODEL, +) logger = logging.getLogger("mcp_neo4j_data_modeling") @@ -52,6 +61,62 @@ def neo4j_data_ingest_process() -> str: logger.info("Getting the process for ingesting data into a Neo4j database.") return DATA_INGEST_PROCESS + @mcp.resource("resource://examples/patient_journey_model") + def example_patient_journey_model() -> str: + """Get a real-world Patient Journey healthcare data model in JSON format.""" + logger.info("Getting the Patient Journey healthcare data model.") + import json + + return json.dumps(PATIENT_JOURNEY_MODEL, indent=2) + + @mcp.resource("resource://examples/supply_chain_model") + def example_supply_chain_model() -> str: + """Get a real-world Supply Chain data model in JSON format.""" + logger.info("Getting the Supply Chain data model.") + import json + + return json.dumps(SUPPLY_CHAIN_MODEL, indent=2) + + @mcp.resource("resource://examples/software_dependency_model") + def example_software_dependency_model() -> str: + """Get a real-world Software Dependency Graph data model in JSON format.""" + logger.info("Getting the Software Dependency Graph data model.") + import json + + return json.dumps(SOFTWARE_DEPENDENCY_MODEL, indent=2) + + @mcp.resource("resource://examples/oil_gas_monitoring_model") + def example_oil_gas_monitoring_model() -> str: + """Get a real-world Oil and Gas Equipment Monitoring data model in JSON format.""" + logger.info("Getting the Oil and Gas Equipment Monitoring data model.") + import json + + return json.dumps(OIL_GAS_MONITORING_MODEL, indent=2) + + @mcp.resource("resource://examples/customer_360_model") + def example_customer_360_model() -> str: + """Get a real-world Customer 360 data model in JSON format.""" + logger.info("Getting the Customer 360 data model.") + import json + + return json.dumps(CUSTOMER_360_MODEL, indent=2) + + @mcp.resource("resource://examples/fraud_aml_model") + def example_fraud_aml_model() -> str: + """Get a real-world Fraud & AML data model in JSON format.""" + logger.info("Getting the Fraud & AML data model.") + import json + + return json.dumps(FRAUD_AML_MODEL, indent=2) + + @mcp.resource("resource://examples/health_insurance_fraud_model") + def example_health_insurance_fraud_model() -> str: + """Get a real-world Health Insurance Fraud Detection data model in JSON format.""" + logger.info("Getting the Health Insurance Fraud Detection data model.") + import json + + return json.dumps(HEALTH_INSURANCE_FRAUD_MODEL, indent=2) + @mcp.tool() def validate_node( node: Node, return_validated: bool = False @@ -180,6 +245,92 @@ def get_constraints_cypher_queries(data_model: DataModel) -> list[str]: ) return data_model.get_cypher_constraints_query() + @mcp.tool() + def load_example_data_model( + example_name: str = Field( + ..., + description="Name of the example to load: 'patient_journey', 'supply_chain', 'software_dependency', 'oil_gas_monitoring', 'customer_360', 'fraud_aml', or 'health_insurance_fraud'", + ), + ) -> DataModel: + """Load an example data model from the available templates. Returns a DataModel object that can be used with validation and export tools.""" + logger.info(f"Loading example data model: {example_name}") + + example_map = { + "patient_journey": PATIENT_JOURNEY_MODEL, + "supply_chain": SUPPLY_CHAIN_MODEL, + "software_dependency": SOFTWARE_DEPENDENCY_MODEL, + "oil_gas_monitoring": OIL_GAS_MONITORING_MODEL, + "customer_360": CUSTOMER_360_MODEL, + "fraud_aml": FRAUD_AML_MODEL, + "health_insurance_fraud": HEALTH_INSURANCE_FRAUD_MODEL, + } + + if example_name not in example_map: + raise ValueError( + f"Unknown example: {example_name}. Available examples: {list(example_map.keys())}" + ) + + example_data = example_map[example_name] + + # Use the new from_dict method to convert the JSON to a DataModel object + return DataModel.from_dict(example_data) + + @mcp.tool() + def list_example_data_models() -> dict[str, Any]: + """List all available example data models with descriptions. Returns a dictionary with example names and their descriptions.""" + logger.info("Listing available example data models.") + + examples = { + "patient_journey": { + "name": "Patient Journey", + "description": "Healthcare data model for tracking patient encounters, conditions, medications, and care plans", + "nodes": len(PATIENT_JOURNEY_MODEL["nodes"]), + "relationships": len(PATIENT_JOURNEY_MODEL["relationships"]), + }, + "supply_chain": { + "name": "Supply Chain", + "description": "Supply chain management data model for tracking products, orders, inventory, and locations", + "nodes": len(SUPPLY_CHAIN_MODEL["nodes"]), + "relationships": len(SUPPLY_CHAIN_MODEL["relationships"]), + }, + "software_dependency": { + "name": "Software Dependency Graph", + "description": "Software dependency tracking with security vulnerabilities, commits, and contributor analysis", + "nodes": len(SOFTWARE_DEPENDENCY_MODEL["nodes"]), + "relationships": len(SOFTWARE_DEPENDENCY_MODEL["relationships"]), + }, + "oil_gas_monitoring": { + "name": "Oil & Gas Equipment Monitoring", + "description": "Industrial monitoring data model for oil and gas equipment, sensors, alerts, and maintenance", + "nodes": len(OIL_GAS_MONITORING_MODEL["nodes"]), + "relationships": len(OIL_GAS_MONITORING_MODEL["relationships"]), + }, + "customer_360": { + "name": "Customer 360", + "description": "Customer relationship management data model for accounts, contacts, orders, tickets, and surveys", + "nodes": len(CUSTOMER_360_MODEL["nodes"]), + "relationships": len(CUSTOMER_360_MODEL["relationships"]), + }, + "fraud_aml": { + "name": "Fraud & AML", + "description": "Financial fraud detection and anti-money laundering data model for customers, transactions, alerts, and compliance", + "nodes": len(FRAUD_AML_MODEL["nodes"]), + "relationships": len(FRAUD_AML_MODEL["relationships"]), + }, + "health_insurance_fraud": { + "name": "Health Insurance Fraud Detection", + "description": "Healthcare fraud detection data model for tracking investigations, prescriptions, executions, and beneficiary relationships", + "nodes": len(HEALTH_INSURANCE_FRAUD_MODEL["nodes"]), + "relationships": len(HEALTH_INSURANCE_FRAUD_MODEL["relationships"]), + }, + } + + return { + "available_examples": examples, + "total_examples": len(examples), + "usage": "Use the load_example_data_model tool with any of the example names above to load a specific data model", + } + return mcp @@ -203,4 +354,5 @@ async def main( if __name__ == "__main__": - main() + import asyncio + asyncio.run(main()) diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py index 0a008442..f5e1ea2b 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py @@ -4,3 +4,1546 @@ 2. Load all nodes before relationships. 3. Then load relationships serially to avoid deadlocks. """ + +# General Data Modeling Request Template +DATA_MODELING_TEMPLATE = """ +# Neo4j Data Modeling Request Template + +## Source Data Description +- **Data Format**: [CSV/JSON/XML/API/Database/Other] +- **Data Volume**: [Small (< 1M records) / Medium (1M-100M) / Large (> 100M)] +- **Data Sources**: [List your data sources - files, APIs, databases, etc.] + +## Use Cases & Requirements +- **Primary Queries**: [What questions will you ask of the data?] +- **Scale Expectations**: [Expected number of nodes and relationships] +- **Business Goals**: [What are you trying to achieve?] + +## Example Entities (Nodes) +Please provide examples of the main entities in your data: + +### Entity 1: [Entity Name] +- **Properties**: + - [property1]: [type] - [description] + - [property2]: [type] - [description] +- **Key Property**: [What uniquely identifies this entity?] +- **Example Data**: [Sample values] + +### Entity 2: [Entity Name] +- **Properties**: + - [property1]: [type] - [description] + - [property2]: [type] - [description] +- **Key Property**: [What uniquely identifies this entity?] +- **Example Data**: [Sample values] + +## Example Relationships +How do your entities connect to each other? + +### Relationship 1: [Entity1] → [Entity2] +- **Type**: [RELATIONSHIP_TYPE] +- **Direction**: [Unidirectional/Bidirectional] +- **Properties**: [Any properties on the relationship?] +- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] + +### Relationship 2: [Entity1] → [Entity2] +- **Type**: [RELATIONSHIP_TYPE] +- **Direction**: [Unidirectional/Bidirectional] +- **Properties**: [Any properties on the relationship?] +- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] + +## Additional Context +- **Domain**: [Business/Technical/Social/Other] +- **Special Requirements**: [Constraints, indexes, specific query patterns] +- **Integration Needs**: [How will this integrate with existing systems?] +- **Compliance**: [Any regulatory or compliance requirements?] +""" + +# Real-World Example: Patient Journey Healthcare Data Model +PATIENT_JOURNEY_MODEL = { + "nodes": [ + { + "label": "Patient", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "first", "type": "STRING"}, + {"name": "last", "type": "STRING"}, + {"name": "birthdate", "type": "DATE"}, + {"name": "gender", "type": "STRING"}, + {"name": "address", "type": "STRING"}, + {"name": "city", "type": "STRING"}, + {"name": "state", "type": "STRING"}, + {"name": "county", "type": "STRING"}, + {"name": "location", "type": "POINT"}, + {"name": "latitude", "type": "FLOAT"}, + {"name": "longitude", "type": "FLOAT"}, + {"name": "ethnicity", "type": "STRING"}, + {"name": "race", "type": "STRING"}, + {"name": "martial", "type": "STRING"}, + {"name": "prefix", "type": "STRING"}, + {"name": "birthplace", "type": "STRING"}, + {"name": "deathdate", "type": "DATE"}, + {"name": "drivers", "type": "STRING"}, + {"name": "healthcare_coverage", "type": "FLOAT"}, + {"name": "healthcare_expenses", "type": "FLOAT"}, + ], + }, + { + "label": "Encounter", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "code", "type": "STRING"}, + {"name": "description", "type": "STRING"}, + {"name": "class", "type": "STRING"}, + {"name": "start", "type": "STRING"}, + {"name": "end", "type": "STRING"}, + {"name": "isStart", "type": "BOOLEAN"}, + {"name": "isEnd", "type": "BOOLEAN"}, + {"name": "baseCost", "type": "FLOAT"}, + {"name": "claimCost", "type": "FLOAT"}, + {"name": "coveredAmount", "type": "FLOAT"}, + ], + }, + { + "label": "Provider", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "address", "type": "STRING"}, + {"name": "location", "type": "POINT"}, + ], + }, + { + "label": "Organization", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "address", "type": "STRING"}, + {"name": "city", "type": "STRING"}, + {"name": "location", "type": "POINT"}, + ], + }, + { + "label": "Condition", + "key_property": {"name": "code", "type": "STRING"}, + "properties": [ + {"name": "description", "type": "STRING"}, + {"name": "start", "type": "STRING"}, + {"name": "stop", "type": "STRING"}, + {"name": "isEnd", "type": "BOOLEAN"}, + {"name": "total_drug_pairings", "type": "INTEGER"}, + ], + }, + { + "label": "Drug", + "key_property": {"name": "code", "type": "STRING"}, + "properties": [ + {"name": "description", "type": "STRING"}, + {"name": "start", "type": "STRING"}, + {"name": "stop", "type": "STRING"}, + {"name": "isEnd", "type": "BOOLEAN"}, + {"name": "basecost", "type": "STRING"}, + {"name": "totalcost", "type": "STRING"}, + ], + }, + { + "label": "Procedure", + "key_property": {"name": "code", "type": "STRING"}, + "properties": [{"name": "description", "type": "STRING"}], + }, + { + "label": "Observation", + "key_property": {"name": "code", "type": "STRING"}, + "properties": [ + {"name": "description", "type": "STRING"}, + {"name": "category", "type": "STRING"}, + {"name": "type", "type": "STRING"}, + ], + }, + { + "label": "Device", + "key_property": {"name": "code", "type": "STRING"}, + "properties": [{"name": "description", "type": "STRING"}], + }, + { + "label": "CarePlan", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "code", "type": "STRING"}, + {"name": "description", "type": "STRING"}, + {"name": "start", "type": "STRING"}, + {"name": "end", "type": "STRING"}, + {"name": "isEnd", "type": "BOOLEAN"}, + {"name": "reasoncode", "type": "STRING"}, + ], + }, + { + "label": "Allergy", + "key_property": {"name": "code", "type": "STRING"}, + "properties": [ + {"name": "description", "type": "STRING"}, + {"name": "category", "type": "STRING"}, + {"name": "system", "type": "STRING"}, + {"name": "type", "type": "STRING"}, + ], + }, + { + "label": "Reaction", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "description", "type": "STRING"}], + }, + { + "label": "Payer", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "name", "type": "STRING"}], + }, + { + "label": "Speciality", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + ], + "relationships": [ + { + "type": "HAS_ENCOUNTER", + "start_node_label": "Patient", + "end_node_label": "Encounter", + }, + {"type": "FIRST", "start_node_label": "Patient", "end_node_label": "Encounter"}, + {"type": "LAST", "start_node_label": "Patient", "end_node_label": "Encounter"}, + { + "type": "NEXT", + "start_node_label": "Encounter", + "end_node_label": "Encounter", + "properties": [{"name": "amount", "type": "INTEGER"}], + }, + { + "type": "HAS_PROVIDER", + "start_node_label": "Encounter", + "end_node_label": "Provider", + }, + { + "type": "AT_ORGANIZATION", + "start_node_label": "Encounter", + "end_node_label": "Organization", + }, + { + "type": "BELONGS_TO", + "start_node_label": "Provider", + "end_node_label": "Organization", + }, + { + "type": "HAS_SPECIALITY", + "start_node_label": "Provider", + "end_node_label": "Speciality", + }, + { + "type": "HAS_CONDITION", + "start_node_label": "Encounter", + "end_node_label": "Condition", + }, + {"type": "HAS_DRUG", "start_node_label": "Encounter", "end_node_label": "Drug"}, + { + "type": "HAS_PROCEDURE", + "start_node_label": "Encounter", + "end_node_label": "Procedure", + }, + { + "type": "HAS_OBSERVATION", + "start_node_label": "Encounter", + "end_node_label": "Observation", + "properties": [ + {"name": "date", "type": "STRING"}, + {"name": "value", "type": "STRING"}, + {"name": "unit", "type": "STRING"}, + ], + }, + { + "type": "DEVICE_USED", + "start_node_label": "Encounter", + "end_node_label": "Device", + }, + { + "type": "HAS_CARE_PLAN", + "start_node_label": "Encounter", + "end_node_label": "CarePlan", + }, + { + "type": "HAS_ALLERGY", + "start_node_label": "Patient", + "end_node_label": "Allergy", + }, + { + "type": "ALLERGY_DETECTED", + "start_node_label": "Encounter", + "end_node_label": "Allergy", + "properties": [{"name": "start", "type": "STRING"}], + }, + { + "type": "CAUSES_REACTION", + "start_node_label": "Allergy", + "end_node_label": "Reaction", + }, + { + "type": "HAS_REACTION", + "start_node_label": "Patient", + "end_node_label": "Reaction", + "properties": [{"name": "severity", "type": "STRING"}], + }, + { + "type": "HAS_PAYER", + "start_node_label": "Encounter", + "end_node_label": "Payer", + }, + { + "type": "INSURANCE_START", + "start_node_label": "Patient", + "end_node_label": "Payer", + }, + { + "type": "INSURANCE_END", + "start_node_label": "Patient", + "end_node_label": "Payer", + }, + ], +} + +# Real-World Example: Supply Chain Data Model +SUPPLY_CHAIN_MODEL = { + "nodes": [ + { + "label": "Product", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Order", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Inventory", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Location", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "LegalEntity", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Asset", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "BOM", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Address", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "GeoRef", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Coordinates", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Adm1Location", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Country", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "GGGRecord", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Date", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + ], + "relationships": [ + { + "type": "HAS_PART", + "start_node_label": "Product", + "end_node_label": "Product", + "properties": [{"name": "quantity", "type": "INTEGER"}], + }, + { + "type": "MANUFACTURER", + "start_node_label": "Product", + "end_node_label": "LegalEntity", + }, + {"type": "SUB_PARTS", "start_node_label": "Product", "end_node_label": "BOM"}, + {"type": "HAS_PARTS", "start_node_label": "BOM", "end_node_label": "Product"}, + {"type": "PRODUCT", "start_node_label": "Order", "end_node_label": "Product"}, + { + "type": "PRODUCT", + "start_node_label": "Inventory", + "end_node_label": "Product", + }, + { + "type": "LEGAL_ENTITY", + "start_node_label": "Order", + "end_node_label": "LegalEntity", + }, + { + "type": "SUPPLIER", + "start_node_label": "Inventory", + "end_node_label": "LegalEntity", + }, + {"type": "LOCATION", "start_node_label": "Order", "end_node_label": "Location"}, + { + "type": "LOCATION", + "start_node_label": "Inventory", + "end_node_label": "Location", + }, + {"type": "LOCATION", "start_node_label": "Asset", "end_node_label": "Location"}, + { + "type": "RECEIVING_LOCATION", + "start_node_label": "Order", + "end_node_label": "Location", + }, + { + "type": "SHIPPING_LOCATION", + "start_node_label": "Order", + "end_node_label": "Location", + }, + {"type": "ASSET", "start_node_label": "Order", "end_node_label": "Asset"}, + { + "type": "ADDRESS", + "start_node_label": "Location", + "end_node_label": "Address", + }, + { + "type": "ADDRESS", + "start_node_label": "LegalEntity", + "end_node_label": "Address", + }, + {"type": "GEOREF", "start_node_label": "GGGRecord", "end_node_label": "GeoRef"}, + { + "type": "COORDINATES", + "start_node_label": "GeoRef", + "end_node_label": "Coordinates", + }, + { + "type": "LOCATION", + "start_node_label": "GeoRef", + "end_node_label": "Adm1Location", + }, + {"type": "COUNTRY", "start_node_label": "Address", "end_node_label": "Country"}, + { + "type": "COUNTRY", + "start_node_label": "Adm1Location", + "end_node_label": "Country", + }, + { + "type": "WITHIN_50K", + "start_node_label": "Address", + "end_node_label": "Coordinates", + }, + {"type": "DATE", "start_node_label": "GGGRecord", "end_node_label": "Date"}, + ], +} + +# Real-World Example: Software Dependency Graph +SOFTWARE_DEPENDENCY_MODEL = { + "nodes": [ + { + "label": "Project", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Dependency", + "key_property": {"name": "artifactId", "type": "STRING"}, + "properties": [ + {"name": "groupId", "type": "STRING"}, + {"name": "version", "type": "STRING"}, + ], + }, + { + "label": "CVE", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "description", "type": "STRING"}, + {"name": "severity", "type": "STRING"}, + ], + }, + { + "label": "Commit", + "key_property": {"name": "sha", "type": "STRING"}, + "properties": [{"name": "repository", "type": "STRING"}], + }, + { + "label": "Contributor", + "key_property": {"name": "id", "type": "INTEGER"}, + "properties": [ + {"name": "login", "type": "STRING"}, + {"name": "contributions", "type": "INTEGER"}, + ], + }, + { + "label": "BadActor", + "key_property": {"name": "id", "type": "INTEGER"}, + "properties": [ + {"name": "login", "type": "STRING"}, + {"name": "contributions", "type": "INTEGER"}, + ], + }, + { + "label": "Organization", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "SuspectOrg", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Pom", + "key_property": {"name": "filePath", "type": "STRING"}, + "properties": [{"name": "parentDirectory", "type": "STRING"}], + }, + { + "label": "Issue", + "key_property": {"name": "id", "type": "INTEGER"}, + "properties": [ + {"name": "title", "type": "STRING"}, + {"name": "body", "type": "STRING"}, + {"name": "state", "type": "STRING"}, + {"name": "severity", "type": "STRING"}, + {"name": "comments", "type": "INTEGER"}, + {"name": "createdAt", "type": "STRING"}, + {"name": "updatedAt", "type": "STRING"}, + {"name": "url", "type": "STRING"}, + {"name": "htmlUrl", "type": "STRING"}, + ], + }, + { + "label": "User", + "key_property": {"name": "id", "type": "INTEGER"}, + "properties": [{"name": "login", "type": "STRING"}], + }, + { + "label": "Product", + "key_property": {"name": "serialNumber", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "softwareVersion", "type": "STRING"}, + {"name": "macAddress", "type": "STRING"}, + ], + }, + { + "label": "Customer", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "SuspectCommit", + "key_property": {"name": "sha", "type": "STRING"}, + "properties": [{"name": "repository", "type": "STRING"}], + }, + ], + "relationships": [ + { + "type": "DEPENDS_ON", + "start_node_label": "Pom", + "end_node_label": "Dependency", + }, + { + "type": "BELONGS_TO_PROJECT", + "start_node_label": "Pom", + "end_node_label": "Project", + }, + { + "type": "AFFECTED_BY", + "start_node_label": "Dependency", + "end_node_label": "CVE", + }, + { + "type": "BELONGS_TO", + "start_node_label": "Project", + "end_node_label": "Organization", + }, + { + "type": "BELONGS_TO", + "start_node_label": "Project", + "end_node_label": "SuspectOrg", + }, + { + "type": "HAS_HEAD_COMMIT", + "start_node_label": "Project", + "end_node_label": "Commit", + }, + {"type": "HAS_ISSUE", "start_node_label": "Project", "end_node_label": "Issue"}, + { + "type": "INSTALLED_ON", + "start_node_label": "Project", + "end_node_label": "Product", + }, + { + "type": "CONTRIBUTED_TO", + "start_node_label": "Contributor", + "end_node_label": "Project", + }, + { + "type": "CONTRIBUTED_TO", + "start_node_label": "BadActor", + "end_node_label": "Project", + }, + { + "type": "HAS_COMMIT", + "start_node_label": "Contributor", + "end_node_label": "Commit", + }, + { + "type": "HAS_COMMIT", + "start_node_label": "BadActor", + "end_node_label": "Commit", + }, + {"type": "CREATED_BY", "start_node_label": "Issue", "end_node_label": "User"}, + { + "type": "PURCHASED", + "start_node_label": "Customer", + "end_node_label": "Product", + "properties": [{"name": "purchaseDate", "type": "STRING"}], + }, + { + "type": "NEXT_COMMIT", + "start_node_label": "Commit", + "end_node_label": "Commit", + }, + { + "type": "NEXT_COMMIT", + "start_node_label": "Commit", + "end_node_label": "SuspectCommit", + }, + { + "type": "NEXT_COMMIT", + "start_node_label": "SuspectCommit", + "end_node_label": "Commit", + }, + ], +} + +# Real-World Example: Oil and Gas Equipment Monitoring +OIL_GAS_MONITORING_MODEL = { + "nodes": [ + { + "label": "Producer", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "location", "type": "POINT"}, + ], + }, + { + "label": "CollectionPoint", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "location", "type": "POINT"}, + ], + }, + { + "label": "Equipment", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "type", "type": "STRING"}], + }, + { + "label": "Vessel", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "type", "type": "STRING"}], + }, + { + "label": "Sensor", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "type", "type": "STRING"}], + }, + { + "label": "Alert", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "type", "type": "STRING"}, + {"name": "status", "type": "STRING"}, + ], + }, + { + "label": "MaintenanceRecord", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "date", "type": "STRING"}, + {"name": "description", "type": "STRING"}, + {"name": "type", "type": "STRING"}, + {"name": "downTime", "type": "FLOAT"}, + ], + }, + { + "label": "ServiceProvider", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "companyName", "type": "STRING"}, + {"name": "type", "type": "STRING"}, + ], + }, + { + "label": "Lease", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "coordinates", "type": "STRING"}, + ], + }, + { + "label": "Basin", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Model", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "type", "type": "STRING"}], + }, + { + "label": "TransmissionRoute", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "start", "type": "POINT"}, + {"name": "end", "type": "POINT"}, + ], + }, + ], + "relationships": [ + { + "type": "COLLECTED_BY", + "start_node_label": "Producer", + "end_node_label": "CollectionPoint", + }, + { + "type": "MONITORED_BY", + "start_node_label": "Producer", + "end_node_label": "Sensor", + }, + { + "type": "MONITORED_BY", + "start_node_label": "CollectionPoint", + "end_node_label": "Sensor", + }, + { + "type": "MONITORED_BY", + "start_node_label": "Equipment", + "end_node_label": "Sensor", + }, + { + "type": "MONITORED_BY", + "start_node_label": "Vessel", + "end_node_label": "Sensor", + }, + {"type": "RAISED", "start_node_label": "Sensor", "end_node_label": "Alert"}, + { + "type": "HAS_ALERT", + "start_node_label": "CollectionPoint", + "end_node_label": "Alert", + }, + { + "type": "HAS_ALERT", + "start_node_label": "Equipment", + "end_node_label": "Alert", + }, + {"type": "HAS_ALERT", "start_node_label": "Vessel", "end_node_label": "Alert"}, + { + "type": "HAS_MAINTENANCE_RECORD", + "start_node_label": "Producer", + "end_node_label": "MaintenanceRecord", + }, + { + "type": "HAS_MAINTENANCE_RECORD", + "start_node_label": "Equipment", + "end_node_label": "MaintenanceRecord", + }, + { + "type": "SERVICED_BY", + "start_node_label": "Producer", + "end_node_label": "ServiceProvider", + }, + { + "type": "SERVICED_BY", + "start_node_label": "CollectionPoint", + "end_node_label": "ServiceProvider", + }, + { + "type": "SERVICED_BY", + "start_node_label": "Equipment", + "end_node_label": "ServiceProvider", + }, + { + "type": "SERVICED_BY", + "start_node_label": "Lease", + "end_node_label": "ServiceProvider", + }, + { + "type": "LOCATED_ON", + "start_node_label": "Producer", + "end_node_label": "Lease", + }, + { + "type": "LOCATED_ON", + "start_node_label": "CollectionPoint", + "end_node_label": "Lease", + }, + { + "type": "LOCATED_IN", + "start_node_label": "Producer", + "end_node_label": "Basin", + }, + { + "type": "LOCATED_AT", + "start_node_label": "Equipment", + "end_node_label": "CollectionPoint", + }, + { + "type": "LOCATED_AT", + "start_node_label": "Vessel", + "end_node_label": "CollectionPoint", + }, + { + "type": "CONNECTED_TO", + "start_node_label": "Vessel", + "end_node_label": "Equipment", + }, + { + "type": "CONNECTED_TO", + "start_node_label": "Vessel", + "end_node_label": "Vessel", + }, + { + "type": "HAS_MODEL", + "start_node_label": "Equipment", + "end_node_label": "Model", + }, + {"type": "MODEL_OF", "start_node_label": "Vessel", "end_node_label": "Model"}, + { + "type": "MODEL_OF", + "start_node_label": "Equipment", + "end_node_label": "Model", + }, + { + "type": "TRANSMITTED_BY", + "start_node_label": "CollectionPoint", + "end_node_label": "TransmissionRoute", + }, + ], +} + +# Real-World Example: Customer 360 +CUSTOMER_360_MODEL = { + "nodes": [ + { + "label": "Account", + "key_property": {"name": "account_id", "type": "STRING"}, + "properties": [{"name": "account_name", "type": "STRING"}], + }, + { + "label": "Contact", + "key_property": {"name": "contact_id", "type": "STRING"}, + "properties": [ + {"name": "title", "type": "STRING"}, + {"name": "job_function", "type": "STRING"}, + {"name": "job_role", "type": "STRING"}, + ], + }, + { + "label": "Order", + "key_property": {"name": "order_number", "type": "STRING"}, + "properties": [ + {"name": "order_create_date", "type": "DATE"}, + {"name": "order_complete_date", "type": "DATE"}, + {"name": "source", "type": "STRING"}, + ], + }, + { + "label": "Product", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Ticket", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Survey", + "key_property": {"name": "record_id", "type": "STRING"}, + "properties": [ + {"name": "status", "type": "STRING"}, + {"name": "start_date", "type": "DATE"}, + {"name": "end_date", "type": "DATE"}, + {"name": "start_timestamp", "type": "INTEGER"}, + {"name": "end_timestamp", "type": "INTEGER"}, + {"name": "duration", "type": "INTEGER"}, + {"name": "recorded_date", "type": "DATE"}, + {"name": "survey_response", "type": "STRING"}, + {"name": "sentiment", "type": "STRING"}, + {"name": "nps_group", "type": "STRING"}, + {"name": "qid1", "type": "STRING"}, + {"name": "qid2_1", "type": "STRING"}, + {"name": "qid2_2", "type": "STRING"}, + {"name": "qid2_3", "type": "STRING"}, + {"name": "qid2_4", "type": "STRING"}, + {"name": "qid4", "type": "STRING"}, + ], + }, + { + "label": "Address", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "city", "type": "STRING"}, + {"name": "country", "type": "STRING"}, + {"name": "subRegion", "type": "STRING"}, + ], + }, + { + "label": "Industry", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Segment", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "ParentAccount", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "SalesProgramType", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Persona", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Service", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "SurveyType", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Question", + "key_property": {"name": "text", "type": "STRING"}, + "properties": [], + }, + { + "label": "Theme", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Aspect", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Milestone", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "DataCenter", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Metro", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Country", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Region", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "OrderType", + "key_property": {"name": "type", "type": "STRING"}, + "properties": [], + }, + { + "label": "SubOrderType", + "key_property": {"name": "type", "type": "STRING"}, + "properties": [], + }, + { + "label": "TicketType", + "key_property": {"name": "type", "type": "STRING"}, + "properties": [], + }, + { + "label": "TicketCode", + "key_property": {"name": "code_name", "type": "STRING"}, + "properties": [], + }, + { + "label": "EscalationCategory", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "EscalationSubCategory", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "IncidentCategory", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "RootCause", + "key_property": {"name": "type", "type": "STRING"}, + "properties": [], + }, + ], + "relationships": [ + { + "type": "ACCOUNT_CONTACT", + "start_node_label": "Account", + "end_node_label": "Contact", + }, + { + "type": "HAS_ADDRESS", + "start_node_label": "Account", + "end_node_label": "Address", + }, + { + "type": "HAS_INDUSTRY", + "start_node_label": "Account", + "end_node_label": "Industry", + }, + { + "type": "IN_SEGMENT", + "start_node_label": "Account", + "end_node_label": "Segment", + }, + { + "type": "PARENT_ACCOUNT", + "start_node_label": "Account", + "end_node_label": "ParentAccount", + }, + { + "type": "SALES_PROGRAM_TYPE", + "start_node_label": "Account", + "end_node_label": "SalesProgramType", + }, + { + "type": "PLACES_ORDER", + "start_node_label": "Account", + "end_node_label": "Order", + }, + { + "type": "CREATED_TICKET", + "start_node_label": "Account", + "end_node_label": "Ticket", + }, + { + "type": "COMPLETED_SURVEY", + "start_node_label": "Account", + "end_node_label": "Survey", + }, + { + "type": "HAS_PERSONA", + "start_node_label": "Contact", + "end_node_label": "Persona", + }, + { + "type": "CONTACT_CREATED_TICKET", + "start_node_label": "Contact", + "end_node_label": "Ticket", + }, + { + "type": "FOR_PRODUCT", + "start_node_label": "Order", + "end_node_label": "Product", + }, + { + "type": "SUB_ORDER_TYPE", + "start_node_label": "Order", + "end_node_label": "SubOrderType", + }, + { + "type": "ORDER_TYPE", + "start_node_label": "SubOrderType", + "end_node_label": "OrderType", + }, + { + "type": "TICKET_FOR_PRODUCT", + "start_node_label": "Ticket", + "end_node_label": "Product", + }, + { + "type": "AFFECTED_SERVICE", + "start_node_label": "Ticket", + "end_node_label": "Service", + }, + { + "type": "HAS_TICKET_TYPE", + "start_node_label": "Ticket", + "end_node_label": "TicketType", + }, + { + "type": "HAS_PROBLEM_CODE", + "start_node_label": "Ticket", + "end_node_label": "TicketCode", + "properties": [{"name": "code_name", "type": "STRING"}], + }, + { + "type": "ESCALATION_CATEGORY", + "start_node_label": "Ticket", + "end_node_label": "EscalationCategory", + }, + { + "type": "ESCALATION_SUB_CATEGORY", + "start_node_label": "Ticket", + "end_node_label": "EscalationSubCategory", + }, + { + "type": "INCIDENT_CATEGORY", + "start_node_label": "Ticket", + "end_node_label": "IncidentCategory", + }, + { + "type": "HAS_ROOT_CAUSE", + "start_node_label": "Ticket", + "end_node_label": "RootCause", + }, + { + "type": "FILLED_BY", + "start_node_label": "Survey", + "end_node_label": "Contact", + }, + { + "type": "HAS_SURVEY_TYPE", + "start_node_label": "Survey", + "end_node_label": "SurveyType", + }, + {"type": "HAS_THEME", "start_node_label": "Survey", "end_node_label": "Theme"}, + { + "type": "HAS_ASPECT", + "start_node_label": "Survey", + "end_node_label": "Aspect", + "properties": [ + {"name": "attributed_sentiment", "type": "STRING"}, + {"name": "pos_sentiment_score", "type": "INTEGER"}, + {"name": "neu_sentiment_score", "type": "INTEGER"}, + {"name": "neg_sentiment_score", "type": "INTEGER"}, + ], + }, + { + "type": "RELATED_ORDER", + "start_node_label": "Survey", + "end_node_label": "Order", + }, + { + "type": "RELATED_TO_DC", + "start_node_label": "Survey", + "end_node_label": "DataCenter", + "properties": [ + {"name": "dcCage", "type": "STRING"}, + {"name": "dc_alias", "type": "STRING"}, + ], + }, + { + "type": "RESPONDED_TO", + "start_node_label": "Survey", + "end_node_label": "Question", + "properties": [ + {"name": "nps_group", "type": "STRING"}, + {"name": "nps_score", "type": "INTEGER"}, + {"name": "rating", "type": "STRING"}, + {"name": "rating_score", "type": "STRING"}, + ], + }, + { + "type": "HAS_MILESTONE", + "start_node_label": "SurveyType", + "end_node_label": "Milestone", + }, + { + "type": "HAS_QUESTION", + "start_node_label": "SurveyType", + "end_node_label": "Question", + }, + { + "type": "THEME_HAS_ASPECT", + "start_node_label": "Theme", + "end_node_label": "Aspect", + }, + { + "type": "IN_METRO", + "start_node_label": "DataCenter", + "end_node_label": "Metro", + }, + { + "type": "IN_COUNTRY", + "start_node_label": "Metro", + "end_node_label": "Country", + }, + { + "type": "IN_REGION", + "start_node_label": "Country", + "end_node_label": "Region", + }, + ], +} + +# Real-World Example: Fraud & AML Data Model +FRAUD_AML_MODEL = { + "nodes": [ + { + "label": "Customer", + "key_property": {"name": "customer_id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "date_of_birth", "type": "DATE"}, + {"name": "nationality", "type": "STRING"}, + {"name": "risk_level", "type": "STRING"}, + ], + }, + { + "label": "Account", + "key_property": {"name": "account_number", "type": "STRING"}, + "properties": [ + {"name": "account_type", "type": "STRING"}, + {"name": "balance", "type": "FLOAT"}, + {"name": "opening_date", "type": "DATE"}, + {"name": "status", "type": "STRING"}, + ], + }, + { + "label": "Transaction", + "key_property": {"name": "transaction_id", "type": "STRING"}, + "properties": [ + {"name": "amount", "type": "FLOAT"}, + {"name": "currency", "type": "STRING"}, + {"name": "transaction_date", "type": "DATETIME"}, + {"name": "transaction_type", "type": "STRING"}, + {"name": "description", "type": "STRING"}, + ], + }, + { + "label": "Counterparty", + "key_property": {"name": "counterparty_id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "type", "type": "STRING"}, + {"name": "country", "type": "STRING"}, + {"name": "risk_score", "type": "INTEGER"}, + ], + }, + { + "label": "Alert", + "key_property": {"name": "alert_id", "type": "STRING"}, + "properties": [ + {"name": "alert_type", "type": "STRING"}, + {"name": "severity", "type": "STRING"}, + {"name": "created_date", "type": "DATETIME"}, + {"name": "status", "type": "STRING"}, + {"name": "description", "type": "STRING"}, + ], + }, + { + "label": "Case", + "key_property": {"name": "case_id", "type": "STRING"}, + "properties": [ + {"name": "case_type", "type": "STRING"}, + {"name": "priority", "type": "STRING"}, + {"name": "created_date", "type": "DATETIME"}, + {"name": "status", "type": "STRING"}, + {"name": "assigned_to", "type": "STRING"}, + ], + }, + { + "label": "Document", + "key_property": {"name": "document_id", "type": "STRING"}, + "properties": [ + {"name": "document_type", "type": "STRING"}, + {"name": "upload_date", "type": "DATETIME"}, + {"name": "status", "type": "STRING"}, + {"name": "file_path", "type": "STRING"}, + ], + }, + { + "label": "RiskAssessment", + "key_property": {"name": "assessment_id", "type": "STRING"}, + "properties": [ + {"name": "assessment_date", "type": "DATETIME"}, + {"name": "risk_score", "type": "INTEGER"}, + {"name": "risk_factors", "type": "STRING"}, + {"name": "recommendations", "type": "STRING"}, + ], + }, + { + "label": "ComplianceRule", + "key_property": {"name": "rule_id", "type": "STRING"}, + "properties": [ + {"name": "rule_name", "type": "STRING"}, + {"name": "rule_type", "type": "STRING"}, + {"name": "threshold", "type": "FLOAT"}, + {"name": "description", "type": "STRING"}, + ], + }, + { + "label": "SanctionList", + "key_property": {"name": "list_id", "type": "STRING"}, + "properties": [ + {"name": "list_name", "type": "STRING"}, + {"name": "source", "type": "STRING"}, + {"name": "last_updated", "type": "DATETIME"}, + ], + }, + { + "label": "SanctionedEntity", + "key_property": {"name": "entity_id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "entity_type", "type": "STRING"}, + {"name": "country", "type": "STRING"}, + {"name": "sanction_date", "type": "DATE"}, + ], + }, + { + "label": "Device", + "key_property": {"name": "device_id", "type": "STRING"}, + "properties": [ + {"name": "device_type", "type": "STRING"}, + {"name": "ip_address", "type": "STRING"}, + {"name": "location", "type": "STRING"}, + {"name": "last_used", "type": "DATETIME"}, + ], + }, + { + "label": "Location", + "key_property": {"name": "location_id", "type": "STRING"}, + "properties": [ + {"name": "country", "type": "STRING"}, + {"name": "city", "type": "STRING"}, + {"name": "risk_level", "type": "STRING"}, + ], + }, + { + "label": "Product", + "key_property": {"name": "product_id", "type": "STRING"}, + "properties": [ + {"name": "product_name", "type": "STRING"}, + {"name": "product_type", "type": "STRING"}, + {"name": "risk_category", "type": "STRING"}, + ], + }, + { + "label": "Employee", + "key_property": {"name": "employee_id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING"}, + {"name": "role", "type": "STRING"}, + {"name": "department", "type": "STRING"}, + ], + }, + ], + "relationships": [ + { + "type": "OWNS_ACCOUNT", + "start_node_label": "Customer", + "end_node_label": "Account", + }, + { + "type": "HAS_TRANSACTION", + "start_node_label": "Account", + "end_node_label": "Transaction", + }, + { + "type": "INVOLVES_COUNTERPARTY", + "start_node_label": "Transaction", + "end_node_label": "Counterparty", + }, + { + "type": "TRIGGERS_ALERT", + "start_node_label": "Transaction", + "end_node_label": "Alert", + }, + { + "type": "ASSOCIATED_WITH_CASE", + "start_node_label": "Alert", + "end_node_label": "Case", + }, + { + "type": "HAS_DOCUMENT", + "start_node_label": "Case", + "end_node_label": "Document", + }, + { + "type": "HAS_RISK_ASSESSMENT", + "start_node_label": "Customer", + "end_node_label": "RiskAssessment", + }, + { + "type": "VIOLATES_RULE", + "start_node_label": "Transaction", + "end_node_label": "ComplianceRule", + }, + { + "type": "ON_SANCTION_LIST", + "start_node_label": "SanctionedEntity", + "end_node_label": "SanctionList", + }, + { + "type": "MATCHES_SANCTIONED_ENTITY", + "start_node_label": "Customer", + "end_node_label": "SanctionedEntity", + }, + { + "type": "MATCHES_SANCTIONED_ENTITY", + "start_node_label": "Counterparty", + "end_node_label": "SanctionedEntity", + }, + { + "type": "USES_DEVICE", + "start_node_label": "Customer", + "end_node_label": "Device", + }, + { + "type": "LOCATED_IN", + "start_node_label": "Customer", + "end_node_label": "Location", + }, + { + "type": "LOCATED_IN", + "start_node_label": "Counterparty", + "end_node_label": "Location", + }, + { + "type": "INVOLVES_PRODUCT", + "start_node_label": "Transaction", + "end_node_label": "Product", + }, + { + "type": "ASSIGNED_TO", + "start_node_label": "Case", + "end_node_label": "Employee", + }, + { + "type": "REVIEWED_BY", + "start_node_label": "Alert", + "end_node_label": "Employee", + }, + { + "type": "RELATED_TRANSACTION", + "start_node_label": "Transaction", + "end_node_label": "Transaction", + }, + { + "type": "SAME_DEVICE", + "start_node_label": "Device", + "end_node_label": "Device", + }, + { + "type": "HIGH_RISK_LOCATION", + "start_node_label": "Location", + "end_node_label": "Location", + }, + { + "type": "SUSPICIOUS_PATTERN", + "start_node_label": "Transaction", + "end_node_label": "Transaction", + }, + { + "type": "FREQUENT_COUNTERPARTY", + "start_node_label": "Customer", + "end_node_label": "Counterparty", + }, + { + "type": "UNUSUAL_AMOUNT", + "start_node_label": "Transaction", + "end_node_label": "Transaction", + }, + { + "type": "RAPID_MOVEMENT", + "start_node_label": "Transaction", + "end_node_label": "Transaction", + }, + ], +} + +# Real-World Example: Health Insurance Fraud Detection +HEALTH_INSURANCE_FRAUD_MODEL = { + "nodes": [ + {"label": "Person", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Address", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Phone", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "IBAN", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Photo", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Investigation", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Beneficiary", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Prescription", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Execution", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Care", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "IP", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Employee", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "Analyst", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, + {"label": "HealthCarePro", "key_property": {"name": "id", "type": "STRING"}, "properties": []} + ], + "relationships": [ + {"type": "HAS", "start_node_label": "Person", "end_node_label": "Address"}, + {"type": "HAS", "start_node_label": "Person", "end_node_label": "Phone"}, + {"type": "HAS", "start_node_label": "Person", "end_node_label": "IBAN"}, + {"type": "HAS", "start_node_label": "Person", "end_node_label": "Photo"}, + {"type": "HAS", "start_node_label": "Person", "end_node_label": "IP"}, + {"type": "HAS_MANAGER", "start_node_label": "Person", "end_node_label": "Person"}, + {"type": "PRESCRIPTION_FOR", "start_node_label": "Person", "end_node_label": "Prescription"}, + {"type": "RESPONSIBLE_FOR", "start_node_label": "Person", "end_node_label": "Prescription"}, + {"type": "BENEFICIARY_FOR", "start_node_label": "Person", "end_node_label": "Execution"}, + {"type": "RESPONSIBLE_FOR", "start_node_label": "Person", "end_node_label": "Execution"}, + {"type": "CONTAINS", "start_node_label": "Prescription", "end_node_label": "Care"}, + {"type": "CONTAINS", "start_node_label": "Execution", "end_node_label": "Care"}, + {"type": "ABOUT", "start_node_label": "Investigation", "end_node_label": "Person"}, + {"type": "HAS_AUTHOR", "start_node_label": "Investigation", "end_node_label": "Person"}, + {"type": "INVOLVES", "start_node_label": "Investigation", "end_node_label": "Person"}, + {"type": "NEXT_STATUS", "start_node_label": "Investigation", "end_node_label": "Investigation"} + ] +} diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py index 31d7e712..384457e2 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py @@ -644,3 +644,543 @@ def test_get_cypher_constraints_query(valid_data_model: DataModel): queries[1] == "CREATE CONSTRAINT Place_constraint IF NOT EXISTS FOR (n:Place) REQUIRE (n.id) IS NODE KEY;" ) + + +def test_from_dict_basic(): + """Test basic from_dict conversion with minimal data.""" + data = { + "nodes": [ + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "name", "type": "STRING"}], + } + ], + "relationships": [ + { + "type": "KNOWS", + "start_node_label": "Person", + "end_node_label": "Person", + } + ], + } + + data_model = DataModel.from_dict(data) + + assert len(data_model.nodes) == 1 + assert len(data_model.relationships) == 1 + assert data_model.nodes[0].label == "Person" + assert data_model.nodes[0].key_property.name == "id" + assert data_model.nodes[0].key_property.type == "STRING" + assert len(data_model.nodes[0].properties) == 1 + assert data_model.nodes[0].properties[0].name == "name" + assert data_model.relationships[0].type == "KNOWS" + + +def test_from_dict_with_relationship_properties(): + """Test from_dict conversion with relationship properties.""" + data = { + "nodes": [ + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + } + ], + "relationships": [ + { + "type": "KNOWS", + "start_node_label": "Person", + "end_node_label": "Person", + "key_property": {"name": "since", "type": "DATE"}, + "properties": [{"name": "strength", "type": "FLOAT"}], + } + ], + } + + data_model = DataModel.from_dict(data) + + assert len(data_model.relationships) == 1 + rel = data_model.relationships[0] + assert rel.key_property.name == "since" + assert rel.key_property.type == "DATE" + assert len(rel.properties) == 1 + assert rel.properties[0].name == "strength" + assert rel.properties[0].type == "FLOAT" + + +def test_from_dict_without_relationship_properties(): + """Test from_dict conversion without relationship properties.""" + data = { + "nodes": [ + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + } + ], + "relationships": [ + { + "type": "KNOWS", + "start_node_label": "Person", + "end_node_label": "Person", + } + ], + } + + data_model = DataModel.from_dict(data) + + assert len(data_model.relationships) == 1 + rel = data_model.relationships[0] + assert rel.key_property is None + assert len(rel.properties) == 0 + + +def test_from_dict_multiple_nodes_and_relationships(): + """Test from_dict conversion with multiple nodes and relationships.""" + data = { + "nodes": [ + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "name", "type": "STRING"}], + }, + { + "label": "Company", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [{"name": "name", "type": "STRING"}], + }, + ], + "relationships": [ + { + "type": "WORKS_FOR", + "start_node_label": "Person", + "end_node_label": "Company", + "properties": [{"name": "start_date", "type": "DATE"}], + }, + { + "type": "MANAGES", + "start_node_label": "Person", + "end_node_label": "Person", + }, + ], + } + + data_model = DataModel.from_dict(data) + + assert len(data_model.nodes) == 2 + assert len(data_model.relationships) == 2 + + # Check nodes + person_node = next(n for n in data_model.nodes if n.label == "Person") + company_node = next(n for n in data_model.nodes if n.label == "Company") + assert person_node is not None + assert company_node is not None + + # Check relationships + works_for_rel = next(r for r in data_model.relationships if r.type == "WORKS_FOR") + manages_rel = next(r for r in data_model.relationships if r.type == "MANAGES") + assert works_for_rel is not None + assert manages_rel is not None + assert len(works_for_rel.properties) == 1 + assert works_for_rel.properties[0].name == "start_date" + + +def test_from_dict_empty_properties(): + """Test from_dict conversion with empty properties arrays.""" + data = { + "nodes": [ + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + } + ], + "relationships": [ + { + "type": "KNOWS", + "start_node_label": "Person", + "end_node_label": "Person", + "properties": [], + } + ], + } + + data_model = DataModel.from_dict(data) + + assert len(data_model.nodes[0].properties) == 0 + assert len(data_model.relationships[0].properties) == 0 + + +def test_from_dict_missing_properties_key(): + """Test from_dict conversion when properties key is missing.""" + data = { + "nodes": [ + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + } + ], + "relationships": [ + { + "type": "KNOWS", + "start_node_label": "Person", + "end_node_label": "Person", + } + ], + } + + data_model = DataModel.from_dict(data) + + assert len(data_model.nodes[0].properties) == 0 + assert len(data_model.relationships[0].properties) == 0 + + +def test_from_dict_all_example_models(): + """Test from_dict conversion with all example models from static.py.""" + from mcp_neo4j_data_modeling.static import ( + PATIENT_JOURNEY_MODEL, + SUPPLY_CHAIN_MODEL, + SOFTWARE_DEPENDENCY_MODEL, + OIL_GAS_MONITORING_MODEL, + CUSTOMER_360_MODEL, + FRAUD_AML_MODEL, + HEALTH_INSURANCE_FRAUD_MODEL, + ) + + models = [ + PATIENT_JOURNEY_MODEL, + SUPPLY_CHAIN_MODEL, + SOFTWARE_DEPENDENCY_MODEL, + OIL_GAS_MONITORING_MODEL, + CUSTOMER_360_MODEL, + FRAUD_AML_MODEL, + HEALTH_INSURANCE_FRAUD_MODEL, + ] + + for i, model_data in enumerate(models): + data_model = DataModel.from_dict(model_data) + + # Verify the conversion worked + assert isinstance(data_model, DataModel) + assert len(data_model.nodes) > 0 + assert len(data_model.relationships) >= 0 + + # Verify all nodes have required fields + for node in data_model.nodes: + assert node.label is not None + assert node.key_property is not None + assert node.key_property.name is not None + assert node.key_property.type is not None + + # Verify all relationships have required fields + for rel in data_model.relationships: + assert rel.type is not None + assert rel.start_node_label is not None + assert rel.end_node_label is not None + + +def test_from_dict_invalid_data(): + """Test from_dict conversion with invalid data structure.""" + # Missing nodes + with pytest.raises(KeyError): + DataModel.from_dict({"relationships": []}) + + # Missing relationships + with pytest.raises(KeyError): + DataModel.from_dict({"nodes": []}) + + # Invalid node structure + with pytest.raises(KeyError): + DataModel.from_dict({ + "nodes": [{"label": "Person"}], # Missing key_property + "relationships": [] + }) + + # Invalid relationship structure + with pytest.raises(KeyError): + DataModel.from_dict({ + "nodes": [{"label": "Person", "key_property": {"name": "id", "type": "STRING"}, "properties": []}], + "relationships": [{"type": "KNOWS"}] # Missing start_node_label and end_node_label + }) + + +# ============================================================================ +# Server Tools and MCP Resources Tests +# ============================================================================ + +@pytest.mark.asyncio +async def test_list_example_data_models_tool(): + """Test the list_example_data_models tool functionality.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + + mcp = create_mcp_server() + + # Get the tool function + tools = await mcp.get_tools() + list_tool = tools.get("list_example_data_models") + + assert list_tool is not None, "list_example_data_models tool not found" + + # Test the tool + result = list_tool.fn() + + # Verify structure + assert "available_examples" in result + assert "total_examples" in result + assert "usage" in result + + # Verify expected examples + expected_examples = [ + "patient_journey", "supply_chain", "software_dependency", + "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" + ] + + assert result["total_examples"] == 7 + assert set(result["available_examples"].keys()) == set(expected_examples) + + # Verify each example has required fields + for example_key, example_data in result["available_examples"].items(): + assert "name" in example_data + assert "description" in example_data + assert "nodes" in example_data + assert "relationships" in example_data + assert isinstance(example_data["nodes"], int) + assert isinstance(example_data["relationships"], int) + assert example_data["nodes"] > 0 + assert example_data["relationships"] >= 0 + + +@pytest.mark.asyncio +async def test_load_example_data_model_tool_all_examples(): + """Test the load_example_data_model tool with all available examples.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + + mcp = create_mcp_server() + + # Get the tool function + tools = await mcp.get_tools() + load_tool = tools.get("load_example_data_model") + + assert load_tool is not None, "load_example_data_model tool not found" + + # Test all examples + examples = [ + "patient_journey", "supply_chain", "software_dependency", + "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" + ] + + for example_name in examples: + data_model = load_tool.fn(example_name=example_name) + + # Verify it's a valid DataModel + assert isinstance(data_model, DataModel) + assert len(data_model.nodes) > 0 + assert len(data_model.relationships) >= 0 + + # Verify all nodes have required fields + for node in data_model.nodes: + assert node.label is not None + assert node.key_property is not None + assert node.key_property.name is not None + assert node.key_property.type is not None + + # Verify all relationships have required fields + for rel in data_model.relationships: + assert rel.type is not None + assert rel.start_node_label is not None + assert rel.end_node_label is not None + + +@pytest.mark.asyncio +async def test_load_example_data_model_tool_invalid_example(): + """Test the load_example_data_model tool with invalid example name.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + + mcp = create_mcp_server() + + # Get the tool function + tools = await mcp.get_tools() + load_tool = tools.get("load_example_data_model") + + assert load_tool is not None, "load_example_data_model tool not found" + + # Test with invalid example name + with pytest.raises(ValueError, match="Unknown example"): + load_tool.fn(example_name="invalid_example") + + +@pytest.mark.asyncio +async def test_mcp_resources_schemas(): + """Test that all MCP schema resources return valid JSON schemas.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + + mcp = create_mcp_server() + + # Test schema resources + schema_resources = [ + "resource://schema/node", + "resource://schema/relationship", + "resource://schema/property", + "resource://schema/data_model" + ] + + resources = await mcp.get_resources() + for resource_uri in schema_resources: + resource_func = resources.get(resource_uri) + + assert resource_func is not None, f"Resource {resource_uri} not found" + + # Test the resource + schema = resource_func.fn() + + # Verify it's a valid JSON schema + assert isinstance(schema, dict) + assert "type" in schema or "properties" in schema or "$schema" in schema + + +@pytest.mark.asyncio +async def test_mcp_resources_example_models(): + """Test that all MCP example model resources return valid JSON.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + import json + + mcp = create_mcp_server() + + # Test example model resources + example_resources = [ + "resource://examples/patient_journey_model", + "resource://examples/supply_chain_model", + "resource://examples/software_dependency_model", + "resource://examples/oil_gas_monitoring_model", + "resource://examples/customer_360_model", + "resource://examples/fraud_aml_model", + "resource://examples/health_insurance_fraud_model" + ] + + resources = await mcp.get_resources() + for resource_uri in example_resources: + resource_func = resources.get(resource_uri) + + assert resource_func is not None, f"Resource {resource_uri} not found" + + # Test the resource + json_str = resource_func.fn() + + # Verify it's valid JSON + assert isinstance(json_str, str) + + # Parse JSON and verify structure + data = json.loads(json_str) + assert "nodes" in data + assert "relationships" in data + assert isinstance(data["nodes"], list) + assert isinstance(data["relationships"], list) + + # Verify nodes have required structure + for node in data["nodes"]: + assert "label" in node + assert "key_property" in node + assert "properties" in node + assert "name" in node["key_property"] + assert "type" in node["key_property"] + + # Verify relationships have required structure + for rel in data["relationships"]: + assert "type" in rel + assert "start_node_label" in rel + assert "end_node_label" in rel + + +@pytest.mark.asyncio +async def test_mcp_resource_neo4j_data_ingest_process(): + """Test the neo4j_data_ingest_process resource.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + + mcp = create_mcp_server() + + # Find the resource + resources = await mcp.get_resources() + resource_func = resources.get("resource://static/neo4j_data_ingest_process") + + assert resource_func is not None, "neo4j_data_ingest_process resource not found" + + # Test the resource + process_text = resource_func.fn() + + # Verify it returns a string with content + assert isinstance(process_text, str) + assert len(process_text) > 0 + assert "Neo4j" in process_text or "ingest" in process_text.lower() + + +@pytest.mark.asyncio +async def test_tool_validation_with_example_models(): + """Test that all example models can be validated using the validation tools.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + + mcp = create_mcp_server() + + # Get the tools + tools = await mcp.get_tools() + load_tool = tools.get("load_example_data_model") + validate_tool = tools.get("validate_data_model") + + assert load_tool is not None, "load_example_data_model tool not found" + assert validate_tool is not None, "validate_data_model tool not found" + + # Test all examples + examples = [ + "patient_journey", "supply_chain", "software_dependency", + "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" + ] + + for example_name in examples: + # Load the example + data_model = load_tool.fn(example_name=example_name) + + # Validate it + validation_result = validate_tool.fn(data_model=data_model) + + # Should return True for valid models + assert validation_result is True + + +@pytest.mark.asyncio +async def test_consistency_between_resources_and_tools(): + """Test that MCP resources and tools return consistent data for the same example.""" + from mcp_neo4j_data_modeling.server import create_mcp_server + import json + + mcp = create_mcp_server() + + # Get the patient journey example from both resource and tool + resources = await mcp.get_resources() + tools = await mcp.get_tools() + + patient_journey_resource = resources.get("resource://examples/patient_journey_model") + load_tool = tools.get("load_example_data_model") + + assert patient_journey_resource is not None, "patient_journey_model resource not found" + assert load_tool is not None, "load_example_data_model tool not found" + + # Get data from resource (JSON string) + resource_json_str = patient_journey_resource.fn() + resource_data = json.loads(resource_json_str) + + # Get data from tool (DataModel object) + tool_data_model = load_tool.fn(example_name="patient_journey") + + # Verify consistency + assert len(resource_data["nodes"]) == len(tool_data_model.nodes) + assert len(resource_data["relationships"]) == len(tool_data_model.relationships) + + # Verify node labels match + resource_node_labels = {node["label"] for node in resource_data["nodes"]} + tool_node_labels = {node.label for node in tool_data_model.nodes} + assert resource_node_labels == tool_node_labels + + # Verify relationship types match + resource_rel_types = {rel["type"] for rel in resource_data["relationships"]} + tool_rel_types = {rel.type for rel in tool_data_model.relationships} + assert resource_rel_types == tool_rel_types From 137faa4272efc79742e09843224f6035da2d5651 Mon Sep 17 00:00:00 2001 From: runfourestrun <90913666+runfourestrun@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:29:58 +0100 Subject: [PATCH 2/8] refactor: improve template and test organization - Update DATA_MODELING_TEMPLATE to include example data in property descriptions - Remove unnecessary template fields (Direction, Integration Needs, Compliance) - Move server tools and MCP resources tests to dedicated test_server.py file - Use pytest fixture for MCP server to avoid duplication - All 43 tests passing with comprehensive coverage --- .../src/mcp_neo4j_data_modeling/data_model.py | 61 +--- .../src/mcp_neo4j_data_modeling/server.py | 26 +- .../src/mcp_neo4j_data_modeling/static.py | 18 +- .../tests/unit/test_data_model.py | 315 +----------------- .../tests/unit/test_server.py | 182 ++++++++++ 5 files changed, 209 insertions(+), 393 deletions(-) create mode 100644 servers/mcp-neo4j-data-modeling/tests/unit/test_server.py diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py index 1129f8d3..074d99f0 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py @@ -536,65 +536,8 @@ def from_arrows(cls, arrows_data_model_dict: dict[str, Any]) -> "DataModel": @classmethod def from_dict(cls, data_model_dict: dict[str, Any]) -> "DataModel": - "Convert a dictionary representation to a DataModel object." - # Convert nodes - nodes = [] - for node_data in data_model_dict["nodes"]: - # Create key property - key_prop = Property( - name=node_data["key_property"]["name"], - type=node_data["key_property"]["type"] - ) - - # Create other properties - properties = [] - for prop_data in node_data.get("properties", []): - prop = Property( - name=prop_data["name"], - type=prop_data["type"] - ) - properties.append(prop) - - # Create node - node = Node( - label=node_data["label"], - key_property=key_prop, - properties=properties - ) - nodes.append(node) - - # Convert relationships - relationships = [] - for rel_data in data_model_dict["relationships"]: - # Create key property if it exists - key_property = None - if "key_property" in rel_data: - key_property = Property( - name=rel_data["key_property"]["name"], - type=rel_data["key_property"]["type"] - ) - - # Create other properties - properties = [] - for prop_data in rel_data.get("properties", []): - prop = Property( - name=prop_data["name"], - type=prop_data["type"] - ) - properties.append(prop) - - # Create relationship - relationship = Relationship( - type=rel_data["type"], - start_node_label=rel_data["start_node_label"], - end_node_label=rel_data["end_node_label"], - key_property=key_property, - properties=properties - ) - relationships.append(relationship) - - # Create and return the data model - return cls(nodes=nodes, relationships=relationships) + """Convert a dictionary representation to a DataModel object.""" + return cls(**data_model_dict) def to_arrows_dict(self) -> dict[str, Any]: "Convert the data model to an Arrows Data Model Python dictionary." diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py index c733feaf..9df7ab0b 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py @@ -1,3 +1,4 @@ +import json import logging from typing import Any, Literal @@ -65,56 +66,42 @@ def neo4j_data_ingest_process() -> str: def example_patient_journey_model() -> str: """Get a real-world Patient Journey healthcare data model in JSON format.""" logger.info("Getting the Patient Journey healthcare data model.") - import json - return json.dumps(PATIENT_JOURNEY_MODEL, indent=2) @mcp.resource("resource://examples/supply_chain_model") def example_supply_chain_model() -> str: """Get a real-world Supply Chain data model in JSON format.""" logger.info("Getting the Supply Chain data model.") - import json - return json.dumps(SUPPLY_CHAIN_MODEL, indent=2) @mcp.resource("resource://examples/software_dependency_model") def example_software_dependency_model() -> str: """Get a real-world Software Dependency Graph data model in JSON format.""" logger.info("Getting the Software Dependency Graph data model.") - import json - return json.dumps(SOFTWARE_DEPENDENCY_MODEL, indent=2) @mcp.resource("resource://examples/oil_gas_monitoring_model") def example_oil_gas_monitoring_model() -> str: """Get a real-world Oil and Gas Equipment Monitoring data model in JSON format.""" logger.info("Getting the Oil and Gas Equipment Monitoring data model.") - import json - return json.dumps(OIL_GAS_MONITORING_MODEL, indent=2) @mcp.resource("resource://examples/customer_360_model") def example_customer_360_model() -> str: """Get a real-world Customer 360 data model in JSON format.""" logger.info("Getting the Customer 360 data model.") - import json - return json.dumps(CUSTOMER_360_MODEL, indent=2) @mcp.resource("resource://examples/fraud_aml_model") def example_fraud_aml_model() -> str: """Get a real-world Fraud & AML data model in JSON format.""" logger.info("Getting the Fraud & AML data model.") - import json - return json.dumps(FRAUD_AML_MODEL, indent=2) @mcp.resource("resource://examples/health_insurance_fraud_model") def example_health_insurance_fraud_model() -> str: """Get a real-world Health Insurance Fraud Detection data model in JSON format.""" logger.info("Getting the Health Insurance Fraud Detection data model.") - import json - return json.dumps(HEALTH_INSURANCE_FRAUD_MODEL, indent=2) @mcp.tool() @@ -246,14 +233,14 @@ def get_constraints_cypher_queries(data_model: DataModel) -> list[str]: return data_model.get_cypher_constraints_query() @mcp.tool() - def load_example_data_model( + def get_example_data_model( example_name: str = Field( ..., description="Name of the example to load: 'patient_journey', 'supply_chain', 'software_dependency', 'oil_gas_monitoring', 'customer_360', 'fraud_aml', or 'health_insurance_fraud'", ), ) -> DataModel: - """Load an example data model from the available templates. Returns a DataModel object that can be used with validation and export tools.""" - logger.info(f"Loading example data model: {example_name}") + """Get an example data model from the available templates. Returns a DataModel object that can be used with validation and export tools.""" + logger.info(f"Getting example data model: {example_name}") example_map = { "patient_journey": PATIENT_JOURNEY_MODEL, @@ -328,7 +315,7 @@ def list_example_data_models() -> dict[str, Any]: return { "available_examples": examples, "total_examples": len(examples), - "usage": "Use the load_example_data_model tool with any of the example names above to load a specific data model", + "usage": "Use the get_example_data_model tool with any of the example names above to get a specific data model", } return mcp @@ -354,5 +341,4 @@ async def main( if __name__ == "__main__": - import asyncio - asyncio.run(main()) + main() diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py index f5e1ea2b..02f0aa74 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py @@ -24,38 +24,32 @@ ### Entity 1: [Entity Name] - **Properties**: - - [property1]: [type] - [description] - - [property2]: [type] - [description] + - [property1]: [type] - [description] - [example data] + - [property2]: [type] - [description] - [example data] - **Key Property**: [What uniquely identifies this entity?] -- **Example Data**: [Sample values] ### Entity 2: [Entity Name] - **Properties**: - - [property1]: [type] - [description] - - [property2]: [type] - [description] + - [property1]: [type] - [description] - [example data] + - [property2]: [type] - [description] - [example data] - **Key Property**: [What uniquely identifies this entity?] -- **Example Data**: [Sample values] ## Example Relationships How do your entities connect to each other? ### Relationship 1: [Entity1] → [Entity2] - **Type**: [RELATIONSHIP_TYPE] -- **Direction**: [Unidirectional/Bidirectional] - **Properties**: [Any properties on the relationship?] -- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] +- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] (optional) ### Relationship 2: [Entity1] → [Entity2] - **Type**: [RELATIONSHIP_TYPE] -- **Direction**: [Unidirectional/Bidirectional] - **Properties**: [Any properties on the relationship?] -- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] +- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] (optional) ## Additional Context - **Domain**: [Business/Technical/Social/Other] - **Special Requirements**: [Constraints, indexes, specific query patterns] -- **Integration Needs**: [How will this integrate with existing systems?] -- **Compliance**: [Any regulatory or compliance requirements?] """ # Real-World Example: Patient Journey Healthcare Data Model diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py index 384457e2..197918bb 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py @@ -881,306 +881,17 @@ def test_from_dict_all_example_models(): def test_from_dict_invalid_data(): - """Test from_dict conversion with invalid data structure.""" - # Missing nodes - with pytest.raises(KeyError): - DataModel.from_dict({"relationships": []}) - - # Missing relationships - with pytest.raises(KeyError): - DataModel.from_dict({"nodes": []}) - - # Invalid node structure - with pytest.raises(KeyError): - DataModel.from_dict({ - "nodes": [{"label": "Person"}], # Missing key_property - "relationships": [] - }) - - # Invalid relationship structure - with pytest.raises(KeyError): - DataModel.from_dict({ - "nodes": [{"label": "Person", "key_property": {"name": "id", "type": "STRING"}, "properties": []}], - "relationships": [{"type": "KNOWS"}] # Missing start_node_label and end_node_label - }) - - -# ============================================================================ -# Server Tools and MCP Resources Tests -# ============================================================================ - -@pytest.mark.asyncio -async def test_list_example_data_models_tool(): - """Test the list_example_data_models tool functionality.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - - mcp = create_mcp_server() - - # Get the tool function - tools = await mcp.get_tools() - list_tool = tools.get("list_example_data_models") - - assert list_tool is not None, "list_example_data_models tool not found" - - # Test the tool - result = list_tool.fn() - - # Verify structure - assert "available_examples" in result - assert "total_examples" in result - assert "usage" in result - - # Verify expected examples - expected_examples = [ - "patient_journey", "supply_chain", "software_dependency", - "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" - ] - - assert result["total_examples"] == 7 - assert set(result["available_examples"].keys()) == set(expected_examples) - - # Verify each example has required fields - for example_key, example_data in result["available_examples"].items(): - assert "name" in example_data - assert "description" in example_data - assert "nodes" in example_data - assert "relationships" in example_data - assert isinstance(example_data["nodes"], int) - assert isinstance(example_data["relationships"], int) - assert example_data["nodes"] > 0 - assert example_data["relationships"] >= 0 - - -@pytest.mark.asyncio -async def test_load_example_data_model_tool_all_examples(): - """Test the load_example_data_model tool with all available examples.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - - mcp = create_mcp_server() - - # Get the tool function - tools = await mcp.get_tools() - load_tool = tools.get("load_example_data_model") - - assert load_tool is not None, "load_example_data_model tool not found" - - # Test all examples - examples = [ - "patient_journey", "supply_chain", "software_dependency", - "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" - ] - - for example_name in examples: - data_model = load_tool.fn(example_name=example_name) - - # Verify it's a valid DataModel - assert isinstance(data_model, DataModel) - assert len(data_model.nodes) > 0 - assert len(data_model.relationships) >= 0 - - # Verify all nodes have required fields - for node in data_model.nodes: - assert node.label is not None - assert node.key_property is not None - assert node.key_property.name is not None - assert node.key_property.type is not None - - # Verify all relationships have required fields - for rel in data_model.relationships: - assert rel.type is not None - assert rel.start_node_label is not None - assert rel.end_node_label is not None - - -@pytest.mark.asyncio -async def test_load_example_data_model_tool_invalid_example(): - """Test the load_example_data_model tool with invalid example name.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - - mcp = create_mcp_server() - - # Get the tool function - tools = await mcp.get_tools() - load_tool = tools.get("load_example_data_model") - - assert load_tool is not None, "load_example_data_model tool not found" - - # Test with invalid example name - with pytest.raises(ValueError, match="Unknown example"): - load_tool.fn(example_name="invalid_example") - - -@pytest.mark.asyncio -async def test_mcp_resources_schemas(): - """Test that all MCP schema resources return valid JSON schemas.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - - mcp = create_mcp_server() - - # Test schema resources - schema_resources = [ - "resource://schema/node", - "resource://schema/relationship", - "resource://schema/property", - "resource://schema/data_model" - ] - - resources = await mcp.get_resources() - for resource_uri in schema_resources: - resource_func = resources.get(resource_uri) - - assert resource_func is not None, f"Resource {resource_uri} not found" - - # Test the resource - schema = resource_func.fn() - - # Verify it's a valid JSON schema - assert isinstance(schema, dict) - assert "type" in schema or "properties" in schema or "$schema" in schema - - -@pytest.mark.asyncio -async def test_mcp_resources_example_models(): - """Test that all MCP example model resources return valid JSON.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - import json - - mcp = create_mcp_server() - - # Test example model resources - example_resources = [ - "resource://examples/patient_journey_model", - "resource://examples/supply_chain_model", - "resource://examples/software_dependency_model", - "resource://examples/oil_gas_monitoring_model", - "resource://examples/customer_360_model", - "resource://examples/fraud_aml_model", - "resource://examples/health_insurance_fraud_model" - ] - - resources = await mcp.get_resources() - for resource_uri in example_resources: - resource_func = resources.get(resource_uri) - - assert resource_func is not None, f"Resource {resource_uri} not found" - - # Test the resource - json_str = resource_func.fn() - - # Verify it's valid JSON - assert isinstance(json_str, str) - - # Parse JSON and verify structure - data = json.loads(json_str) - assert "nodes" in data - assert "relationships" in data - assert isinstance(data["nodes"], list) - assert isinstance(data["relationships"], list) - - # Verify nodes have required structure - for node in data["nodes"]: - assert "label" in node - assert "key_property" in node - assert "properties" in node - assert "name" in node["key_property"] - assert "type" in node["key_property"] - - # Verify relationships have required structure - for rel in data["relationships"]: - assert "type" in rel - assert "start_node_label" in rel - assert "end_node_label" in rel - - -@pytest.mark.asyncio -async def test_mcp_resource_neo4j_data_ingest_process(): - """Test the neo4j_data_ingest_process resource.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - - mcp = create_mcp_server() - - # Find the resource - resources = await mcp.get_resources() - resource_func = resources.get("resource://static/neo4j_data_ingest_process") - - assert resource_func is not None, "neo4j_data_ingest_process resource not found" - - # Test the resource - process_text = resource_func.fn() - - # Verify it returns a string with content - assert isinstance(process_text, str) - assert len(process_text) > 0 - assert "Neo4j" in process_text or "ingest" in process_text.lower() - - -@pytest.mark.asyncio -async def test_tool_validation_with_example_models(): - """Test that all example models can be validated using the validation tools.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - - mcp = create_mcp_server() - - # Get the tools - tools = await mcp.get_tools() - load_tool = tools.get("load_example_data_model") - validate_tool = tools.get("validate_data_model") - - assert load_tool is not None, "load_example_data_model tool not found" - assert validate_tool is not None, "validate_data_model tool not found" - - # Test all examples - examples = [ - "patient_journey", "supply_chain", "software_dependency", - "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" - ] - - for example_name in examples: - # Load the example - data_model = load_tool.fn(example_name=example_name) - - # Validate it - validation_result = validate_tool.fn(data_model=data_model) - - # Should return True for valid models - assert validation_result is True - - -@pytest.mark.asyncio -async def test_consistency_between_resources_and_tools(): - """Test that MCP resources and tools return consistent data for the same example.""" - from mcp_neo4j_data_modeling.server import create_mcp_server - import json - - mcp = create_mcp_server() - - # Get the patient journey example from both resource and tool - resources = await mcp.get_resources() - tools = await mcp.get_tools() - - patient_journey_resource = resources.get("resource://examples/patient_journey_model") - load_tool = tools.get("load_example_data_model") - - assert patient_journey_resource is not None, "patient_journey_model resource not found" - assert load_tool is not None, "load_example_data_model tool not found" - - # Get data from resource (JSON string) - resource_json_str = patient_journey_resource.fn() - resource_data = json.loads(resource_json_str) - - # Get data from tool (DataModel object) - tool_data_model = load_tool.fn(example_name="patient_journey") - - # Verify consistency - assert len(resource_data["nodes"]) == len(tool_data_model.nodes) - assert len(resource_data["relationships"]) == len(tool_data_model.relationships) - - # Verify node labels match - resource_node_labels = {node["label"] for node in resource_data["nodes"]} - tool_node_labels = {node.label for node in tool_data_model.nodes} - assert resource_node_labels == tool_node_labels + """Test from_dict with invalid data that should raise ValidationError.""" + invalid_data = { + "nodes": [ + { + "label": "Person", + # Missing key_property - should cause ValidationError + "properties": [] + } + ], + "relationships": [] + } - # Verify relationship types match - resource_rel_types = {rel["type"] for rel in resource_data["relationships"]} - tool_rel_types = {rel.type for rel in tool_data_model.relationships} - assert resource_rel_types == tool_rel_types + with pytest.raises(ValidationError): + DataModel.from_dict(invalid_data) diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py new file mode 100644 index 00000000..c5ca8ea8 --- /dev/null +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py @@ -0,0 +1,182 @@ +# ============================================================================ +# Server Tools and MCP Resources Tests +# ============================================================================ + +import pytest +from fastmcp.server import FastMCP + +from mcp_neo4j_data_modeling.server import create_mcp_server + + +@pytest.fixture +def mcp() -> FastMCP: + """Create an MCP server instance for testing.""" + return create_mcp_server() + + +class TestServerTools: + """Test server tools functionality.""" + + @pytest.mark.asyncio + async def test_list_example_data_models_tool(self, mcp: FastMCP): + """Test the list_example_data_models tool.""" + tools = await mcp.get_tools() + list_tool = tools.get("list_example_data_models") + + assert list_tool is not None + result = list_tool.fn() + + assert "available_examples" in result + assert "total_examples" in result + assert "usage" in result + assert result["total_examples"] == 7 + + examples = result["available_examples"] + expected_examples = [ + "patient_journey", "supply_chain", "software_dependency", + "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" + ] + + for example in expected_examples: + assert example in examples + assert "name" in examples[example] + assert "description" in examples[example] + assert "nodes" in examples[example] + assert "relationships" in examples[example] + + @pytest.mark.asyncio + async def test_get_example_data_model_tool_all_examples(self, mcp: FastMCP): + """Test the get_example_data_model tool with all available examples.""" + tools = await mcp.get_tools() + get_tool = tools.get("get_example_data_model") + + assert get_tool is not None + + # Test all available examples + examples = [ + "patient_journey", "supply_chain", "software_dependency", + "oil_gas_monitoring", "customer_360", "fraud_aml", "health_insurance_fraud" + ] + + for example in examples: + result = get_tool.fn(example_name=example) + assert result is not None + assert hasattr(result, "nodes") + assert hasattr(result, "relationships") + assert len(result.nodes) > 0 + assert len(result.relationships) > 0 + + @pytest.mark.asyncio + async def test_get_example_data_model_tool_invalid_example(self, mcp: FastMCP): + """Test the get_example_data_model tool with invalid example name.""" + tools = await mcp.get_tools() + get_tool = tools.get("get_example_data_model") + + assert get_tool is not None + + with pytest.raises(ValueError, match="Unknown example"): + get_tool.fn(example_name="invalid_example") + + +class TestMCPResources: + """Test MCP resources functionality.""" + + @pytest.mark.asyncio + async def test_mcp_resources_schemas(self, mcp: FastMCP): + """Test that schema resources return valid JSON schemas.""" + resources = await mcp.get_resources() + + schema_resources = [ + "resource://schema/node", + "resource://schema/relationship", + "resource://schema/property", + "resource://schema/data_model" + ] + + for resource_uri in schema_resources: + resource = resources.get(resource_uri) + assert resource is not None + result = resource.fn() + assert isinstance(result, dict) + assert "type" in result or "properties" in result + + @pytest.mark.asyncio + async def test_mcp_resources_example_models(self, mcp: FastMCP): + """Test that example model resources return valid JSON strings.""" + resources = await mcp.get_resources() + + example_resources = [ + "resource://examples/patient_journey_model", + "resource://examples/supply_chain_model", + "resource://examples/software_dependency_model", + "resource://examples/oil_gas_monitoring_model", + "resource://examples/customer_360_model", + "resource://examples/fraud_aml_model", + "resource://examples/health_insurance_fraud_model" + ] + + for resource_uri in example_resources: + resource = resources.get(resource_uri) + assert resource is not None + result = resource.fn() + assert isinstance(result, str) + # Should be valid JSON + import json + parsed = json.loads(result) + assert "nodes" in parsed + assert "relationships" in parsed + + @pytest.mark.asyncio + async def test_mcp_resource_neo4j_data_ingest_process(self, mcp: FastMCP): + """Test the Neo4j data ingest process resource.""" + resources = await mcp.get_resources() + resource = resources.get("resource://static/neo4j_data_ingest_process") + + assert resource is not None + result = resource.fn() + assert isinstance(result, str) + assert "constraints" in result.lower() + assert "nodes" in result.lower() + assert "relationships" in result.lower() + + @pytest.mark.asyncio + async def test_tool_validation_with_example_models(self, mcp: FastMCP): + """Test that example models can be validated using the validation tools.""" + tools = await mcp.get_tools() + get_tool = tools.get("get_example_data_model") + validate_tool = tools.get("validate_data_model") + + assert get_tool is not None + assert validate_tool is not None + + # Test validation with each example model + examples = ["patient_journey", "supply_chain", "software_dependency"] + + for example in examples: + data_model = get_tool.fn(example_name=example) + validation_result = validate_tool.fn(data_model=data_model) + assert validation_result is True + + @pytest.mark.asyncio + async def test_consistency_between_resources_and_tools(self, mcp: FastMCP): + """Test that resources and tools return consistent data for the same models.""" + tools = await mcp.get_tools() + resources = await mcp.get_resources() + + get_tool = tools.get("get_example_data_model") + patient_journey_resource = resources.get("resource://examples/patient_journey_model") + + assert get_tool is not None + assert patient_journey_resource is not None + + # Get model via tool + tool_model = get_tool.fn(example_name="patient_journey") + + # Get model via resource + import json + resource_json = patient_journey_resource.fn() + resource_data = json.loads(resource_json) + + # Both should have the same number of nodes and relationships + assert len(tool_model.nodes) == len(resource_data["nodes"]) + assert len(tool_model.relationships) == len(resource_data["relationships"]) \ No newline at end of file From 2e38733bc7bd58522b7274454c12e5f6e46fda8f Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Jul 2025 11:52:05 -0500 Subject: [PATCH 3/8] replace .to_dict with built in functions, update example unit tests and get_example model_tool to return viz config too --- .../src/mcp_neo4j_data_modeling/data_model.py | 5 - .../src/mcp_neo4j_data_modeling/models.py | 8 + .../src/mcp_neo4j_data_modeling/server.py | 13 +- .../tests/unit/conftest.py | 9 + .../tests/unit/test_data_model.py | 249 ------------------ .../tests/unit/test_example_data_models.py | 35 +++ .../tests/unit/test_server.py | 46 ++-- 7 files changed, 78 insertions(+), 287 deletions(-) create mode 100644 servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/models.py create mode 100644 servers/mcp-neo4j-data-modeling/tests/unit/test_example_data_models.py diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py index 074d99f0..b0bc03dd 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py @@ -534,11 +534,6 @@ def from_arrows(cls, arrows_data_model_dict: dict[str, Any]) -> "DataModel": } return cls(nodes=nodes, relationships=relationships, metadata=metadata) - @classmethod - def from_dict(cls, data_model_dict: dict[str, Any]) -> "DataModel": - """Convert a dictionary representation to a DataModel object.""" - return cls(**data_model_dict) - def to_arrows_dict(self) -> dict[str, Any]: "Convert the data model to an Arrows Data Model Python dictionary." node_spacing: int = 200 diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/models.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/models.py new file mode 100644 index 00000000..42f08e47 --- /dev/null +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/models.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel, Field +from .data_model import DataModel + +class ExampleDataModelResponse(BaseModel): + """Response model for the `get_example_data_model` tool.""" + + data_model: DataModel = Field(description="The example graph data model.") + mermaid_config: str = Field(description="The Mermaid visualization configuration for the example graph data model.") \ No newline at end of file diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py index 9df7ab0b..1eacd33f 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py @@ -11,6 +11,7 @@ Property, Relationship, ) +from .models import ExampleDataModelResponse from .static import ( DATA_INGEST_PROCESS, PATIENT_JOURNEY_MODEL, @@ -238,8 +239,8 @@ def get_example_data_model( ..., description="Name of the example to load: 'patient_journey', 'supply_chain', 'software_dependency', 'oil_gas_monitoring', 'customer_360', 'fraud_aml', or 'health_insurance_fraud'", ), - ) -> DataModel: - """Get an example data model from the available templates. Returns a DataModel object that can be used with validation and export tools.""" + ) -> ExampleDataModelResponse: + """Get an example graph data model from the available templates. Returns a DataModel object and the Mermaid visualization configuration for the example graph data model.""" logger.info(f"Getting example data model: {example_name}") example_map = { @@ -259,8 +260,12 @@ def get_example_data_model( example_data = example_map[example_name] - # Use the new from_dict method to convert the JSON to a DataModel object - return DataModel.from_dict(example_data) + validated_data_model = DataModel.model_validate(example_data) + + return ExampleDataModelResponse( + data_model=validated_data_model, + mermaid_config=validated_data_model.get_mermaid_config_str(), + ) @mcp.tool() def list_example_data_models() -> dict[str, Any]: diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/conftest.py b/servers/mcp-neo4j-data-modeling/tests/unit/conftest.py index 3d65ecec..e1f96bb0 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/conftest.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/conftest.py @@ -1,10 +1,17 @@ from typing import Any import pytest +from fastmcp.server import FastMCP from mcp_neo4j_data_modeling.data_model import DataModel, Node, Property, Relationship +from mcp_neo4j_data_modeling.server import create_mcp_server +@pytest.fixture +def test_mcp_server() -> FastMCP: + """Create an MCP server instance for testing.""" + return create_mcp_server() + @pytest.fixture(scope="function") def arrows_data_model_dict() -> dict[str, Any]: return { @@ -165,3 +172,5 @@ def valid_data_model() -> DataModel: end_node_label="Place", ) return DataModel(nodes=nodes, relationships=[relationship]) + + diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py index 197918bb..d4876544 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py @@ -646,252 +646,3 @@ def test_get_cypher_constraints_query(valid_data_model: DataModel): ) -def test_from_dict_basic(): - """Test basic from_dict conversion with minimal data.""" - data = { - "nodes": [ - { - "label": "Person", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "name", "type": "STRING"}], - } - ], - "relationships": [ - { - "type": "KNOWS", - "start_node_label": "Person", - "end_node_label": "Person", - } - ], - } - - data_model = DataModel.from_dict(data) - - assert len(data_model.nodes) == 1 - assert len(data_model.relationships) == 1 - assert data_model.nodes[0].label == "Person" - assert data_model.nodes[0].key_property.name == "id" - assert data_model.nodes[0].key_property.type == "STRING" - assert len(data_model.nodes[0].properties) == 1 - assert data_model.nodes[0].properties[0].name == "name" - assert data_model.relationships[0].type == "KNOWS" - - -def test_from_dict_with_relationship_properties(): - """Test from_dict conversion with relationship properties.""" - data = { - "nodes": [ - { - "label": "Person", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [], - } - ], - "relationships": [ - { - "type": "KNOWS", - "start_node_label": "Person", - "end_node_label": "Person", - "key_property": {"name": "since", "type": "DATE"}, - "properties": [{"name": "strength", "type": "FLOAT"}], - } - ], - } - - data_model = DataModel.from_dict(data) - - assert len(data_model.relationships) == 1 - rel = data_model.relationships[0] - assert rel.key_property.name == "since" - assert rel.key_property.type == "DATE" - assert len(rel.properties) == 1 - assert rel.properties[0].name == "strength" - assert rel.properties[0].type == "FLOAT" - - -def test_from_dict_without_relationship_properties(): - """Test from_dict conversion without relationship properties.""" - data = { - "nodes": [ - { - "label": "Person", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [], - } - ], - "relationships": [ - { - "type": "KNOWS", - "start_node_label": "Person", - "end_node_label": "Person", - } - ], - } - - data_model = DataModel.from_dict(data) - - assert len(data_model.relationships) == 1 - rel = data_model.relationships[0] - assert rel.key_property is None - assert len(rel.properties) == 0 - - -def test_from_dict_multiple_nodes_and_relationships(): - """Test from_dict conversion with multiple nodes and relationships.""" - data = { - "nodes": [ - { - "label": "Person", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "name", "type": "STRING"}], - }, - { - "label": "Company", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "name", "type": "STRING"}], - }, - ], - "relationships": [ - { - "type": "WORKS_FOR", - "start_node_label": "Person", - "end_node_label": "Company", - "properties": [{"name": "start_date", "type": "DATE"}], - }, - { - "type": "MANAGES", - "start_node_label": "Person", - "end_node_label": "Person", - }, - ], - } - - data_model = DataModel.from_dict(data) - - assert len(data_model.nodes) == 2 - assert len(data_model.relationships) == 2 - - # Check nodes - person_node = next(n for n in data_model.nodes if n.label == "Person") - company_node = next(n for n in data_model.nodes if n.label == "Company") - assert person_node is not None - assert company_node is not None - - # Check relationships - works_for_rel = next(r for r in data_model.relationships if r.type == "WORKS_FOR") - manages_rel = next(r for r in data_model.relationships if r.type == "MANAGES") - assert works_for_rel is not None - assert manages_rel is not None - assert len(works_for_rel.properties) == 1 - assert works_for_rel.properties[0].name == "start_date" - - -def test_from_dict_empty_properties(): - """Test from_dict conversion with empty properties arrays.""" - data = { - "nodes": [ - { - "label": "Person", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [], - } - ], - "relationships": [ - { - "type": "KNOWS", - "start_node_label": "Person", - "end_node_label": "Person", - "properties": [], - } - ], - } - - data_model = DataModel.from_dict(data) - - assert len(data_model.nodes[0].properties) == 0 - assert len(data_model.relationships[0].properties) == 0 - - -def test_from_dict_missing_properties_key(): - """Test from_dict conversion when properties key is missing.""" - data = { - "nodes": [ - { - "label": "Person", - "key_property": {"name": "id", "type": "STRING"}, - } - ], - "relationships": [ - { - "type": "KNOWS", - "start_node_label": "Person", - "end_node_label": "Person", - } - ], - } - - data_model = DataModel.from_dict(data) - - assert len(data_model.nodes[0].properties) == 0 - assert len(data_model.relationships[0].properties) == 0 - - -def test_from_dict_all_example_models(): - """Test from_dict conversion with all example models from static.py.""" - from mcp_neo4j_data_modeling.static import ( - PATIENT_JOURNEY_MODEL, - SUPPLY_CHAIN_MODEL, - SOFTWARE_DEPENDENCY_MODEL, - OIL_GAS_MONITORING_MODEL, - CUSTOMER_360_MODEL, - FRAUD_AML_MODEL, - HEALTH_INSURANCE_FRAUD_MODEL, - ) - - models = [ - PATIENT_JOURNEY_MODEL, - SUPPLY_CHAIN_MODEL, - SOFTWARE_DEPENDENCY_MODEL, - OIL_GAS_MONITORING_MODEL, - CUSTOMER_360_MODEL, - FRAUD_AML_MODEL, - HEALTH_INSURANCE_FRAUD_MODEL, - ] - - for i, model_data in enumerate(models): - data_model = DataModel.from_dict(model_data) - - # Verify the conversion worked - assert isinstance(data_model, DataModel) - assert len(data_model.nodes) > 0 - assert len(data_model.relationships) >= 0 - - # Verify all nodes have required fields - for node in data_model.nodes: - assert node.label is not None - assert node.key_property is not None - assert node.key_property.name is not None - assert node.key_property.type is not None - - # Verify all relationships have required fields - for rel in data_model.relationships: - assert rel.type is not None - assert rel.start_node_label is not None - assert rel.end_node_label is not None - - -def test_from_dict_invalid_data(): - """Test from_dict with invalid data that should raise ValidationError.""" - invalid_data = { - "nodes": [ - { - "label": "Person", - # Missing key_property - should cause ValidationError - "properties": [] - } - ], - "relationships": [] - } - - with pytest.raises(ValidationError): - DataModel.from_dict(invalid_data) diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_example_data_models.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_example_data_models.py new file mode 100644 index 00000000..44707fe6 --- /dev/null +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_example_data_models.py @@ -0,0 +1,35 @@ +""" +Test that the example data models adhere to the DataModel structure. +""" + +from mcp_neo4j_data_modeling.data_model import DataModel +from mcp_neo4j_data_modeling.static import ( + PATIENT_JOURNEY_MODEL, + SUPPLY_CHAIN_MODEL, + SOFTWARE_DEPENDENCY_MODEL, + OIL_GAS_MONITORING_MODEL, + CUSTOMER_360_MODEL, + FRAUD_AML_MODEL, + HEALTH_INSURANCE_FRAUD_MODEL, +) + +def test_patient_journey_model() -> None: + DataModel.model_validate(PATIENT_JOURNEY_MODEL) + +def test_supply_chain_model() -> None: + DataModel.model_validate(SUPPLY_CHAIN_MODEL) + +def test_software_dependency_model() -> None: + DataModel.model_validate(SOFTWARE_DEPENDENCY_MODEL) + +def test_oil_gas_monitoring_model() -> None: + DataModel.model_validate(OIL_GAS_MONITORING_MODEL) + +def test_customer_360_model() -> None: + DataModel.model_validate(CUSTOMER_360_MODEL) + +def test_fraud_aml_model() -> None: + DataModel.model_validate(FRAUD_AML_MODEL) + +def test_health_insurance_fraud_model() -> None: + DataModel.model_validate(HEALTH_INSURANCE_FRAUD_MODEL) \ No newline at end of file diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py index c5ca8ea8..1f56c994 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py @@ -1,26 +1,14 @@ -# ============================================================================ -# Server Tools and MCP Resources Tests -# ============================================================================ - import pytest from fastmcp.server import FastMCP -from mcp_neo4j_data_modeling.server import create_mcp_server - - -@pytest.fixture -def mcp() -> FastMCP: - """Create an MCP server instance for testing.""" - return create_mcp_server() - class TestServerTools: """Test server tools functionality.""" @pytest.mark.asyncio - async def test_list_example_data_models_tool(self, mcp: FastMCP): + async def test_list_example_data_models_tool(self, test_mcp_server: FastMCP): """Test the list_example_data_models tool.""" - tools = await mcp.get_tools() + tools = await test_mcp_server.get_tools() list_tool = tools.get("list_example_data_models") assert list_tool is not None @@ -45,9 +33,9 @@ async def test_list_example_data_models_tool(self, mcp: FastMCP): assert "relationships" in examples[example] @pytest.mark.asyncio - async def test_get_example_data_model_tool_all_examples(self, mcp: FastMCP): + async def test_get_example_data_model_tool_all_examples(self, test_mcp_server: FastMCP): """Test the get_example_data_model tool with all available examples.""" - tools = await mcp.get_tools() + tools = await test_mcp_server.get_tools() get_tool = tools.get("get_example_data_model") assert get_tool is not None @@ -67,9 +55,9 @@ async def test_get_example_data_model_tool_all_examples(self, mcp: FastMCP): assert len(result.relationships) > 0 @pytest.mark.asyncio - async def test_get_example_data_model_tool_invalid_example(self, mcp: FastMCP): + async def test_get_example_data_model_tool_invalid_example(self, test_mcp_server: FastMCP): """Test the get_example_data_model tool with invalid example name.""" - tools = await mcp.get_tools() + tools = await test_mcp_server.get_tools() get_tool = tools.get("get_example_data_model") assert get_tool is not None @@ -82,9 +70,9 @@ class TestMCPResources: """Test MCP resources functionality.""" @pytest.mark.asyncio - async def test_mcp_resources_schemas(self, mcp: FastMCP): + async def test_mcp_resources_schemas(self, test_mcp_server: FastMCP): """Test that schema resources return valid JSON schemas.""" - resources = await mcp.get_resources() + resources = await test_mcp_server.get_resources() schema_resources = [ "resource://schema/node", @@ -101,9 +89,9 @@ async def test_mcp_resources_schemas(self, mcp: FastMCP): assert "type" in result or "properties" in result @pytest.mark.asyncio - async def test_mcp_resources_example_models(self, mcp: FastMCP): + async def test_mcp_resources_example_models(self, test_mcp_server: FastMCP): """Test that example model resources return valid JSON strings.""" - resources = await mcp.get_resources() + resources = await test_mcp_server.get_resources() example_resources = [ "resource://examples/patient_journey_model", @@ -127,9 +115,9 @@ async def test_mcp_resources_example_models(self, mcp: FastMCP): assert "relationships" in parsed @pytest.mark.asyncio - async def test_mcp_resource_neo4j_data_ingest_process(self, mcp: FastMCP): + async def test_mcp_resource_neo4j_data_ingest_process(self, test_mcp_server: FastMCP): """Test the Neo4j data ingest process resource.""" - resources = await mcp.get_resources() + resources = await test_mcp_server.get_resources() resource = resources.get("resource://static/neo4j_data_ingest_process") assert resource is not None @@ -140,9 +128,9 @@ async def test_mcp_resource_neo4j_data_ingest_process(self, mcp: FastMCP): assert "relationships" in result.lower() @pytest.mark.asyncio - async def test_tool_validation_with_example_models(self, mcp: FastMCP): + async def test_tool_validation_with_example_models(self, test_mcp_server: FastMCP): """Test that example models can be validated using the validation tools.""" - tools = await mcp.get_tools() + tools = await test_mcp_server.get_tools() get_tool = tools.get("get_example_data_model") validate_tool = tools.get("validate_data_model") @@ -158,10 +146,10 @@ async def test_tool_validation_with_example_models(self, mcp: FastMCP): assert validation_result is True @pytest.mark.asyncio - async def test_consistency_between_resources_and_tools(self, mcp: FastMCP): + async def test_consistency_between_resources_and_tools(self, test_mcp_server: FastMCP): """Test that resources and tools return consistent data for the same models.""" - tools = await mcp.get_tools() - resources = await mcp.get_resources() + tools = await test_mcp_server.get_tools() + resources = await test_mcp_server.get_resources() get_tool = tools.get("get_example_data_model") patient_journey_resource = resources.get("resource://examples/patient_journey_model") From 9e8a5fa8e4994f4fb2fa66a5f88c6b831a0a60d4 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Jul 2025 11:58:26 -0500 Subject: [PATCH 4/8] update get_example_data_model tests need to parse data model from response since we now include the viz config --- .../tests/unit/test_server.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py index 1f56c994..7c85334c 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_server.py @@ -48,11 +48,17 @@ async def test_get_example_data_model_tool_all_examples(self, test_mcp_server: F for example in examples: result = get_tool.fn(example_name=example) - assert result is not None - assert hasattr(result, "nodes") - assert hasattr(result, "relationships") - assert len(result.nodes) > 0 - assert len(result.relationships) > 0 + data_model = result.data_model + mermaid_config = result.mermaid_config + + assert data_model is not None + assert hasattr(data_model, "nodes") + assert hasattr(data_model, "relationships") + assert len(data_model.nodes) > 0 + assert len(data_model.relationships) > 0 + + assert isinstance(mermaid_config, str) + assert len(mermaid_config) > 0 @pytest.mark.asyncio async def test_get_example_data_model_tool_invalid_example(self, test_mcp_server: FastMCP): @@ -141,7 +147,7 @@ async def test_tool_validation_with_example_models(self, test_mcp_server: FastMC examples = ["patient_journey", "supply_chain", "software_dependency"] for example in examples: - data_model = get_tool.fn(example_name=example) + data_model = get_tool.fn(example_name=example).data_model validation_result = validate_tool.fn(data_model=data_model) assert validation_result is True @@ -158,7 +164,7 @@ async def test_consistency_between_resources_and_tools(self, test_mcp_server: Fa assert patient_journey_resource is not None # Get model via tool - tool_model = get_tool.fn(example_name="patient_journey") + tool_model = get_tool.fn(example_name="patient_journey").data_model # Get model via resource import json From a15a292b1205b116977548c858496bedcc65bf77 Mon Sep 17 00:00:00 2001 From: runfourestrun <90913666+runfourestrun@users.noreply.github.com> Date: Sat, 2 Aug 2025 01:47:08 +0500 Subject: [PATCH 5/8] Added description field to static data models and removed data model prompt --- .../src/mcp_neo4j_data_modeling/static.py | 2658 +++++++++++++---- 1 file changed, 2107 insertions(+), 551 deletions(-) diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py index 02f0aa74..ac9783ec 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/static.py @@ -5,53 +5,6 @@ 3. Then load relationships serially to avoid deadlocks. """ -# General Data Modeling Request Template -DATA_MODELING_TEMPLATE = """ -# Neo4j Data Modeling Request Template - -## Source Data Description -- **Data Format**: [CSV/JSON/XML/API/Database/Other] -- **Data Volume**: [Small (< 1M records) / Medium (1M-100M) / Large (> 100M)] -- **Data Sources**: [List your data sources - files, APIs, databases, etc.] - -## Use Cases & Requirements -- **Primary Queries**: [What questions will you ask of the data?] -- **Scale Expectations**: [Expected number of nodes and relationships] -- **Business Goals**: [What are you trying to achieve?] - -## Example Entities (Nodes) -Please provide examples of the main entities in your data: - -### Entity 1: [Entity Name] -- **Properties**: - - [property1]: [type] - [description] - [example data] - - [property2]: [type] - [description] - [example data] -- **Key Property**: [What uniquely identifies this entity?] - -### Entity 2: [Entity Name] -- **Properties**: - - [property1]: [type] - [description] - [example data] - - [property2]: [type] - [description] - [example data] -- **Key Property**: [What uniquely identifies this entity?] - -## Example Relationships -How do your entities connect to each other? - -### Relationship 1: [Entity1] → [Entity2] -- **Type**: [RELATIONSHIP_TYPE] -- **Properties**: [Any properties on the relationship?] -- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] (optional) - -### Relationship 2: [Entity1] → [Entity2] -- **Type**: [RELATIONSHIP_TYPE] -- **Properties**: [Any properties on the relationship?] -- **Cardinality**: [One-to-One/One-to-Many/Many-to-Many] (optional) - -## Additional Context -- **Domain**: [Business/Technical/Social/Other] -- **Special Requirements**: [Constraints, indexes, specific query patterns] -""" - # Real-World Example: Patient Journey Healthcare Data Model PATIENT_JOURNEY_MODEL = { "nodes": [ @@ -59,136 +12,404 @@ "label": "Patient", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "first", "type": "STRING"}, - {"name": "last", "type": "STRING"}, - {"name": "birthdate", "type": "DATE"}, - {"name": "gender", "type": "STRING"}, - {"name": "address", "type": "STRING"}, - {"name": "city", "type": "STRING"}, - {"name": "state", "type": "STRING"}, - {"name": "county", "type": "STRING"}, - {"name": "location", "type": "POINT"}, - {"name": "latitude", "type": "FLOAT"}, - {"name": "longitude", "type": "FLOAT"}, - {"name": "ethnicity", "type": "STRING"}, - {"name": "race", "type": "STRING"}, - {"name": "martial", "type": "STRING"}, - {"name": "prefix", "type": "STRING"}, - {"name": "birthplace", "type": "STRING"}, - {"name": "deathdate", "type": "DATE"}, - {"name": "drivers", "type": "STRING"}, - {"name": "healthcare_coverage", "type": "FLOAT"}, - {"name": "healthcare_expenses", "type": "FLOAT"}, + { + "name": "first", + "type": "STRING", + "description": "Patient's first name", + }, + { + "name": "last", + "type": "STRING", + "description": "Patient's last name", + }, + { + "name": "birthdate", + "type": "DATE", + "description": "Patient's date of birth", + }, + { + "name": "gender", + "type": "STRING", + "description": "Patient's gender identity", + }, + { + "name": "address", + "type": "STRING", + "description": "Patient's street address", + }, + { + "name": "city", + "type": "STRING", + "description": "City where patient resides", + }, + { + "name": "state", + "type": "STRING", + "description": "State/province where patient resides", + }, + { + "name": "county", + "type": "STRING", + "description": "County where patient resides", + }, + { + "name": "location", + "type": "POINT", + "description": "Geographic coordinates of patient's location", + }, + { + "name": "latitude", + "type": "FLOAT", + "description": "Latitude coordinate of patient's location", + }, + { + "name": "longitude", + "type": "FLOAT", + "description": "Longitude coordinate of patient's location", + }, + { + "name": "ethnicity", + "type": "STRING", + "description": "Patient's ethnic background", + }, + { + "name": "race", + "type": "STRING", + "description": "Patient's racial background", + }, + { + "name": "martial", + "type": "STRING", + "description": "Patient's marital status", + }, + { + "name": "prefix", + "type": "STRING", + "description": "Patient's name prefix (e.g., Dr., Mr., Ms.)", + }, + { + "name": "birthplace", + "type": "STRING", + "description": "City/country where patient was born", + }, + { + "name": "deathdate", + "type": "DATE", + "description": "Date of patient's death if deceased", + }, + { + "name": "drivers", + "type": "STRING", + "description": "Patient's driver's license number", + }, + { + "name": "healthcare_coverage", + "type": "FLOAT", + "description": "Amount of healthcare coverage in currency", + }, + { + "name": "healthcare_expenses", + "type": "FLOAT", + "description": "Total healthcare expenses incurred", + }, ], }, { "label": "Encounter", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "code", "type": "STRING"}, - {"name": "description", "type": "STRING"}, - {"name": "class", "type": "STRING"}, - {"name": "start", "type": "STRING"}, - {"name": "end", "type": "STRING"}, - {"name": "isStart", "type": "BOOLEAN"}, - {"name": "isEnd", "type": "BOOLEAN"}, - {"name": "baseCost", "type": "FLOAT"}, - {"name": "claimCost", "type": "FLOAT"}, - {"name": "coveredAmount", "type": "FLOAT"}, + { + "name": "code", + "type": "STRING", + "description": "Encounter code or identifier", + }, + { + "name": "description", + "type": "STRING", + "description": "Encounter description or reason for visit", + }, + { + "name": "class", + "type": "STRING", + "description": "Encounter class (emergency, outpatient, inpatient, etc.)", + }, + { + "name": "start", + "type": "STRING", + "description": "Encounter start date and time", + }, + { + "name": "end", + "type": "STRING", + "description": "Encounter end date and time", + }, + { + "name": "isStart", + "type": "BOOLEAN", + "description": "Whether this is the start of the encounter", + }, + { + "name": "isEnd", + "type": "BOOLEAN", + "description": "Whether this is the end of the encounter", + }, + { + "name": "baseCost", + "type": "FLOAT", + "description": "Base cost of the encounter", + }, + { + "name": "claimCost", + "type": "FLOAT", + "description": "Claim cost for the encounter", + }, + { + "name": "coveredAmount", + "type": "FLOAT", + "description": "Amount covered by insurance", + }, ], }, { "label": "Provider", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "address", "type": "STRING"}, - {"name": "location", "type": "POINT"}, + { + "name": "name", + "type": "STRING", + "description": "Provider's full name", + }, + { + "name": "address", + "type": "STRING", + "description": "Provider's practice address", + }, + { + "name": "location", + "type": "POINT", + "description": "Geographic coordinates of provider's practice location", + }, ], }, { "label": "Organization", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "address", "type": "STRING"}, - {"name": "city", "type": "STRING"}, - {"name": "location", "type": "POINT"}, + { + "name": "name", + "type": "STRING", + "description": "Healthcare organization name", + }, + { + "name": "address", + "type": "STRING", + "description": "Organization's address", + }, + { + "name": "city", + "type": "STRING", + "description": "City where organization is located", + }, + { + "name": "location", + "type": "POINT", + "description": "Geographic coordinates of organization location", + }, ], }, { "label": "Condition", "key_property": {"name": "code", "type": "STRING"}, "properties": [ - {"name": "description", "type": "STRING"}, - {"name": "start", "type": "STRING"}, - {"name": "stop", "type": "STRING"}, - {"name": "isEnd", "type": "BOOLEAN"}, - {"name": "total_drug_pairings", "type": "INTEGER"}, + { + "name": "description", + "type": "STRING", + "description": "Medical condition description", + }, + { + "name": "start", + "type": "STRING", + "description": "Date when condition was diagnosed", + }, + { + "name": "stop", + "type": "STRING", + "description": "Date when condition was resolved (Optional)", + }, + { + "name": "isEnd", + "type": "BOOLEAN", + "description": "Indicates if condition is resolved", + }, + { + "name": "total_drug_pairings", + "type": "INTEGER", + "description": "Number of drug combinations prescribed for this condition", + }, ], }, { "label": "Drug", "key_property": {"name": "code", "type": "STRING"}, "properties": [ - {"name": "description", "type": "STRING"}, - {"name": "start", "type": "STRING"}, - {"name": "stop", "type": "STRING"}, - {"name": "isEnd", "type": "BOOLEAN"}, - {"name": "basecost", "type": "STRING"}, - {"name": "totalcost", "type": "STRING"}, + { + "name": "description", + "type": "STRING", + "description": "Drug description", + }, + { + "name": "start", + "type": "STRING", + "description": "Date when drug was prescribed", + }, + { + "name": "stop", + "type": "STRING", + "description": "Date when drug prescription ended (Optional), Conditional on isEnd property", + }, + { + "name": "isEnd", + "type": "BOOLEAN", + "description": "Indicates if drug prescription is discontinued", + }, + { + "name": "basecost", + "type": "STRING", + "description": "Base cost of the drug before insurance", + }, + { + "name": "totalcost", + "type": "STRING", + "description": "Total cost of the drug including insurance", + }, ], }, { "label": "Procedure", "key_property": {"name": "code", "type": "STRING"}, - "properties": [{"name": "description", "type": "STRING"}], + "properties": [ + { + "name": "description", + "type": "STRING", + "description": "Medical procedure description", + } + ], }, { "label": "Observation", "key_property": {"name": "code", "type": "STRING"}, "properties": [ - {"name": "description", "type": "STRING"}, - {"name": "category", "type": "STRING"}, - {"name": "type", "type": "STRING"}, + { + "name": "description", + "type": "STRING", + "description": "Observation description", + }, + { + "name": "category", + "type": "STRING", + "description": "Category of observation", + }, + { + "name": "type", + "type": "STRING", + "description": "Type of observation measurement", + }, ], }, { "label": "Device", "key_property": {"name": "code", "type": "STRING"}, - "properties": [{"name": "description", "type": "STRING"}], + "properties": [ + { + "name": "description", + "type": "STRING", + "description": "Medical device description", + } + ], }, { "label": "CarePlan", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "code", "type": "STRING"}, - {"name": "description", "type": "STRING"}, - {"name": "start", "type": "STRING"}, - {"name": "end", "type": "STRING"}, - {"name": "isEnd", "type": "BOOLEAN"}, - {"name": "reasoncode", "type": "STRING"}, + { + "name": "code", + "type": "STRING", + "description": "Care plan code or identifier", + }, + { + "name": "description", + "type": "STRING", + "description": "Care plan description", + }, + { + "name": "start", + "type": "STRING", + "description": "Date when care plan was initiated", + }, + { + "name": "end", + "type": "STRING", + "description": "Date when care plan was completed (Optional) and conditional on isEnd property", + }, + { + "name": "isEnd", + "type": "BOOLEAN", + "description": "Indicates if care plan is completed", + }, + { + "name": "reasoncode", + "type": "STRING", + "description": "Reason code for care plan creation", + }, ], }, { "label": "Allergy", "key_property": {"name": "code", "type": "STRING"}, "properties": [ - {"name": "description", "type": "STRING"}, - {"name": "category", "type": "STRING"}, - {"name": "system", "type": "STRING"}, - {"name": "type", "type": "STRING"}, + { + "name": "description", + "type": "STRING", + "description": "Allergy description and symptoms", + }, + { + "name": "category", + "type": "STRING", + "description": "Category of allergy", + }, + { + "name": "system", + "type": "STRING", + "description": "Body system affected by the allergy", + }, + { + "name": "type", + "type": "STRING", + "description": "Type of allergic reaction", + }, ], }, { "label": "Reaction", "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "description", "type": "STRING"}], + "properties": [ + { + "name": "description", + "type": "STRING", + "description": "Description of allergic reaction symptoms", + } + ], }, { "label": "Payer", "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "name", "type": "STRING"}], + "properties": [ + { + "name": "name", + "type": "STRING", + "description": "Insurance payer name", + } + ], }, { "label": "Speciality", @@ -246,9 +467,21 @@ "start_node_label": "Encounter", "end_node_label": "Observation", "properties": [ - {"name": "date", "type": "STRING"}, - {"name": "value", "type": "STRING"}, - {"name": "unit", "type": "STRING"}, + { + "name": "date", + "type": "STRING", + "description": "Date when observation was recorded", + }, + { + "name": "value", + "type": "STRING", + "description": "Observation value or result", + }, + { + "name": "unit", + "type": "STRING", + "description": "Unit of measurement for the observation", + }, ], }, { @@ -270,7 +503,13 @@ "type": "ALLERGY_DETECTED", "start_node_label": "Encounter", "end_node_label": "Allergy", - "properties": [{"name": "start", "type": "STRING"}], + "properties": [ + { + "name": "start", + "type": "STRING", + "description": "Date when allergy was detected during encounter", + } + ], }, { "type": "CAUSES_REACTION", @@ -281,7 +520,13 @@ "type": "HAS_REACTION", "start_node_label": "Patient", "end_node_label": "Reaction", - "properties": [{"name": "severity", "type": "STRING"}], + "properties": [ + { + "name": "severity", + "type": "STRING", + "description": "Severity level of the allergic reaction", + } + ], }, { "type": "HAS_PAYER", @@ -307,42 +552,197 @@ { "label": "Product", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + {"name": "name", "type": "STRING", "description": "Product name"}, + { + "name": "category", + "type": "STRING", + "description": "Product category", + }, + { + "name": "manufacturer", + "type": "STRING", + "description": "Manufacturer name", + }, + {"name": "cost", "type": "FLOAT", "description": "Product unit cost"}, + { + "name": "weight", + "type": "FLOAT", + "description": "Product weight in kg", + }, + { + "name": "dimensions", + "type": "STRING", + "description": "Product dimensions (LxWxH)", + }, + ], }, { "label": "Order", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "order_date", + "type": "DATE", + "description": "Date when order was placed", + }, + { + "name": "delivery_date", + "type": "DATE", + "description": "Expected delivery date", + }, + { + "name": "status", + "type": "STRING", + "description": "Order status (pending, shipped, delivered)", + }, + { + "name": "total_amount", + "type": "FLOAT", + "description": "Total order value", + }, + { + "name": "priority", + "type": "STRING", + "description": "Order priority level", + }, + ], }, { "label": "Inventory", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "quantity", + "type": "INTEGER", + "description": "Available quantity in stock", + }, + { + "name": "min_threshold", + "type": "INTEGER", + "description": "Minimum stock level for reorder", + }, + { + "name": "max_capacity", + "type": "INTEGER", + "description": "Maximum storage capacity", + }, + { + "name": "last_updated", + "type": "DATETIME", + "description": "Last inventory update timestamp", + }, + ], }, { "label": "Location", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "name", + "type": "STRING", + "description": "Location name (warehouse, store, etc.)", + }, + { + "name": "type", + "type": "STRING", + "description": "Location type (warehouse, retail, distribution center)", + }, + { + "name": "capacity", + "type": "INTEGER", + "description": "Storage capacity in units", + }, + { + "name": "coordinates", + "type": "POINT", + "description": "Geographic coordinates of location", + }, + ], }, { "label": "LegalEntity", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "name", + "type": "STRING", + "description": "Company or organization name", + }, + { + "name": "type", + "type": "STRING", + "description": "Entity type (supplier, customer, manufacturer)", + }, + { + "name": "tax_id", + "type": "STRING", + "description": "Tax identification number", + }, + { + "name": "country", + "type": "STRING", + "description": "Country of registration", + }, + ], }, { "label": "Asset", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + {"name": "name", "type": "STRING", "description": "Asset name"}, + { + "name": "type", + "type": "STRING", + "description": "Asset type (equipment, vehicle, building)", + }, + { + "name": "purchase_date", + "type": "DATE", + "description": "Date when asset was acquired", + }, + { + "name": "value", + "type": "FLOAT", + "description": "Asset value or cost", + }, + ], }, { "label": "BOM", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "name", + "type": "STRING", + "description": "Bill of Materials name", + }, + { + "name": "version", + "type": "STRING", + "description": "BOM version number", + }, + { + "name": "effective_date", + "type": "DATE", + "description": "Date when BOM becomes effective", + }, + ], }, { "label": "Address", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + {"name": "street", "type": "STRING", "description": "Street address"}, + {"name": "city", "type": "STRING", "description": "City name"}, + {"name": "state", "type": "STRING", "description": "State or province"}, + { + "name": "postal_code", + "type": "STRING", + "description": "Postal or ZIP code", + }, + {"name": "country", "type": "STRING", "description": "Country name"}, + ], }, { "label": "GeoRef", @@ -463,113 +863,193 @@ SOFTWARE_DEPENDENCY_MODEL = { "nodes": [ { - "label": "Project", - "key_property": {"name": "name", "type": "STRING"}, - "properties": [], - }, - { - "label": "Dependency", - "key_property": {"name": "artifactId", "type": "STRING"}, + "label": "BadActor", + "key_property": {"name": "id", "type": "INTEGER"}, "properties": [ - {"name": "groupId", "type": "STRING"}, - {"name": "version", "type": "STRING"}, + { + "name": "contributions", + "type": "INTEGER", + "description": "Number of contributions made by the bad actor", + }, + { + "name": "login", + "type": "STRING", + "description": "Bad actor's login username", + }, ], }, { "label": "CVE", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "description", "type": "STRING"}, - {"name": "severity", "type": "STRING"}, + { + "name": "description", + "type": "STRING", + "description": "CVE vulnerability description", + }, + { + "name": "severity", + "type": "STRING", + "description": "CVE severity level (low, medium, high, critical)", + }, ], }, { "label": "Commit", "key_property": {"name": "sha", "type": "STRING"}, - "properties": [{"name": "repository", "type": "STRING"}], - }, - { - "label": "Contributor", - "key_property": {"name": "id", "type": "INTEGER"}, "properties": [ - {"name": "login", "type": "STRING"}, - {"name": "contributions", "type": "INTEGER"}, + { + "name": "repository", + "type": "STRING", + "description": "Repository name where commit was made", + }, ], }, { - "label": "BadActor", + "label": "Contributor", "key_property": {"name": "id", "type": "INTEGER"}, "properties": [ - {"name": "login", "type": "STRING"}, - {"name": "contributions", "type": "INTEGER"}, + { + "name": "contributions", + "type": "INTEGER", + "description": "Number of contributions made by the contributor", + }, + { + "name": "login", + "type": "STRING", + "description": "Contributor's login username", + }, ], }, { - "label": "Organization", - "key_property": {"name": "name", "type": "STRING"}, - "properties": [], - }, - { - "label": "SuspectOrg", + "label": "Customer", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Pom", - "key_property": {"name": "filePath", "type": "STRING"}, - "properties": [{"name": "parentDirectory", "type": "STRING"}], + "label": "Dependency", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "artifactId", + "type": "STRING", + "description": "Maven artifact ID", + }, + {"name": "groupId", "type": "STRING", "description": "Maven group ID"}, + { + "name": "version", + "type": "STRING", + "description": "Dependency version", + }, + ], }, { "label": "Issue", "key_property": {"name": "id", "type": "INTEGER"}, "properties": [ - {"name": "title", "type": "STRING"}, - {"name": "body", "type": "STRING"}, - {"name": "state", "type": "STRING"}, - {"name": "severity", "type": "STRING"}, - {"name": "comments", "type": "INTEGER"}, - {"name": "createdAt", "type": "STRING"}, - {"name": "updatedAt", "type": "STRING"}, - {"name": "url", "type": "STRING"}, - {"name": "htmlUrl", "type": "STRING"}, + {"name": "body", "type": "STRING", "description": "Issue body text"}, + { + "name": "comments", + "type": "INTEGER", + "description": "Number of comments on the issue", + }, + { + "name": "createdAt", + "type": "STRING", + "description": "Issue creation date", + }, + { + "name": "htmlUrl", + "type": "STRING", + "description": "HTML URL for the issue", + }, + { + "name": "severity", + "type": "STRING", + "description": "Issue severity level", + }, + { + "name": "state", + "type": "STRING", + "description": "Issue state (open, closed, etc.)", + }, + {"name": "title", "type": "STRING", "description": "Issue title"}, + { + "name": "updatedAt", + "type": "STRING", + "description": "Issue last update date", + }, + {"name": "url", "type": "STRING", "description": "Issue URL"}, ], }, { - "label": "User", - "key_property": {"name": "id", "type": "INTEGER"}, - "properties": [{"name": "login", "type": "STRING"}], + "label": "Organization", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], + }, + { + "label": "Pom", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "filePath", "type": "STRING", "description": "POM file path"}, + { + "name": "parentDirectory", + "type": "STRING", + "description": "Parent directory path", + }, + ], }, { "label": "Product", "key_property": {"name": "serialNumber", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "softwareVersion", "type": "STRING"}, - {"name": "macAddress", "type": "STRING"}, + { + "name": "macAddress", + "type": "STRING", + "description": "Product MAC address", + }, + {"name": "name", "type": "STRING", "description": "Product name"}, + { + "name": "softwareVersion", + "type": "STRING", + "description": "Software version installed on product", + }, ], }, { - "label": "Customer", + "label": "Project", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { "label": "SuspectCommit", "key_property": {"name": "sha", "type": "STRING"}, - "properties": [{"name": "repository", "type": "STRING"}], + "properties": [ + { + "name": "repository", + "type": "STRING", + "description": "Repository name where suspect commit was made", + }, + ], }, - ], - "relationships": [ { - "type": "DEPENDS_ON", - "start_node_label": "Pom", - "end_node_label": "Dependency", + "label": "SuspectOrg", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], }, { - "type": "BELONGS_TO_PROJECT", - "start_node_label": "Pom", - "end_node_label": "Project", + "label": "User", + "key_property": {"name": "id", "type": "INTEGER"}, + "properties": [ + { + "name": "login", + "type": "STRING", + "description": "User's login username", + }, + ], }, + ], + "relationships": [ { "type": "AFFECTED_BY", "start_node_label": "Dependency", @@ -586,15 +1066,9 @@ "end_node_label": "SuspectOrg", }, { - "type": "HAS_HEAD_COMMIT", - "start_node_label": "Project", - "end_node_label": "Commit", - }, - {"type": "HAS_ISSUE", "start_node_label": "Project", "end_node_label": "Issue"}, - { - "type": "INSTALLED_ON", - "start_node_label": "Project", - "end_node_label": "Product", + "type": "BELONGS_TO_PROJECT", + "start_node_label": "Pom", + "end_node_label": "Project", }, { "type": "CONTRIBUTED_TO", @@ -606,26 +1080,44 @@ "start_node_label": "BadActor", "end_node_label": "Project", }, + { + "type": "CREATED_BY", + "start_node_label": "Issue", + "end_node_label": "User", + }, + { + "type": "DEPENDS_ON", + "start_node_label": "Pom", + "end_node_label": "Dependency", + }, { "type": "HAS_COMMIT", - "start_node_label": "Contributor", + "start_node_label": "BadActor", "end_node_label": "Commit", }, { "type": "HAS_COMMIT", - "start_node_label": "BadActor", + "start_node_label": "Contributor", "end_node_label": "Commit", }, - {"type": "CREATED_BY", "start_node_label": "Issue", "end_node_label": "User"}, { - "type": "PURCHASED", - "start_node_label": "Customer", + "type": "HAS_HEAD_COMMIT", + "start_node_label": "Project", + "end_node_label": "Commit", + }, + { + "type": "HAS_ISSUE", + "start_node_label": "Project", + "end_node_label": "Issue", + }, + { + "type": "INSTALLED_ON", + "start_node_label": "Project", "end_node_label": "Product", - "properties": [{"name": "purchaseDate", "type": "STRING"}], }, { "type": "NEXT_COMMIT", - "start_node_label": "Commit", + "start_node_label": "SuspectCommit", "end_node_label": "Commit", }, { @@ -633,194 +1125,463 @@ "start_node_label": "Commit", "end_node_label": "SuspectCommit", }, + { + "type": "NEXT_COMMIT", + "start_node_label": "Commit", + "end_node_label": "SuspectCommit", + }, { "type": "NEXT_COMMIT", "start_node_label": "SuspectCommit", "end_node_label": "Commit", }, + { + "type": "NEXT_COMMIT", + "start_node_label": "Commit", + "end_node_label": "Commit", + }, + { + "type": "NEXT_COMMIT", + "start_node_label": "Commit", + "end_node_label": "Commit", + }, + { + "type": "PURCHASED", + "start_node_label": "Customer", + "end_node_label": "Product", + }, ], } -# Real-World Example: Oil and Gas Equipment Monitoring +# Real-World Example: Oil & Gas Equipment Monitoring OIL_GAS_MONITORING_MODEL = { "nodes": [ { - "label": "Producer", + "label": "Alert", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "status", + "type": "STRING", + "description": "Alert status (active, resolved, acknowledged)", + }, + { + "name": "type", + "type": "STRING", + "description": "Alert type (pressure, temperature, flow, level)", + }, + ], + }, + { + "label": "Basin", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], + }, + { + "label": "Battery", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "location", "type": "POINT"}, + { + "name": "location", + "type": "POINT", + "description": "Battery location coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Battery name or identifier", + }, ], }, { "label": "CollectionPoint", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "location", "type": "POINT"}, + { + "name": "location", + "type": "POINT", + "description": "Collection point location coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Collection point name or identifier", + }, ], }, { "label": "Equipment", "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "type", "type": "STRING"}], + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Equipment type (compressor, pump, valve, etc.)", + }, + ], }, { - "label": "Vessel", + "label": "EquipmentServiceProvider", "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "type", "type": "STRING"}], + "properties": [ + { + "name": "companyName", + "type": "STRING", + "description": "Service provider company name", + }, + { + "name": "type", + "type": "STRING", + "description": "Service provider type", + }, + ], }, { - "label": "Sensor", + "label": "FlowSensor", "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "type", "type": "STRING"}], + "properties": [ + {"name": "type", "type": "STRING", "description": "Flow sensor type"}, + ], }, { - "label": "Alert", + "label": "FreeWaterKnockOut", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "type", "type": "STRING"}, - {"name": "status", "type": "STRING"}, + { + "name": "type", + "type": "STRING", + "description": "Free water knock out type", + }, ], }, { - "label": "MaintenanceRecord", + "label": "GasCompressor", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "date", "type": "STRING"}, - {"name": "description", "type": "STRING"}, - {"name": "type", "type": "STRING"}, - {"name": "downTime", "type": "FLOAT"}, + { + "name": "type", + "type": "STRING", + "description": "Gas compressor type", + }, ], }, { - "label": "ServiceProvider", + "label": "HeaterTreater", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "companyName", "type": "STRING"}, - {"name": "type", "type": "STRING"}, + { + "name": "type", + "type": "STRING", + "description": "Heater treater type", + }, ], }, { - "label": "Lease", + "label": "HighLevel", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "coordinates", "type": "STRING"}, + { + "name": "status", + "type": "STRING", + "description": "High level status", + }, + {"name": "type", "type": "STRING", "description": "High level type"}, ], }, { - "label": "Basin", + "label": "LACT", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "LACT (Lease Automatic Custody Transfer) type", + }, + ], }, { - "label": "Model", + "label": "Lease", "key_property": {"name": "id", "type": "STRING"}, - "properties": [{"name": "type", "type": "STRING"}], + "properties": [ + { + "name": "coordinates", + "type": "STRING", + "description": "Lease coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Lease name or identifier", + }, + ], }, { - "label": "TransmissionRoute", + "label": "LeaseOperator", "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "start", "type": "POINT"}, - {"name": "end", "type": "POINT"}, + { + "name": "name", + "type": "STRING", + "description": "Lease operator name", + }, + { + "name": "type", + "type": "STRING", + "description": "Lease operator type", + }, ], }, - ], - "relationships": [ { - "type": "COLLECTED_BY", - "start_node_label": "Producer", - "end_node_label": "CollectionPoint", + "label": "Level", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "type", "type": "STRING", "description": "Level sensor type"}, + ], }, { - "type": "MONITORED_BY", - "start_node_label": "Producer", - "end_node_label": "Sensor", + "label": "MaintenanceRecord", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "date", + "type": "STRING", + "description": "Maintenance record date", + }, + { + "name": "description", + "type": "STRING", + "description": "Maintenance description", + }, + { + "name": "downTime", + "type": "FLOAT", + "description": "Downtime in hours", + }, + {"name": "type", "type": "STRING", "description": "Maintenance type"}, + ], }, { - "type": "MONITORED_BY", - "start_node_label": "CollectionPoint", - "end_node_label": "Sensor", + "label": "Model", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Equipment model type", + }, + ], }, { - "type": "MONITORED_BY", - "start_node_label": "Equipment", - "end_node_label": "Sensor", + "label": "OilLease", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "coordinates", + "type": "STRING", + "description": "Oil lease coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Oil lease name or identifier", + }, + ], }, { - "type": "MONITORED_BY", - "start_node_label": "Vessel", - "end_node_label": "Sensor", + "label": "OilPipeline", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "end", + "type": "POINT", + "description": "Pipeline end point coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Pipeline name or identifier", + }, + { + "name": "start", + "type": "POINT", + "description": "Pipeline start point coordinates", + }, + ], }, - {"type": "RAISED", "start_node_label": "Sensor", "end_node_label": "Alert"}, { - "type": "HAS_ALERT", - "start_node_label": "CollectionPoint", - "end_node_label": "Alert", + "label": "OilTank", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "type", "type": "STRING", "description": "Oil tank type"}, + ], }, { - "type": "HAS_ALERT", - "start_node_label": "Equipment", - "end_node_label": "Alert", + "label": "OilWell", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "location", + "type": "POINT", + "description": "Oil well location coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Oil well name or identifier", + }, + ], }, - {"type": "HAS_ALERT", "start_node_label": "Vessel", "end_node_label": "Alert"}, { - "type": "HAS_MAINTENANCE_RECORD", - "start_node_label": "Producer", - "end_node_label": "MaintenanceRecord", + "label": "PipelineStation", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "location", + "type": "POINT", + "description": "Pipeline station location coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Pipeline station name or identifier", + }, + ], }, { - "type": "HAS_MAINTENANCE_RECORD", - "start_node_label": "Equipment", - "end_node_label": "MaintenanceRecord", + "label": "PressureSensor", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Pressure sensor type", + }, + ], }, { - "type": "SERVICED_BY", - "start_node_label": "Producer", - "end_node_label": "ServiceProvider", - }, + "label": "Producer", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "location", + "type": "POINT", + "description": "Producer location coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Producer name or identifier", + }, + ], + }, { - "type": "SERVICED_BY", - "start_node_label": "CollectionPoint", - "end_node_label": "ServiceProvider", + "label": "PumpOffControl", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Pump off control type", + }, + ], }, { - "type": "SERVICED_BY", - "start_node_label": "Equipment", - "end_node_label": "ServiceProvider", + "label": "SWD", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [], }, { - "type": "SERVICED_BY", - "start_node_label": "Lease", - "end_node_label": "ServiceProvider", + "label": "Sensor", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Sensor type (flow, pressure, temperature, level)", + }, + ], }, { - "type": "LOCATED_ON", - "start_node_label": "Producer", - "end_node_label": "Lease", + "label": "ServiceProvider", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "companyName", + "type": "STRING", + "description": "Service provider company name", + }, + { + "name": "name", + "type": "STRING", + "description": "Service provider name", + }, + { + "name": "type", + "type": "STRING", + "description": "Service provider type", + }, + ], }, { - "type": "LOCATED_ON", - "start_node_label": "CollectionPoint", - "end_node_label": "Lease", + "label": "Temperature", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Temperature sensor type", + }, + ], }, { - "type": "LOCATED_IN", - "start_node_label": "Producer", - "end_node_label": "Basin", + "label": "TransmissionRoute", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "end", + "type": "POINT", + "description": "Transmission route end point coordinates", + }, + { + "name": "name", + "type": "STRING", + "description": "Transmission route name or identifier", + }, + { + "name": "start", + "type": "POINT", + "description": "Transmission route start point coordinates", + }, + ], }, { - "type": "LOCATED_AT", - "start_node_label": "Equipment", + "label": "Vessel", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "type", + "type": "STRING", + "description": "Vessel type (tank, separator, etc.)", + }, + ], + }, + { + "label": "WaterTank", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "type", "type": "STRING", "description": "Water tank type"}, + ], + }, + ], + "relationships": [ + { + "type": "COLLECTED_BY", + "start_node_label": "Producer", "end_node_label": "CollectionPoint", }, { - "type": "LOCATED_AT", - "start_node_label": "Vessel", + "type": "COLLECTED_BY", + "start_node_label": "CollectionPoint", "end_node_label": "CollectionPoint", }, { @@ -833,17 +1594,116 @@ "start_node_label": "Vessel", "end_node_label": "Vessel", }, + { + "type": "HAS_ALERT", + "start_node_label": "CollectionPoint", + "end_node_label": "Alert", + }, + { + "type": "HAS_ALERT", + "start_node_label": "Equipment", + "end_node_label": "Alert", + }, + { + "type": "HAS_ALERT", + "start_node_label": "Vessel", + "end_node_label": "Alert", + }, + { + "type": "HAS_MAINTENANCE_RECORD", + "start_node_label": "Equipment", + "end_node_label": "MaintenanceRecord", + }, + { + "type": "HAS_MAINTENANCE_RECORD", + "start_node_label": "Producer", + "end_node_label": "MaintenanceRecord", + }, { "type": "HAS_MODEL", "start_node_label": "Equipment", "end_node_label": "Model", }, - {"type": "MODEL_OF", "start_node_label": "Vessel", "end_node_label": "Model"}, + { + "type": "LOCATED_AT", + "start_node_label": "Vessel", + "end_node_label": "CollectionPoint", + }, + { + "type": "LOCATED_AT", + "start_node_label": "Equipment", + "end_node_label": "CollectionPoint", + }, + { + "type": "LOCATED_IN", + "start_node_label": "Producer", + "end_node_label": "Basin", + }, + { + "type": "LOCATED_ON", + "start_node_label": "CollectionPoint", + "end_node_label": "Lease", + }, + { + "type": "LOCATED_ON", + "start_node_label": "Producer", + "end_node_label": "Lease", + }, + { + "type": "MODEL_OF", + "start_node_label": "Vessel", + "end_node_label": "Model", + }, { "type": "MODEL_OF", "start_node_label": "Equipment", "end_node_label": "Model", }, + { + "type": "MONITORED_BY", + "start_node_label": "CollectionPoint", + "end_node_label": "Sensor", + }, + { + "type": "MONITORED_BY", + "start_node_label": "Producer", + "end_node_label": "Sensor", + }, + { + "type": "MONITORED_BY", + "start_node_label": "Equipment", + "end_node_label": "Sensor", + }, + { + "type": "MONITORED_BY", + "start_node_label": "Vessel", + "end_node_label": "Sensor", + }, + { + "type": "RAISED", + "start_node_label": "Sensor", + "end_node_label": "Alert", + }, + { + "type": "SERVICED_BY", + "start_node_label": "Lease", + "end_node_label": "ServiceProvider", + }, + { + "type": "SERVICED_BY", + "start_node_label": "Producer", + "end_node_label": "ServiceProvider", + }, + { + "type": "SERVICED_BY", + "start_node_label": "Equipment", + "end_node_label": "ServiceProvider", + }, + { + "type": "SERVICED_BY", + "start_node_label": "CollectionPoint", + "end_node_label": "ServiceProvider", + }, { "type": "TRANSMITTED_BY", "start_node_label": "CollectionPoint", @@ -858,145 +1718,207 @@ { "label": "Account", "key_property": {"name": "account_id", "type": "STRING"}, - "properties": [{"name": "account_name", "type": "STRING"}], + "properties": [ + { + "name": "account_name", + "type": "STRING", + "description": "Account or company name", + }, + ], }, { - "label": "Contact", - "key_property": {"name": "contact_id", "type": "STRING"}, + "label": "Address", + "key_property": {"name": "id", "type": "STRING"}, "properties": [ - {"name": "title", "type": "STRING"}, - {"name": "job_function", "type": "STRING"}, - {"name": "job_role", "type": "STRING"}, + {"name": "city", "type": "STRING", "description": "City name"}, + {"name": "country", "type": "STRING", "description": "Country name"}, + { + "name": "subRegion", + "type": "STRING", + "description": "State or province", + }, ], }, { - "label": "Order", - "key_property": {"name": "order_number", "type": "STRING"}, + "label": "Aspect", + "key_property": {"name": "name", "type": "STRING"}, "properties": [ - {"name": "order_create_date", "type": "DATE"}, - {"name": "order_complete_date", "type": "DATE"}, - {"name": "source", "type": "STRING"}, + { + "name": "description", + "type": "STRING", + "description": "Aspect description - aspects are extracted from surveys using NLP techniques", + }, ], }, { - "label": "Product", - "key_property": {"name": "id", "type": "STRING"}, + "label": "Contact", + "key_property": {"name": "contact_id", "type": "STRING"}, + "properties": [ + { + "name": "job_function", + "type": "STRING", + "description": "Job function or department", + }, + { + "name": "job_role", + "type": "STRING", + "description": "Specific job role", + }, + {"name": "title", "type": "STRING", "description": "Job title"}, + ], + }, + { + "label": "Country", + "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Ticket", - "key_property": {"name": "id", "type": "STRING"}, + "label": "DataCenter", + "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Survey", - "key_property": {"name": "record_id", "type": "STRING"}, - "properties": [ - {"name": "status", "type": "STRING"}, - {"name": "start_date", "type": "DATE"}, - {"name": "end_date", "type": "DATE"}, - {"name": "start_timestamp", "type": "INTEGER"}, - {"name": "end_timestamp", "type": "INTEGER"}, - {"name": "duration", "type": "INTEGER"}, - {"name": "recorded_date", "type": "DATE"}, - {"name": "survey_response", "type": "STRING"}, - {"name": "sentiment", "type": "STRING"}, - {"name": "nps_group", "type": "STRING"}, - {"name": "qid1", "type": "STRING"}, - {"name": "qid2_1", "type": "STRING"}, - {"name": "qid2_2", "type": "STRING"}, - {"name": "qid2_3", "type": "STRING"}, - {"name": "qid2_4", "type": "STRING"}, - {"name": "qid4", "type": "STRING"}, - ], + "label": "EscalationCategory", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], }, { - "label": "Address", - "key_property": {"name": "id", "type": "STRING"}, - "properties": [ - {"name": "city", "type": "STRING"}, - {"name": "country", "type": "STRING"}, - {"name": "subRegion", "type": "STRING"}, - ], + "label": "EscalationSubCategory", + "key_property": {"name": "name", "type": "STRING"}, + "properties": [], }, { - "label": "Industry", + "label": "EscalationTicket", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "receipt_date", + "type": "DATE", + "description": "Date when escalation was received", + }, + { + "name": "response_id", + "type": "STRING", + "description": "Response identifier", + }, + { + "name": "status", + "type": "STRING", + "description": "Escalation status", + }, + ], }, { - "label": "Segment", + "label": "IncidentCategory", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "ParentAccount", + "label": "IncidentTicket", "key_property": {"name": "id", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "declared_date", + "type": "DATE", + "description": "Date when incident was declared", + }, + { + "name": "highest_color", + "type": "STRING", + "description": "Highest severity color", + }, + { + "name": "incident_number", + "type": "STRING", + "description": "Incident number", + }, + {"name": "status", "type": "STRING", "description": "Incident status"}, + ], }, { - "label": "SalesProgramType", + "label": "Industry", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Persona", + "label": "Metro", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Service", + "label": "Milestone", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "SurveyType", - "key_property": {"name": "name", "type": "STRING"}, + "label": "Order", + "key_property": {"name": "order_number", "type": "STRING"}, + "properties": [ + { + "name": "order_complete_date", + "type": "DATE", + "description": "Date when order was completed", + }, + { + "name": "order_create_date", + "type": "DATE", + "description": "Date when order was created", + }, + { + "name": "source", + "type": "STRING", + "description": "Order source (web, phone, sales rep)", + }, + ], + }, + { + "label": "OrderType", + "key_property": {"name": "type", "type": "STRING"}, "properties": [], }, { - "label": "Question", - "key_property": {"name": "text", "type": "STRING"}, + "label": "ParentAccount", + "key_property": {"name": "id", "type": "STRING"}, "properties": [], }, { - "label": "Theme", + "label": "Persona", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Aspect", - "key_property": {"name": "name", "type": "STRING"}, + "label": "Product", + "key_property": {"name": "id", "type": "STRING"}, "properties": [], }, { - "label": "Milestone", - "key_property": {"name": "name", "type": "STRING"}, + "label": "Question", + "key_property": {"name": "text", "type": "STRING"}, "properties": [], }, { - "label": "DataCenter", + "label": "Region", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Metro", - "key_property": {"name": "name", "type": "STRING"}, + "label": "RootCause", + "key_property": {"name": "type", "type": "STRING"}, "properties": [], }, { - "label": "Country", + "label": "SalesProgramType", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "Region", + "label": "Segment", "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "OrderType", - "key_property": {"name": "type", "type": "STRING"}, + "label": "Service", + "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { @@ -1005,71 +1927,171 @@ "properties": [], }, { - "label": "TicketType", - "key_property": {"name": "type", "type": "STRING"}, - "properties": [], + "label": "Survey", + "key_property": {"name": "record_id", "type": "STRING"}, + "properties": [ + { + "name": "duration", + "type": "INTEGER", + "description": "Survey duration in seconds", + }, + {"name": "end_date", "type": "DATE", "description": "Survey end date"}, + { + "name": "end_timestamp", + "type": "INTEGER", + "description": "Survey end timestamp", + }, + { + "name": "nps_group", + "type": "STRING", + "description": "Net Promoter Score group", + }, + { + "name": "qid1", + "type": "STRING", + "description": "Question 1 response", + }, + { + "name": "qid2_1", + "type": "STRING", + "description": "Question 2.1 response", + }, + { + "name": "qid2_2", + "type": "STRING", + "description": "Question 2.2 response", + }, + { + "name": "qid2_3", + "type": "STRING", + "description": "Question 2.3 response", + }, + { + "name": "qid2_4", + "type": "STRING", + "description": "Question 2.4 response", + }, + { + "name": "qid4", + "type": "STRING", + "description": "Question 4 response", + }, + { + "name": "recorded_date", + "type": "DATE", + "description": "Date when survey was recorded", + }, + { + "name": "sentiment", + "type": "STRING", + "description": "Sentiment analysis result", + }, + { + "name": "start_date", + "type": "DATE", + "description": "Survey start date", + }, + { + "name": "start_timestamp", + "type": "INTEGER", + "description": "Survey start timestamp", + }, + { + "name": "status", + "type": "STRING", + "description": "Survey completion status", + }, + { + "name": "survey_response", + "type": "STRING", + "description": "Survey response text", + }, + ], }, { - "label": "TicketCode", - "key_property": {"name": "code_name", "type": "STRING"}, + "label": "SurveyType", + "key_property": {"name": "name", "type": "STRING"}, "properties": [], }, { - "label": "EscalationCategory", + "label": "Theme", "key_property": {"name": "name", "type": "STRING"}, - "properties": [], + "properties": [ + { + "name": "description", + "type": "STRING", + "description": "Theme description - themes are extracted from surveys using NLP techniques", + }, + ], }, { - "label": "EscalationSubCategory", - "key_property": {"name": "name", "type": "STRING"}, + "label": "Ticket", + "key_property": {"name": "id", "type": "STRING"}, "properties": [], }, { - "label": "IncidentCategory", - "key_property": {"name": "name", "type": "STRING"}, + "label": "TicketCode", + "key_property": {"name": "code_name", "type": "STRING"}, "properties": [], }, { - "label": "RootCause", + "label": "TicketType", "key_property": {"name": "type", "type": "STRING"}, "properties": [], }, + { + "label": "TroubleTicket", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "created_date", + "type": "STRING", + "description": "Ticket creation date", + }, + { + "name": "days_difference", + "type": "STRING", + "description": "Days between creation and resolution", + }, + { + "name": "resolution_date", + "type": "STRING", + "description": "Ticket resolution date", + }, + { + "name": "severity", + "type": "STRING", + "description": "Ticket severity level", + }, + {"name": "status", "type": "STRING", "description": "Ticket status"}, + { + "name": "ticket_number", + "type": "STRING", + "description": "Ticket number", + }, + ], + }, ], "relationships": [ { "type": "ACCOUNT_CONTACT", "start_node_label": "Account", - "end_node_label": "Contact", - }, - { - "type": "HAS_ADDRESS", - "start_node_label": "Account", - "end_node_label": "Address", - }, - { - "type": "HAS_INDUSTRY", - "start_node_label": "Account", - "end_node_label": "Industry", - }, - { - "type": "IN_SEGMENT", - "start_node_label": "Account", - "end_node_label": "Segment", + "end_node_label": "Contact", }, { - "type": "PARENT_ACCOUNT", - "start_node_label": "Account", - "end_node_label": "ParentAccount", + "type": "AFFECTED_SERVICE", + "start_node_label": "Ticket", + "end_node_label": "Service", }, { - "type": "SALES_PROGRAM_TYPE", + "type": "COMPLETED_SURVEY", "start_node_label": "Account", - "end_node_label": "SalesProgramType", + "end_node_label": "Survey", }, { - "type": "PLACES_ORDER", - "start_node_label": "Account", - "end_node_label": "Order", + "type": "CONTACT_CREATED_TICKET", + "start_node_label": "Contact", + "end_node_label": "Ticket", }, { "type": "CREATED_TICKET", @@ -1077,19 +2099,19 @@ "end_node_label": "Ticket", }, { - "type": "COMPLETED_SURVEY", - "start_node_label": "Account", - "end_node_label": "Survey", + "type": "ESCALATION_CATEGORY", + "start_node_label": "Ticket", + "end_node_label": "EscalationCategory", }, { - "type": "HAS_PERSONA", - "start_node_label": "Contact", - "end_node_label": "Persona", + "type": "ESCALATION_SUB_CATEGORY", + "start_node_label": "Ticket", + "end_node_label": "EscalationSubCategory", }, { - "type": "CONTACT_CREATED_TICKET", - "start_node_label": "Contact", - "end_node_label": "Ticket", + "type": "FILLED_BY", + "start_node_label": "Survey", + "end_node_label": "Contact", }, { "type": "FOR_PRODUCT", @@ -1097,45 +2119,64 @@ "end_node_label": "Product", }, { - "type": "SUB_ORDER_TYPE", - "start_node_label": "Order", - "end_node_label": "SubOrderType", + "type": "HAS_ADDRESS", + "start_node_label": "Account", + "end_node_label": "Address", }, { - "type": "ORDER_TYPE", - "start_node_label": "SubOrderType", - "end_node_label": "OrderType", + "type": "HAS_ASPECT", + "start_node_label": "Survey", + "end_node_label": "Aspect", }, { - "type": "TICKET_FOR_PRODUCT", - "start_node_label": "Ticket", - "end_node_label": "Product", + "type": "HAS_ESCALATION_SUB_CATEGORY", + "start_node_label": "EscalationCategory", + "end_node_label": "EscalationSubCategory", }, { - "type": "AFFECTED_SERVICE", - "start_node_label": "Ticket", - "end_node_label": "Service", + "type": "HAS_INDUSTRY", + "start_node_label": "Account", + "end_node_label": "Industry", }, { - "type": "HAS_TICKET_TYPE", - "start_node_label": "Ticket", - "end_node_label": "TicketType", + "type": "HAS_MILESTONE", + "start_node_label": "SurveyType", + "end_node_label": "Milestone", + }, + { + "type": "HAS_PERSONA", + "start_node_label": "Contact", + "end_node_label": "Persona", }, { "type": "HAS_PROBLEM_CODE", "start_node_label": "Ticket", "end_node_label": "TicketCode", - "properties": [{"name": "code_name", "type": "STRING"}], }, { - "type": "ESCALATION_CATEGORY", + "type": "HAS_QUESTION", + "start_node_label": "SurveyType", + "end_node_label": "Question", + }, + { + "type": "HAS_ROOT_CAUSE", "start_node_label": "Ticket", - "end_node_label": "EscalationCategory", + "end_node_label": "RootCause", }, { - "type": "ESCALATION_SUB_CATEGORY", + "type": "HAS_SURVEY_TYPE", + "start_node_label": "Survey", + "end_node_label": "SurveyType", + }, + { + "type": "HAS_THEME", + "start_node_label": "Survey", + "end_node_label": "Theme", + }, + { + "type": "HAS_TICKET_TYPE", "start_node_label": "Ticket", - "end_node_label": "EscalationSubCategory", + "end_node_label": "TicketType", }, { "type": "INCIDENT_CATEGORY", @@ -1143,31 +2184,39 @@ "end_node_label": "IncidentCategory", }, { - "type": "HAS_ROOT_CAUSE", - "start_node_label": "Ticket", - "end_node_label": "RootCause", + "type": "IN_COUNTRY", + "start_node_label": "Metro", + "end_node_label": "Country", }, { - "type": "FILLED_BY", - "start_node_label": "Survey", - "end_node_label": "Contact", + "type": "IN_METRO", + "start_node_label": "DataCenter", + "end_node_label": "Metro", }, { - "type": "HAS_SURVEY_TYPE", - "start_node_label": "Survey", - "end_node_label": "SurveyType", + "type": "IN_REGION", + "start_node_label": "Country", + "end_node_label": "Region", }, - {"type": "HAS_THEME", "start_node_label": "Survey", "end_node_label": "Theme"}, { - "type": "HAS_ASPECT", - "start_node_label": "Survey", - "end_node_label": "Aspect", - "properties": [ - {"name": "attributed_sentiment", "type": "STRING"}, - {"name": "pos_sentiment_score", "type": "INTEGER"}, - {"name": "neu_sentiment_score", "type": "INTEGER"}, - {"name": "neg_sentiment_score", "type": "INTEGER"}, - ], + "type": "IN_SEGMENT", + "start_node_label": "Account", + "end_node_label": "Segment", + }, + { + "type": "ORDER_TYPE", + "start_node_label": "SubOrderType", + "end_node_label": "OrderType", + }, + { + "type": "PARENT_ACCOUNT", + "start_node_label": "Account", + "end_node_label": "ParentAccount", + }, + { + "type": "PLACES_ORDER", + "start_node_label": "Account", + "end_node_label": "Order", }, { "type": "RELATED_ORDER", @@ -1178,31 +2227,21 @@ "type": "RELATED_TO_DC", "start_node_label": "Survey", "end_node_label": "DataCenter", - "properties": [ - {"name": "dcCage", "type": "STRING"}, - {"name": "dc_alias", "type": "STRING"}, - ], }, { "type": "RESPONDED_TO", "start_node_label": "Survey", "end_node_label": "Question", - "properties": [ - {"name": "nps_group", "type": "STRING"}, - {"name": "nps_score", "type": "INTEGER"}, - {"name": "rating", "type": "STRING"}, - {"name": "rating_score", "type": "STRING"}, - ], }, { - "type": "HAS_MILESTONE", - "start_node_label": "SurveyType", - "end_node_label": "Milestone", + "type": "SALES_PROGRAM_TYPE", + "start_node_label": "Account", + "end_node_label": "SalesProgramType", }, { - "type": "HAS_QUESTION", - "start_node_label": "SurveyType", - "end_node_label": "Question", + "type": "SUB_ORDER_TYPE", + "start_node_label": "Order", + "end_node_label": "SubOrderType", }, { "type": "THEME_HAS_ASPECT", @@ -1210,19 +2249,9 @@ "end_node_label": "Aspect", }, { - "type": "IN_METRO", - "start_node_label": "DataCenter", - "end_node_label": "Metro", - }, - { - "type": "IN_COUNTRY", - "start_node_label": "Metro", - "end_node_label": "Country", - }, - { - "type": "IN_REGION", - "start_node_label": "Country", - "end_node_label": "Region", + "type": "TICKET_FOR_PRODUCT", + "start_node_label": "Ticket", + "end_node_label": "Product", }, ], } @@ -1234,149 +2263,361 @@ "label": "Customer", "key_property": {"name": "customer_id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "date_of_birth", "type": "DATE"}, - {"name": "nationality", "type": "STRING"}, - {"name": "risk_level", "type": "STRING"}, + {"name": "name", "type": "STRING", "description": "Customer full name"}, + { + "name": "date_of_birth", + "type": "DATE", + "description": "Customer date of birth", + }, + { + "name": "nationality", + "type": "STRING", + "description": "Customer nationality", + }, + { + "name": "risk_level", + "type": "STRING", + "description": "Customer risk level (low, medium, high)", + }, ], }, { "label": "Account", "key_property": {"name": "account_number", "type": "STRING"}, "properties": [ - {"name": "account_type", "type": "STRING"}, - {"name": "balance", "type": "FLOAT"}, - {"name": "opening_date", "type": "DATE"}, - {"name": "status", "type": "STRING"}, + { + "name": "account_type", + "type": "STRING", + "description": "Account type (checking, savings, business)", + }, + { + "name": "balance", + "type": "FLOAT", + "description": "Current account balance", + }, + { + "name": "opening_date", + "type": "DATE", + "description": "Account opening date", + }, + { + "name": "status", + "type": "STRING", + "description": "Account status (active, frozen, closed)", + }, ], }, { "label": "Transaction", "key_property": {"name": "transaction_id", "type": "STRING"}, "properties": [ - {"name": "amount", "type": "FLOAT"}, - {"name": "currency", "type": "STRING"}, - {"name": "transaction_date", "type": "DATETIME"}, - {"name": "transaction_type", "type": "STRING"}, - {"name": "description", "type": "STRING"}, + { + "name": "amount", + "type": "FLOAT", + "description": "Transaction amount", + }, + { + "name": "currency", + "type": "STRING", + "description": "Transaction currency", + }, + { + "name": "transaction_date", + "type": "DATETIME", + "description": "Transaction timestamp", + }, + { + "name": "transaction_type", + "type": "STRING", + "description": "Transaction type (transfer, deposit, withdrawal)", + }, + { + "name": "description", + "type": "STRING", + "description": "Transaction description", + }, ], }, { "label": "Counterparty", "key_property": {"name": "counterparty_id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "type", "type": "STRING"}, - {"name": "country", "type": "STRING"}, - {"name": "risk_score", "type": "INTEGER"}, + {"name": "name", "type": "STRING", "description": "Counterparty name"}, + { + "name": "type", + "type": "STRING", + "description": "Counterparty type (individual, business, bank)", + }, + { + "name": "country", + "type": "STRING", + "description": "Counterparty country", + }, + { + "name": "risk_score", + "type": "INTEGER", + "description": "Counterparty risk score", + }, ], }, { "label": "Alert", "key_property": {"name": "alert_id", "type": "STRING"}, "properties": [ - {"name": "alert_type", "type": "STRING"}, - {"name": "severity", "type": "STRING"}, - {"name": "created_date", "type": "DATETIME"}, - {"name": "status", "type": "STRING"}, - {"name": "description", "type": "STRING"}, + { + "name": "alert_type", + "type": "STRING", + "description": "Alert type (suspicious activity, large transaction, etc.)", + }, + { + "name": "severity", + "type": "STRING", + "description": "Alert severity (low, medium, high, critical)", + }, + { + "name": "created_date", + "type": "DATETIME", + "description": "Alert creation timestamp", + }, + { + "name": "status", + "type": "STRING", + "description": "Alert status (new, reviewed, closed)", + }, + { + "name": "description", + "type": "STRING", + "description": "Alert description and details", + }, ], }, { "label": "Case", "key_property": {"name": "case_id", "type": "STRING"}, "properties": [ - {"name": "case_type", "type": "STRING"}, - {"name": "priority", "type": "STRING"}, - {"name": "created_date", "type": "DATETIME"}, - {"name": "status", "type": "STRING"}, - {"name": "assigned_to", "type": "STRING"}, + { + "name": "case_type", + "type": "STRING", + "description": "Case type (fraud investigation, aml review)", + }, + { + "name": "priority", + "type": "STRING", + "description": "Case priority (low, medium, high, urgent)", + }, + { + "name": "created_date", + "type": "DATETIME", + "description": "Case creation timestamp", + }, + { + "name": "status", + "type": "STRING", + "description": "Case status (open, in progress, closed)", + }, + { + "name": "assigned_to", + "type": "STRING", + "description": "Case assigned investigator", + }, ], }, { "label": "Document", "key_property": {"name": "document_id", "type": "STRING"}, "properties": [ - {"name": "document_type", "type": "STRING"}, - {"name": "upload_date", "type": "DATETIME"}, - {"name": "status", "type": "STRING"}, - {"name": "file_path", "type": "STRING"}, + { + "name": "document_type", + "type": "STRING", + "description": "Document type (id, proof of address, bank statement)", + }, + { + "name": "upload_date", + "type": "DATETIME", + "description": "Document upload timestamp", + }, + { + "name": "status", + "type": "STRING", + "description": "Document status (pending, verified, rejected)", + }, + { + "name": "file_path", + "type": "STRING", + "description": "Document file path", + }, ], }, { "label": "RiskAssessment", "key_property": {"name": "assessment_id", "type": "STRING"}, "properties": [ - {"name": "assessment_date", "type": "DATETIME"}, - {"name": "risk_score", "type": "INTEGER"}, - {"name": "risk_factors", "type": "STRING"}, - {"name": "recommendations", "type": "STRING"}, + { + "name": "assessment_date", + "type": "DATETIME", + "description": "Risk assessment date", + }, + { + "name": "risk_score", + "type": "INTEGER", + "description": "Risk score (1-100)", + }, + { + "name": "risk_factors", + "type": "STRING", + "description": "Identified risk factors", + }, + { + "name": "recommendations", + "type": "STRING", + "description": "Risk mitigation recommendations", + }, ], }, { "label": "ComplianceRule", "key_property": {"name": "rule_id", "type": "STRING"}, "properties": [ - {"name": "rule_name", "type": "STRING"}, - {"name": "rule_type", "type": "STRING"}, - {"name": "threshold", "type": "FLOAT"}, - {"name": "description", "type": "STRING"}, + { + "name": "rule_name", + "type": "STRING", + "description": "Compliance rule name", + }, + { + "name": "rule_type", + "type": "STRING", + "description": "Rule type (transaction limit, frequency, pattern)", + }, + { + "name": "threshold", + "type": "FLOAT", + "description": "Rule threshold value", + }, + { + "name": "description", + "type": "STRING", + "description": "Rule description and criteria", + }, ], }, { "label": "SanctionList", "key_property": {"name": "list_id", "type": "STRING"}, "properties": [ - {"name": "list_name", "type": "STRING"}, - {"name": "source", "type": "STRING"}, - {"name": "last_updated", "type": "DATETIME"}, + { + "name": "list_name", + "type": "STRING", + "description": "Sanction list name", + }, + { + "name": "source", + "type": "STRING", + "description": "List source (OFAC, UN, EU)", + }, + { + "name": "last_updated", + "type": "DATETIME", + "description": "Last update timestamp", + }, ], }, { "label": "SanctionedEntity", "key_property": {"name": "entity_id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "entity_type", "type": "STRING"}, - {"name": "country", "type": "STRING"}, - {"name": "sanction_date", "type": "DATE"}, + { + "name": "name", + "type": "STRING", + "description": "Sanctioned entity name", + }, + { + "name": "entity_type", + "type": "STRING", + "description": "Entity type (individual, organization, vessel)", + }, + { + "name": "country", + "type": "STRING", + "description": "Entity's country of origin", + }, + { + "name": "sanction_date", + "type": "DATE", + "description": "Date when entity was sanctioned", + }, ], }, { "label": "Device", "key_property": {"name": "device_id", "type": "STRING"}, "properties": [ - {"name": "device_type", "type": "STRING"}, - {"name": "ip_address", "type": "STRING"}, - {"name": "location", "type": "STRING"}, - {"name": "last_used", "type": "DATETIME"}, + { + "name": "device_type", + "type": "STRING", + "description": "Device type (mobile, desktop, tablet)", + }, + { + "name": "ip_address", + "type": "STRING", + "description": "Device IP address", + }, + { + "name": "location", + "type": "STRING", + "description": "Device location", + }, + { + "name": "last_used", + "type": "DATETIME", + "description": "Last time device was used", + }, ], }, { "label": "Location", "key_property": {"name": "location_id", "type": "STRING"}, "properties": [ - {"name": "country", "type": "STRING"}, - {"name": "city", "type": "STRING"}, - {"name": "risk_level", "type": "STRING"}, + {"name": "country", "type": "STRING", "description": "Country name"}, + {"name": "city", "type": "STRING", "description": "City name"}, + { + "name": "risk_level", + "type": "STRING", + "description": "Location risk level (low, medium, high)", + }, ], }, { "label": "Product", "key_property": {"name": "product_id", "type": "STRING"}, "properties": [ - {"name": "product_name", "type": "STRING"}, - {"name": "product_type", "type": "STRING"}, - {"name": "risk_category", "type": "STRING"}, + { + "name": "product_name", + "type": "STRING", + "description": "Product name", + }, + { + "name": "product_type", + "type": "STRING", + "description": "Product type", + }, + { + "name": "risk_category", + "type": "STRING", + "description": "Product risk category", + }, ], }, { "label": "Employee", "key_property": {"name": "employee_id", "type": "STRING"}, "properties": [ - {"name": "name", "type": "STRING"}, - {"name": "role", "type": "STRING"}, - {"name": "department", "type": "STRING"}, + {"name": "name", "type": "STRING", "description": "Employee name"}, + {"name": "role", "type": "STRING", "description": "Employee role"}, + { + "name": "department", + "type": "STRING", + "description": "Employee department", + }, ], }, ], @@ -1507,20 +2748,295 @@ # Real-World Example: Health Insurance Fraud Detection HEALTH_INSURANCE_FRAUD_MODEL = { "nodes": [ - {"label": "Person", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Address", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Phone", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "IBAN", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Photo", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Investigation", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Beneficiary", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Prescription", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Execution", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Care", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "IP", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Employee", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "Analyst", "key_property": {"name": "id", "type": "STRING"}, "properties": []}, - {"label": "HealthCarePro", "key_property": {"name": "id", "type": "STRING"}, "properties": []} + { + "label": "Person", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING", "description": "Person's full name"}, + { + "name": "date_of_birth", + "type": "DATE", + "description": "Person's date of birth", + }, + { + "name": "nationality", + "type": "STRING", + "description": "Person's nationality", + }, + { + "name": "role", + "type": "STRING", + "description": "Person's role (patient, provider, beneficiary)", + }, + ], + }, + { + "label": "Address", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "street", "type": "STRING", "description": "Street address"}, + {"name": "city", "type": "STRING", "description": "City name"}, + {"name": "postal_code", "type": "STRING", "description": "Postal code"}, + {"name": "country", "type": "STRING", "description": "Country name"}, + ], + }, + { + "label": "Phone", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "number", "type": "STRING", "description": "Phone number"}, + { + "name": "type", + "type": "STRING", + "description": "Phone type (mobile, landline)", + }, + { + "name": "country_code", + "type": "STRING", + "description": "Country calling code", + }, + ], + }, + { + "label": "IBAN", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "account_number", + "type": "STRING", + "description": "IBAN account number", + }, + {"name": "bank_name", "type": "STRING", "description": "Bank name"}, + {"name": "country", "type": "STRING", "description": "Bank country"}, + ], + }, + { + "label": "Photo", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "file_path", + "type": "STRING", + "description": "Photo file path", + }, + { + "name": "upload_date", + "type": "DATETIME", + "description": "Photo upload date", + }, + { + "name": "verification_status", + "type": "STRING", + "description": "Photo verification status", + }, + ], + }, + { + "label": "Investigation", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "case_number", + "type": "STRING", + "description": "Investigation case number", + }, + { + "name": "status", + "type": "STRING", + "description": "Investigation status", + }, + { + "name": "priority", + "type": "STRING", + "description": "Investigation priority level", + }, + { + "name": "created_date", + "type": "DATETIME", + "description": "Investigation creation date", + }, + { + "name": "fraud_type", + "type": "STRING", + "description": "Type of fraud being investigated", + }, + ], + }, + { + "label": "Beneficiary", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING", "description": "Beneficiary name"}, + { + "name": "relationship", + "type": "STRING", + "description": "Relationship to policyholder", + }, + { + "name": "coverage_type", + "type": "STRING", + "description": "Type of coverage", + }, + ], + }, + { + "label": "Prescription", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "medication_name", + "type": "STRING", + "description": "Prescribed medication name", + }, + { + "name": "dosage", + "type": "STRING", + "description": "Prescribed dosage", + }, + { + "name": "quantity", + "type": "INTEGER", + "description": "Prescribed quantity", + }, + { + "name": "prescription_date", + "type": "DATE", + "description": "Date prescription was written", + }, + { + "name": "refills", + "type": "INTEGER", + "description": "Number of refills allowed", + }, + ], + }, + { + "label": "Execution", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "execution_date", + "type": "DATE", + "description": "Date prescription was filled", + }, + { + "name": "pharmacy_name", + "type": "STRING", + "description": "Pharmacy where filled", + }, + {"name": "cost", "type": "FLOAT", "description": "Cost of medication"}, + { + "name": "insurance_coverage", + "type": "FLOAT", + "description": "Amount covered by insurance", + }, + ], + }, + { + "label": "Care", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "care_type", + "type": "STRING", + "description": "Type of care provided", + }, + { + "name": "diagnosis", + "type": "STRING", + "description": "Medical diagnosis", + }, + { + "name": "treatment_date", + "type": "DATE", + "description": "Date of treatment", + }, + {"name": "cost", "type": "FLOAT", "description": "Cost of care"}, + ], + }, + { + "label": "IP", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "ip_address", "type": "STRING", "description": "IP address"}, + { + "name": "location", + "type": "STRING", + "description": "Geographic location", + }, + { + "name": "isp", + "type": "STRING", + "description": "Internet service provider", + }, + { + "name": "last_used", + "type": "DATETIME", + "description": "Last time IP was used", + }, + ], + }, + { + "label": "Employee", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING", "description": "Employee name"}, + { + "name": "department", + "type": "STRING", + "description": "Employee department", + }, + {"name": "role", "type": "STRING", "description": "Employee role"}, + { + "name": "employee_id", + "type": "STRING", + "description": "Employee ID number", + }, + ], + }, + { + "label": "Analyst", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + {"name": "name", "type": "STRING", "description": "Analyst name"}, + { + "name": "specialization", + "type": "STRING", + "description": "Analyst specialization", + }, + { + "name": "experience_years", + "type": "INTEGER", + "description": "Years of experience", + }, + ], + }, + { + "label": "HealthCarePro", + "key_property": {"name": "id", "type": "STRING"}, + "properties": [ + { + "name": "name", + "type": "STRING", + "description": "Healthcare professional name", + }, + { + "name": "license_number", + "type": "STRING", + "description": "Professional license number", + }, + { + "name": "specialty", + "type": "STRING", + "description": "Medical specialty", + }, + { + "name": "practice_name", + "type": "STRING", + "description": "Practice or hospital name", + }, + ], + }, ], "relationships": [ {"type": "HAS", "start_node_label": "Person", "end_node_label": "Address"}, @@ -1528,16 +3044,56 @@ {"type": "HAS", "start_node_label": "Person", "end_node_label": "IBAN"}, {"type": "HAS", "start_node_label": "Person", "end_node_label": "Photo"}, {"type": "HAS", "start_node_label": "Person", "end_node_label": "IP"}, - {"type": "HAS_MANAGER", "start_node_label": "Person", "end_node_label": "Person"}, - {"type": "PRESCRIPTION_FOR", "start_node_label": "Person", "end_node_label": "Prescription"}, - {"type": "RESPONSIBLE_FOR", "start_node_label": "Person", "end_node_label": "Prescription"}, - {"type": "BENEFICIARY_FOR", "start_node_label": "Person", "end_node_label": "Execution"}, - {"type": "RESPONSIBLE_FOR", "start_node_label": "Person", "end_node_label": "Execution"}, - {"type": "CONTAINS", "start_node_label": "Prescription", "end_node_label": "Care"}, + { + "type": "HAS_MANAGER", + "start_node_label": "Person", + "end_node_label": "Person", + }, + { + "type": "PRESCRIPTION_FOR", + "start_node_label": "Person", + "end_node_label": "Prescription", + }, + { + "type": "RESPONSIBLE_FOR", + "start_node_label": "Person", + "end_node_label": "Prescription", + }, + { + "type": "BENEFICIARY_FOR", + "start_node_label": "Person", + "end_node_label": "Execution", + }, + { + "type": "RESPONSIBLE_FOR", + "start_node_label": "Person", + "end_node_label": "Execution", + }, + { + "type": "CONTAINS", + "start_node_label": "Prescription", + "end_node_label": "Care", + }, {"type": "CONTAINS", "start_node_label": "Execution", "end_node_label": "Care"}, - {"type": "ABOUT", "start_node_label": "Investigation", "end_node_label": "Person"}, - {"type": "HAS_AUTHOR", "start_node_label": "Investigation", "end_node_label": "Person"}, - {"type": "INVOLVES", "start_node_label": "Investigation", "end_node_label": "Person"}, - {"type": "NEXT_STATUS", "start_node_label": "Investigation", "end_node_label": "Investigation"} - ] + { + "type": "ABOUT", + "start_node_label": "Investigation", + "end_node_label": "Person", + }, + { + "type": "HAS_AUTHOR", + "start_node_label": "Investigation", + "end_node_label": "Person", + }, + { + "type": "INVOLVES", + "start_node_label": "Investigation", + "end_node_label": "Person", + }, + { + "type": "NEXT_STATUS", + "start_node_label": "Investigation", + "end_node_label": "Investigation", + }, + ], } From 3e0b84f36f379ce9a8de1d18155e034c89fb5e28 Mon Sep 17 00:00:00 2001 From: runfourestrun <90913666+runfourestrun@users.noreply.github.com> Date: Sat, 2 Aug 2025 01:55:33 +0500 Subject: [PATCH 6/8] Fix duplicate relationship validation to allow multiple relationships with same pattern --- .../src/mcp_neo4j_data_modeling/data_model.py | 8 -------- .../tests/unit/test_data_model.py | 16 +++++++++++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py index b0bc03dd..89eabded 100644 --- a/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py +++ b/servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py @@ -414,14 +414,6 @@ def validate_relationships( ) -> list[Relationship]: "Validate the relationships." - # check for duplicate relationships - counts = Counter([r.pattern for r in relationships]) - for pattern, count in counts.items(): - if count > 1: - raise ValueError( - f"Relationship with pattern {pattern} appears {count} times in data model" - ) - # ensure source and target nodes exist for relationship in relationships: if relationship.start_node_label not in [ diff --git a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py index d4876544..550d871a 100644 --- a/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py +++ b/servers/mcp-neo4j-data-modeling/tests/unit/test_data_model.py @@ -262,6 +262,14 @@ def test_data_model_validate_relationships_valid(): def test_data_model_validate_relationships_invalid_dupe_patterns(): """Test data model validation with duplicate relationship patterns.""" + nodes = [ + Node( + label="Person", + key_property=Property( + name="id", type="string", description="Unique identifier" + ), + ), + ] relationships = [ Relationship( type="KNOWS", @@ -276,11 +284,9 @@ def test_data_model_validate_relationships_invalid_dupe_patterns(): properties=[], ), ] - with pytest.raises( - ValidationError, - match=r"Relationship with pattern \(:Person\)-\[:KNOWS\]->\(:Person\) appears 2 times in data model", - ): - DataModel(nodes=[], relationships=relationships) + # Since we removed duplicate relationship validation, this should now pass + data_model = DataModel(nodes=nodes, relationships=relationships) + assert len(data_model.relationships) == 2 def test_data_model_validate_relationships_invalid_start_node_does_not_exist(): From 4f088c4be52316db4ab886a66affad4d6a19f770 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 7 Aug 2025 18:01:06 -0500 Subject: [PATCH 7/8] update readme and changelog --- servers/mcp-neo4j-data-modeling/CHANGELOG.md | 1 + servers/mcp-neo4j-data-modeling/README.md | 52 +++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/servers/mcp-neo4j-data-modeling/CHANGELOG.md b/servers/mcp-neo4j-data-modeling/CHANGELOG.md index 616f5ab0..989c22fc 100644 --- a/servers/mcp-neo4j-data-modeling/CHANGELOG.md +++ b/servers/mcp-neo4j-data-modeling/CHANGELOG.md @@ -7,6 +7,7 @@ ### Added * Update PR workflow to iterate over Python 3.10 to 3.13 +* Add example data model resources and tools to retrieve them ## v0.2.0 diff --git a/servers/mcp-neo4j-data-modeling/README.md b/servers/mcp-neo4j-data-modeling/README.md index c4486868..c2ed76e4 100644 --- a/servers/mcp-neo4j-data-modeling/README.md +++ b/servers/mcp-neo4j-data-modeling/README.md @@ -10,6 +10,8 @@ A Model Context Protocol (MCP) server implementation that provides tools for cre The server provides these resources: +#### Schema + - `resource://schema/node` - Get the JSON schema for a Node object - Returns: JSON schema defining the structure of a Node @@ -25,7 +27,40 @@ The server provides these resources: - `resource://schema/data_model` - Get the JSON schema for a DataModel object - Returns: JSON schema defining the structure of a DataModel - + +#### Example Data Models + +- `resource://examples/patient_journey_model` + - Get a real-world Patient Journey healthcare data model in JSON format + - Returns: JSON DataModel for tracking patient encounters, conditions, medications, and care plans + +- `resource://examples/supply_chain_model` + - Get a real-world Supply Chain data model in JSON format + - Returns: JSON DataModel for tracking products, orders, inventory, and locations + +- `resource://examples/software_dependency_model` + - Get a real-world Software Dependency Graph data model in JSON format + - Returns: JSON DataModel for software dependency tracking with security vulnerabilities, commits, and contributor analysis + +- `resource://examples/oil_gas_monitoring_model` + - Get a real-world Oil and Gas Equipment Monitoring data model in JSON format + - Returns: JSON DataModel for industrial monitoring of oil and gas equipment, sensors, alerts, and maintenance + +- `resource://examples/customer_360_model` + - Get a real-world Customer 360 data model in JSON format + - Returns: JSON DataModel for customer relationship management with accounts, contacts, orders, tickets, and surveys + +- `resource://examples/fraud_aml_model` + - Get a real-world Fraud & AML data model in JSON format + - Returns: JSON DataModel for financial fraud detection and anti-money laundering with customers, transactions, alerts, and compliance + +- `resource://examples/health_insurance_fraud_model` + - Get a real-world Health Insurance Fraud Detection data model in JSON format + - Returns: JSON DataModel for healthcare fraud detection tracking investigations, prescriptions, executions, and beneficiary relationships + + +#### Ingest + - `resource://neo4j_data_ingest_process` - Get a detailed explanation of the recommended process for ingesting data into Neo4j using the data model - Returns: Markdown document explaining the ingest process @@ -77,6 +112,21 @@ These tools provide integration with **[Arrows](https://arrows.app/)** - a graph - `data_model` (DataModel): The data model to export - Returns: JSON string compatible with Arrows app +#### 📚 Example Data Model Tools + +These tools provide access to pre-built example data models for common use cases and domains. + +- `list_example_data_models` + - List all available example data models with descriptions + - Input: None + - Returns: Dictionary with example names, descriptions, node/relationship counts, and usage instructions + +- `get_example_data_model` + - Get an example graph data model from the available templates + - Input: + - `example_name` (str): Name of the example to load ('patient_journey', 'supply_chain', 'software_dependency', 'oil_gas_monitoring', 'customer_360', 'fraud_aml', or 'health_insurance_fraud') + - Returns: ExampleDataModelResponse containing DataModel object and Mermaid visualization configuration + #### 📝 Cypher Ingest Tools These tools may be used to create Cypher ingest queries based on the data model. These queries may then be used by other MCP servers or applications to load data into Neo4j. From 88e9631b9d3984157f9735360a079ab162e9a20c Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 7 Aug 2025 18:05:17 -0500 Subject: [PATCH 8/8] Update CHANGELOG.md --- servers/mcp-neo4j-data-modeling/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/servers/mcp-neo4j-data-modeling/CHANGELOG.md b/servers/mcp-neo4j-data-modeling/CHANGELOG.md index 989c22fc..c123ad9b 100644 --- a/servers/mcp-neo4j-data-modeling/CHANGELOG.md +++ b/servers/mcp-neo4j-data-modeling/CHANGELOG.md @@ -7,7 +7,8 @@ ### Added * Update PR workflow to iterate over Python 3.10 to 3.13 -* Add example data model resources and tools to retrieve them +* Add example data model resources +* Add tools to list and retrieve example data models and their Mermaid configurations ## v0.2.0