georag 0.1.0__tar.gz

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.
georag-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: georag
3
+ Version: 0.1.0
4
+ Summary: Spatial-Semantic Retrieval Augmented Generation framework
5
+ Author-email: Developer <makindeadedeji500@gmail.com>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: django>=4.2
9
+ Requires-Dist: scikit-learn>=1.3.0
10
+ Requires-Dist: psycopg2-binary>=2.9
11
+ Requires-Dist: numpy>=1.24
12
+
13
+ # georag
georag-0.1.0/README.md ADDED
@@ -0,0 +1 @@
1
+ # georag
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "georag"
3
+ version = "0.1.0"
4
+ description = "Spatial-Semantic Retrieval Augmented Generation framework"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Developer", email = "makindeadedeji500@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "django>=4.2",
12
+ "scikit-learn>=1.3.0",
13
+ "psycopg2-binary>=2.9",
14
+ "numpy>=1.24"
15
+ ]
georag-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,54 @@
1
+ from typing import Dict, Any
2
+ from .parsers import GeoParser
3
+ from .retrievers import SpatialVectorRetriever
4
+ from .cluster import SpatialRouter
5
+
6
+ class GeoRAGPipeline:
7
+ """
8
+ The main orchestration engine for the GeoRAG framework.
9
+ """
10
+ def __init__(self, chunk_model, max_clusters: int = 3):
11
+ self.chunk_model = chunk_model
12
+ self.retriever = SpatialVectorRetriever(chunk_model)
13
+ self.router = SpatialRouter(max_clusters=max_clusters)
14
+
15
+ def generate_context(
16
+ self,
17
+ query_embedding: list[float],
18
+ target_longitude: float,
19
+ target_latitude: float,
20
+ radius_km: float,
21
+ top_k: int = 10
22
+ ) -> str:
23
+ """
24
+ Executes the full pipeline and formats the results into an LLM-ready context string.
25
+ """
26
+ # 1. Parse coordinates into PostGIS format
27
+ target_point = GeoParser.point_from_coords(target_longitude, target_latitude)
28
+
29
+ # 2. Run the two-pass spatial-semantic search
30
+ results = self.retriever.hybrid_search(
31
+ query_embedding=query_embedding,
32
+ target_point=target_point,
33
+ radius_km=radius_km,
34
+ top_k=top_k
35
+ )
36
+
37
+ # 3. Route and cluster the data geographically
38
+ clustered_data = self.router.cluster_results(results)
39
+
40
+ # 4. Synthesize the context string
41
+ if not clustered_data:
42
+ return "No relevant geospatial data found for this query."
43
+
44
+ context_string = "Geospatial Data Context:\n"
45
+ for region_id, data in clustered_data.items():
46
+ lon, lat = data['centroid_lon_lat']
47
+ context_string += f"\n--- {region_id} (Approx. Center: {lon:.4f} Lon, {lat:.4f} Lat) ---\n"
48
+ for excerpt in data['excerpts']:
49
+ context_string += f"- {excerpt}\n"
50
+
51
+ return context_string
52
+
53
+ # Expose key components at the package level
54
+ __all__ = ['GeoRAGPipeline', 'GeoParser']
@@ -0,0 +1,47 @@
1
+ import numpy as np
2
+ from sklearn.cluster import KMeans
3
+
4
+ class SpatialRouter:
5
+ """
6
+ Groups retrieved geographic chunks into logical spatial clusters
7
+ to provide structured context for the LLM.
8
+ """
9
+ def __init__(self, max_clusters: int = 3):
10
+ self.max_clusters = max_clusters
11
+
12
+ def cluster_results(self, chunks) -> dict:
13
+ """
14
+ Takes a QuerySet of retrieved chunk objects and groups them geographically.
15
+ Returns a dictionary mapping cluster IDs to their centroid coordinates and text.
16
+ """
17
+ # Convert QuerySet to a list to avoid multiple database hits
18
+ chunk_list = list(chunks)
19
+
20
+ if not chunk_list:
21
+ return {}
22
+
23
+ # Extract coordinates [longitude, latitude] for the clustering algorithm
24
+ coords = np.array([[chunk.geometry.x, chunk.geometry.y] for chunk in chunk_list])
25
+
26
+ # We can't have more clusters than we have data points
27
+ n_clusters = min(self.max_clusters, len(chunk_list))
28
+
29
+ # Initialize and run Scikit-Learn K-Means
30
+ kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
31
+ labels = kmeans.fit_predict(coords)
32
+
33
+ grouped_data = {}
34
+ for idx, label in enumerate(labels):
35
+ cluster_id = f"Region_{label + 1}"
36
+
37
+ if cluster_id not in grouped_data:
38
+ # Store the geometric center of this cluster
39
+ centroid = kmeans.cluster_centers_[label].tolist()
40
+ grouped_data[cluster_id] = {
41
+ "centroid_lon_lat": centroid,
42
+ "excerpts": []
43
+ }
44
+
45
+ grouped_data[cluster_id]["excerpts"].append(chunk_list[idx].text)
46
+
47
+ return grouped_data
@@ -0,0 +1,38 @@
1
+ from django.contrib.gis.db import models as gis_models
2
+ from django.db import models
3
+ from pgvector.django import VectorField, HnswIndex
4
+ from django.contrib.postgres.indexes import GistIndex
5
+
6
+ class GeoRAGDocumentBase(models.Model):
7
+ """
8
+ Abstract base model representing a parent document.
9
+ """
10
+ title = models.CharField(max_length=255)
11
+ metadata = models.JSONField(default=dict, blank=True)
12
+ created_at = models.DateTimeField(auto_now_add=True)
13
+
14
+ class Meta:
15
+ abstract = True
16
+
17
+ class GeoRAGChunkBase(models.Model):
18
+ """
19
+ Abstract base model representing a text chunk with spatial and semantic data.
20
+ """
21
+ text = models.TextField()
22
+ embedding = VectorField(dimensions=768, null=True, blank=True)
23
+ geometry = gis_models.GeometryField(srid=4326, null=True, blank=True)
24
+
25
+ class Meta:
26
+ abstract = True
27
+ indexes = [
28
+ # Optimize spatial queries (radius, intersection)
29
+ GistIndex(fields=['geometry'], name='%(class)s_geom_idx'),
30
+ # Optimize semantic vector search (cosine distance)
31
+ HnswIndex(
32
+ name='%(class)s_vector_idx',
33
+ fields=['embedding'],
34
+ m=16,
35
+ ef_construction=64,
36
+ opclasses=['vector_cosine_ops']
37
+ )
38
+ ]
@@ -0,0 +1,42 @@
1
+ import json
2
+ from django.contrib.gis.geos import GEOSGeometry, Point
3
+
4
+ class GeoParser:
5
+ """
6
+ Handles the conversion of raw coordinates or GeoJSON into PostGIS-compatible geometries.
7
+ """
8
+ @staticmethod
9
+ def point_from_coords(longitude: float, latitude: float) -> Point:
10
+ """
11
+ Creates a standard PostGIS Point geometry.
12
+ CRITICAL: In spatial databases, Longitude (X) always comes before Latitude (Y).
13
+ """
14
+ # SRID 4326 is the standard GPS coordinate system (WGS 84)
15
+ return Point(longitude, latitude, srid=4326)
16
+
17
+ @staticmethod
18
+ def parse_geojson(geojson_dict: dict) -> GEOSGeometry:
19
+ """
20
+ Converts a standard GeoJSON boundary (like a polygon of an oil spill) into PostGIS geometry.
21
+ """
22
+ return GEOSGeometry(json.dumps(geojson_dict))
23
+
24
+
25
+ class DocumentChunker:
26
+ """
27
+ Splits large documents into smaller chunks for vector embedding,
28
+ preparing them to be tagged with spatial metadata.
29
+ """
30
+ def __init__(self, chunk_size: int = 250):
31
+ self.chunk_size = chunk_size
32
+
33
+ def chunk_text(self, text: str) -> list[str]:
34
+ """
35
+ A robust word-based chunker.
36
+ """
37
+ words = text.split()
38
+ chunks = []
39
+ for i in range(0, len(words), self.chunk_size):
40
+ chunk = " ".join(words[i:i + self.chunk_size])
41
+ chunks.append(chunk)
42
+ return chunks
@@ -0,0 +1,36 @@
1
+ from django.contrib.gis.geos import Point
2
+ from django.contrib.gis.measure import Distance
3
+ from pgvector.django import CosineDistance
4
+ from django.db.models import QuerySet
5
+
6
+ class SpatialVectorRetriever:
7
+ """
8
+ Executes the two-pass GeoRAG algorithm combining PostGIS spatial boundaries
9
+ with pgvector semantic cosine similarity.
10
+ """
11
+ def __init__(self, chunk_model):
12
+ """
13
+ Initializes the retriever with the specific Django model to query.
14
+ """
15
+ self.chunk_model = chunk_model
16
+
17
+ def hybrid_search(
18
+ self,
19
+ query_embedding: list[float],
20
+ target_point: Point,
21
+ radius_km: float,
22
+ top_k: int = 5
23
+ ) -> QuerySet:
24
+ """
25
+ Pass 1: Filter database by geographic radius using GiST indexes.
26
+ Pass 2: Calculate cosine distance using HNSW indexes on the filtered subset.
27
+ """
28
+ return (
29
+ self.chunk_model.objects
30
+ # Spatial Pre-Filter: Only keep chunks within the target radius
31
+ .filter(geometry__distance_lte=(target_point, Distance(km=radius_km)))
32
+ # Semantic Search: Calculate cosine distance between chunk and query
33
+ .annotate(semantic_distance=CosineDistance('embedding', query_embedding))
34
+ # Sort by the most semantically relevant
35
+ .order_by('semantic_distance')[:top_k]
36
+ )
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: georag
3
+ Version: 0.1.0
4
+ Summary: Spatial-Semantic Retrieval Augmented Generation framework
5
+ Author-email: Developer <makindeadedeji500@gmail.com>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: django>=4.2
9
+ Requires-Dist: scikit-learn>=1.3.0
10
+ Requires-Dist: psycopg2-binary>=2.9
11
+ Requires-Dist: numpy>=1.24
12
+
13
+ # georag
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/georag/__init__.py
4
+ src/georag/cluster.py
5
+ src/georag/models.py
6
+ src/georag/parsers.py
7
+ src/georag/retrievers.py
8
+ src/georag.egg-info/PKG-INFO
9
+ src/georag.egg-info/SOURCES.txt
10
+ src/georag.egg-info/dependency_links.txt
11
+ src/georag.egg-info/requires.txt
12
+ src/georag.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ django>=4.2
2
+ scikit-learn>=1.3.0
3
+ psycopg2-binary>=2.9
4
+ numpy>=1.24
@@ -0,0 +1 @@
1
+ georag