sfm-core 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. api/__init__.py +19 -0
  2. api/rest/__init__.py +1 -0
  3. api/rest/app.py +87 -0
  4. api/rest/config.py +39 -0
  5. api/rest/dependencies.py +24 -0
  6. api/rest/exceptions.py +126 -0
  7. api/rest/node_registry.py +120 -0
  8. api/rest/routers/__init__.py +1 -0
  9. api/rest/routers/evaluate.py +345 -0
  10. api/rest/routers/health.py +54 -0
  11. api/rest/routers/import_export.py +421 -0
  12. api/rest/routers/nodes.py +295 -0
  13. api/rest/routers/query.py +197 -0
  14. api/rest/routers/relationships.py +221 -0
  15. api/rest/schemas.py +244 -0
  16. api/sfm_service.py +1780 -0
  17. data/__init__.py +30 -0
  18. data/importers/__init__.py +96 -0
  19. data/importers/base_adapter.py +164 -0
  20. data/importers/csv_adapter.py +345 -0
  21. data/importers/mapping_config.py +348 -0
  22. data/importers/oecd_adapter.py +323 -0
  23. data/importers/validators.py +201 -0
  24. data/importers/worldbank_adapter.py +340 -0
  25. data/neo4j_repository.py +854 -0
  26. data/repositories.py +652 -0
  27. graph/__init__.py +34 -0
  28. graph/converters.py +302 -0
  29. graph/exporters/__init__.py +15 -0
  30. graph/exporters/system_dynamics_exporter.py +284 -0
  31. graph/exporters/xlsx_exporter.py +320 -0
  32. graph/sfm_graph.py +174 -0
  33. graph/sfm_persistence.py +682 -0
  34. graph/sfm_query.py +1108 -0
  35. models/__init__.py +183 -0
  36. models/base_nodes.py +86 -0
  37. models/complex_analysis.py +620 -0
  38. models/cultural_analysis.py +244 -0
  39. models/delivery_matrix.py +383 -0
  40. models/economic_analysis.py +90 -0
  41. models/exceptions.py +518 -0
  42. models/institutional_analysis.py +90 -0
  43. models/matrix_components.py +592 -0
  44. models/meta_entities.py +189 -0
  45. models/methodological_framework.py +557 -0
  46. models/network_analysis.py +264 -0
  47. models/policy_framework.py +92 -0
  48. models/sfm_enums.py +4371 -0
  49. models/social_assessment.py +225 -0
  50. models/specialized_components.py +318 -0
  51. models/specialized_nodes.py +146 -0
  52. models/system_analysis.py +160 -0
  53. models/technology_integration.py +103 -0
  54. models/temporal_clocks.py +388 -0
  55. sfm_core-0.1.0.dist-info/METADATA +777 -0
  56. sfm_core-0.1.0.dist-info/RECORD +59 -0
  57. sfm_core-0.1.0.dist-info/WHEEL +5 -0
  58. sfm_core-0.1.0.dist-info/licenses/LICENSE +674 -0
  59. sfm_core-0.1.0.dist-info/top_level.txt +4 -0
api/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ API layer module for SFM Core.
3
+
4
+ Provides the service facade for Beta unified model operations.
5
+ """
6
+
7
+ from api.sfm_service import (
8
+ SFMService,
9
+ SFMServiceConfig,
10
+ ServiceHealth,
11
+ GraphStatistics,
12
+ )
13
+
14
+ __all__ = [
15
+ "SFMService",
16
+ "SFMServiceConfig",
17
+ "ServiceHealth",
18
+ "GraphStatistics",
19
+ ]
api/rest/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """REST API module for SFM Core."""
api/rest/app.py ADDED
@@ -0,0 +1,87 @@
1
+ """FastAPI application factory for SFM Core REST API."""
2
+
3
+ from fastapi import FastAPI
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.exceptions import RequestValidationError
6
+
7
+ from api.rest.config import settings
8
+ from api.rest.exceptions import (
9
+ sfm_exception_handler,
10
+ validation_exception_handler,
11
+ generic_exception_handler,
12
+ )
13
+ from models.exceptions import SFMError
14
+
15
+ # Import routers
16
+ from api.rest.routers import health, nodes, relationships, query, evaluate, import_export
17
+
18
+
19
+ def create_app() -> FastAPI:
20
+ """
21
+ Create and configure FastAPI application.
22
+
23
+ Returns:
24
+ Configured FastAPI application instance
25
+ """
26
+ app = FastAPI(
27
+ title=settings.PROJECT_NAME,
28
+ version=settings.PROJECT_VERSION,
29
+ debug=settings.DEBUG,
30
+ docs_url=f"{settings.API_V1_PREFIX}/docs",
31
+ openapi_url=f"{settings.API_V1_PREFIX}/openapi.json",
32
+ redoc_url=f"{settings.API_V1_PREFIX}/redoc",
33
+ description="REST API for Social Fabric Matrix (SFM) Core framework - "
34
+ "modeling, analyzing, and querying complex socio-economic systems",
35
+ )
36
+
37
+ # CORS middleware
38
+ app.add_middleware(
39
+ CORSMiddleware,
40
+ allow_origins=settings.CORS_ORIGINS,
41
+ allow_credentials=settings.CORS_ALLOW_CREDENTIALS,
42
+ allow_methods=settings.CORS_ALLOW_METHODS,
43
+ allow_headers=settings.CORS_ALLOW_HEADERS,
44
+ )
45
+
46
+ # Exception handlers
47
+ app.add_exception_handler(SFMError, sfm_exception_handler) # type: ignore[arg-type]
48
+ app.add_exception_handler(RequestValidationError, validation_exception_handler) # type: ignore[arg-type]
49
+ app.add_exception_handler(Exception, generic_exception_handler)
50
+
51
+ # Include routers
52
+ app.include_router(
53
+ health.router,
54
+ prefix=settings.API_V1_PREFIX,
55
+ tags=["Health & Diagnostics"]
56
+ )
57
+ app.include_router(
58
+ nodes.router,
59
+ prefix=f"{settings.API_V1_PREFIX}/nodes",
60
+ tags=["Nodes"]
61
+ )
62
+ app.include_router(
63
+ relationships.router,
64
+ prefix=f"{settings.API_V1_PREFIX}/relationships",
65
+ tags=["Relationships"]
66
+ )
67
+ app.include_router(
68
+ query.router,
69
+ prefix=f"{settings.API_V1_PREFIX}/query",
70
+ tags=["Query Analysis"]
71
+ )
72
+ app.include_router(
73
+ evaluate.router,
74
+ prefix=f"{settings.API_V1_PREFIX}/evaluate",
75
+ tags=["Evaluation"]
76
+ )
77
+ app.include_router(
78
+ import_export.router,
79
+ prefix=f"{settings.API_V1_PREFIX}/import",
80
+ tags=["Import/Export"]
81
+ )
82
+
83
+ return app
84
+
85
+
86
+ # Create application instance
87
+ app = create_app()
api/rest/config.py ADDED
@@ -0,0 +1,39 @@
1
+ """Configuration settings for SFM REST API."""
2
+
3
+ from typing import List
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
+
6
+
7
+ class APISettings(BaseSettings):
8
+ """API configuration settings loaded from environment variables."""
9
+
10
+ # API Settings
11
+ API_V1_PREFIX: str = "/api/v1"
12
+ PROJECT_NAME: str = "SFM Core REST API"
13
+ PROJECT_VERSION: str = "1.0.0"
14
+ DEBUG: bool = False
15
+
16
+ # CORS
17
+ CORS_ORIGINS: List[str] = ["*"]
18
+ CORS_ALLOW_CREDENTIALS: bool = True
19
+ CORS_ALLOW_METHODS: List[str] = ["*"]
20
+ CORS_ALLOW_HEADERS: List[str] = ["*"]
21
+
22
+ # Service Configuration
23
+ STORAGE_TYPE: str = "networkx"
24
+ GRAPH_SIZE_LIMIT: int = 10000
25
+
26
+ # Neo4j (optional, for production)
27
+ NEO4J_URI: str = "bolt://localhost:7687"
28
+ NEO4J_USERNAME: str = "neo4j"
29
+ NEO4J_PASSWORD: str = "password"
30
+
31
+ model_config = SettingsConfigDict(
32
+ env_file=".env",
33
+ env_file_encoding="utf-8",
34
+ case_sensitive=True
35
+ )
36
+
37
+
38
+ # Global settings instance
39
+ settings = APISettings()
@@ -0,0 +1,24 @@
1
+ """Dependency injection for REST API."""
2
+
3
+ from functools import lru_cache
4
+ from api.sfm_service import SFMService, SFMServiceConfig
5
+ from api.rest.config import settings
6
+
7
+
8
+ @lru_cache()
9
+ def get_sfm_service() -> SFMService:
10
+ """
11
+ Get or create singleton SFMService instance.
12
+
13
+ Uses lru_cache to ensure one instance per worker process.
14
+ Safe for NetworkX backend (thread-safe reads).
15
+ For Neo4j backend, connection pooling handles concurrency.
16
+
17
+ Returns:
18
+ SFMService instance configured from environment settings.
19
+ """
20
+ config = SFMServiceConfig(
21
+ storage_type=settings.STORAGE_TYPE,
22
+ graph_size_limit=settings.GRAPH_SIZE_LIMIT,
23
+ )
24
+ return SFMService(config=config)
api/rest/exceptions.py ADDED
@@ -0,0 +1,126 @@
1
+ """HTTP exception handlers for REST API.
2
+
3
+ Note: Some status codes use raw integers (422, 413) instead of named constants
4
+ to avoid deprecation warnings. The new constant names (HTTP_422_UNPROCESSABLE_CONTENT,
5
+ HTTP_413_CONTENT_TOO_LARGE) are not yet available in Starlette 1.1.0.
6
+ """
7
+
8
+ from datetime import datetime, timezone
9
+ from fastapi import Request, status
10
+ from fastapi.responses import JSONResponse
11
+ from fastapi.exceptions import RequestValidationError
12
+
13
+ from models.exceptions import (
14
+ SFMError,
15
+ ErrorCode,
16
+ )
17
+
18
+
19
+ # HTTP status code mapping for SFM error codes
20
+ ERROR_CODE_HTTP_STATUS = {
21
+ ErrorCode.NOT_FOUND_ERROR: status.HTTP_404_NOT_FOUND,
22
+ ErrorCode.VALIDATION_ERROR: status.HTTP_400_BAD_REQUEST,
23
+ ErrorCode.NODE_CREATION_ERROR: status.HTTP_400_BAD_REQUEST,
24
+ ErrorCode.NODE_UPDATE_ERROR: status.HTTP_400_BAD_REQUEST,
25
+ ErrorCode.NODE_DELETE_ERROR: status.HTTP_400_BAD_REQUEST,
26
+ ErrorCode.RELATIONSHIP_ERROR: status.HTTP_400_BAD_REQUEST,
27
+ ErrorCode.INTEGRITY_ERROR: status.HTTP_409_CONFLICT,
28
+ ErrorCode.QUERY_EXECUTION_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
29
+ ErrorCode.QUERY_TIMEOUT_ERROR: status.HTTP_504_GATEWAY_TIMEOUT,
30
+ ErrorCode.QUERY_SYNTAX_ERROR: status.HTTP_400_BAD_REQUEST,
31
+ ErrorCode.DATABASE_CONNECTION_ERROR: status.HTTP_503_SERVICE_UNAVAILABLE,
32
+ ErrorCode.DATABASE_TRANSACTION_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
33
+ ErrorCode.DATABASE_PERSISTENCE_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
34
+ ErrorCode.PERMISSION_DENIED_ERROR: status.HTTP_403_FORBIDDEN,
35
+ ErrorCode.SECURITY_VALIDATION_ERROR: status.HTTP_403_FORBIDDEN,
36
+ ErrorCode.GRAPH_SIZE_EXCEEDED: 413, # Raw int avoids deprecation (HTTP_413_CONTENT_TOO_LARGE not yet available)
37
+ ErrorCode.GRAPH_OPERATION_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR,
38
+ }
39
+
40
+
41
+ async def sfm_exception_handler(request: Request, exc: SFMError) -> JSONResponse:
42
+ """
43
+ Handle SFMError exceptions and convert to HTTP responses.
44
+
45
+ Maps SFM error codes to appropriate HTTP status codes and
46
+ returns structured error response.
47
+
48
+ Args:
49
+ request: FastAPI request object
50
+ exc: SFMError exception instance
51
+
52
+ Returns:
53
+ JSONResponse with error details and appropriate status code
54
+ """
55
+ # Get HTTP status code from error code, default to 500
56
+ http_status = ERROR_CODE_HTTP_STATUS.get(
57
+ exc.error_code,
58
+ status.HTTP_500_INTERNAL_SERVER_ERROR
59
+ )
60
+
61
+ # Build error response
62
+ error_response = {
63
+ "error": exc.error_code.value if hasattr(exc.error_code, "value") else str(exc.error_code),
64
+ "message": exc.message,
65
+ "context": exc.context.to_dict() if hasattr(exc.context, "to_dict") else {},
66
+ "remediation": exc.remediation,
67
+ "details": exc.details if hasattr(exc, "details") else None,
68
+ "timestamp": datetime.now(timezone.utc).isoformat(),
69
+ }
70
+
71
+ return JSONResponse(
72
+ status_code=http_status,
73
+ content=error_response
74
+ )
75
+
76
+
77
+ async def validation_exception_handler(
78
+ request: Request,
79
+ exc: RequestValidationError
80
+ ) -> JSONResponse:
81
+ """
82
+ Handle Pydantic validation errors.
83
+
84
+ Args:
85
+ request: FastAPI request object
86
+ exc: RequestValidationError exception
87
+
88
+ Returns:
89
+ JSONResponse with validation error details
90
+ """
91
+ return JSONResponse(
92
+ status_code=422, # Raw int avoids deprecation (HTTP_422_UNPROCESSABLE_CONTENT not yet available)
93
+ content={
94
+ "error": "VALIDATION_ERROR",
95
+ "message": "Request validation failed",
96
+ "context": {
97
+ "errors": exc.errors(),
98
+ "body": exc.body if hasattr(exc, "body") else None,
99
+ },
100
+ "timestamp": datetime.now(timezone.utc).isoformat(),
101
+ }
102
+ )
103
+
104
+
105
+ async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
106
+ """
107
+ Handle unexpected exceptions.
108
+
109
+ Args:
110
+ request: FastAPI request object
111
+ exc: Generic exception
112
+
113
+ Returns:
114
+ JSONResponse with generic error message
115
+ """
116
+ return JSONResponse(
117
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
118
+ content={
119
+ "error": "INTERNAL_SERVER_ERROR",
120
+ "message": "An unexpected error occurred",
121
+ "context": {
122
+ "exception_type": type(exc).__name__,
123
+ },
124
+ "timestamp": datetime.now(timezone.utc).isoformat(),
125
+ }
126
+ )
@@ -0,0 +1,120 @@
1
+ """Node type registry for API validation.
2
+
3
+ This module provides a centralized registry of all available SFM node types,
4
+ enabling validation and documentation of node_type parameters in API endpoints.
5
+ """
6
+
7
+ from typing import Set, Dict
8
+
9
+ # Complete registry of all Node subclasses from models/
10
+ # Organized by domain module for maintainability
11
+ NODE_TYPES: Dict[str, Set[str]] = {
12
+ "base": {
13
+ "Node", # Base class from models/base_nodes.py
14
+ },
15
+ "complex_analysis": {
16
+ "DigraphAnalysis",
17
+ "CircularCausationProcess",
18
+ "ConflictDetection",
19
+ },
20
+ "cultural_analysis": {
21
+ "CeremonialInstrumentalClassification",
22
+ "ValueSystem",
23
+ "SocialBelief",
24
+ "CulturalAttitude",
25
+ },
26
+ "economic_analysis": {
27
+ "TransactionCost",
28
+ "CoordinationMechanism",
29
+ "CommonsGovernance",
30
+ },
31
+ "institutional_analysis": {
32
+ "InstitutionalStructure",
33
+ "PathDependencyAnalysis",
34
+ },
35
+ "matrix_components": {
36
+ "MatrixCell",
37
+ "SFMCriteria",
38
+ "SFMMatrix",
39
+ },
40
+ "meta_entities": {
41
+ "Scenario",
42
+ "ScenarioSet",
43
+ "ScenarioPath",
44
+ },
45
+ "methodological_framework": {
46
+ "InstrumentalistInquiryFramework",
47
+ "NormativeSystemsAnalysis",
48
+ "PolicyRelevanceIntegration",
49
+ "DatabaseIntegrationCapability",
50
+ },
51
+ "network_analysis": {
52
+ "CrossImpactAnalysis",
53
+ "DeliveryRelationship",
54
+ "MatrixDeliveryNetwork",
55
+ },
56
+ "policy_framework": {
57
+ "PolicyInstrument",
58
+ "ValueJudgment",
59
+ "ProblemSolvingSequence",
60
+ },
61
+ "social_assessment": {
62
+ "SocialValueAssessment",
63
+ "SocialFabricIndicator",
64
+ "SocialCost",
65
+ },
66
+ "specialized_components": {
67
+ "SocialIndicatorSystem",
68
+ "EvolutionaryPathway",
69
+ "SocialProvisioningMatrix",
70
+ },
71
+ "system_analysis": {
72
+ "SystemProperty",
73
+ "SystemLevelAnalysis",
74
+ "InstitutionalHolarchy",
75
+ },
76
+ "technology_integration": {
77
+ "ToolSkillTechnologyComplex",
78
+ "EcologicalSystem",
79
+ },
80
+ }
81
+
82
+
83
+ def get_all_node_types() -> Set[str]:
84
+ """
85
+ Get set of all valid node type names.
86
+
87
+ Returns:
88
+ Set of all node type class names
89
+ """
90
+ all_types = set()
91
+ for types in NODE_TYPES.values():
92
+ all_types.update(types)
93
+ return all_types
94
+
95
+
96
+ def is_valid_node_type(node_type: str) -> bool:
97
+ """
98
+ Check if a node type is valid.
99
+
100
+ Args:
101
+ node_type: Node type name to validate
102
+
103
+ Returns:
104
+ True if node_type is in the registry
105
+ """
106
+ return node_type in get_all_node_types()
107
+
108
+
109
+ def get_node_types_by_domain() -> Dict[str, Set[str]]:
110
+ """
111
+ Get node types organized by domain module.
112
+
113
+ Returns:
114
+ Dictionary mapping domain module names to sets of node types
115
+ """
116
+ return NODE_TYPES.copy()
117
+
118
+
119
+ # Pre-computed set for fast validation
120
+ ALL_NODE_TYPES = get_all_node_types()
@@ -0,0 +1 @@
1
+ """REST API routers."""