1- # ABOUTME: Temporal activities for exporting pixel coverage data to ODK entity lists
2- # ABOUTME: Handles fetching pixel data and creating entities via Ona API
1+ # ABOUTME: Temporal activities for exporting coverage data to ODK entity lists
2+ # ABOUTME: Handles fetching pixel and location data and creating entities via Ona API
33
44from temporalio import activity
55from typing import List , Dict , Any
@@ -85,7 +85,7 @@ async def fetch_pixel_coverage_activity(
8585 p.adm4_pcode,
8686 pc.rounds
8787 FROM coverage_pixel pc
88- JOIN pixels p ON p.quadkey = pc.quadkey AND p.campaign_id = pc.campaign_id
88+ JOIN pixels p ON p.quadkey = pc.quadkey
8989 WHERE pc.campaign_id = %s
9090 AND pc.indicator_id = %s
9191 AND pc.rounds && %s::integer[]
@@ -111,19 +111,84 @@ async def fetch_pixel_coverage_activity(
111111 return_db_connection (conn )
112112
113113
114+ @activity .defn
115+ async def fetch_location_coverage_activity (
116+ campaign_id : str ,
117+ indicator_id : str ,
118+ round_ids : List [str ]
119+ ) -> List [Dict [str , Any ]]:
120+ """
121+ Fetch location coverage data filtered by selected rounds.
122+
123+ Returns list of location coverage records with location details.
124+ """
125+ conn = get_db_connection ()
126+ cursor = conn .cursor ()
127+
128+ try :
129+ # Get round numbers from round IDs
130+ placeholders = ',' .join (['%s' ] * len (round_ids ))
131+ cursor .execute (f"""
132+ SELECT round_number
133+ FROM rounds
134+ WHERE id IN ({ placeholders } )
135+ """ , tuple (round_ids ))
136+
137+ round_numbers = [row [0 ] for row in cursor .fetchall ()]
138+
139+ if not round_numbers :
140+ return []
141+
142+ cursor .execute ("""
143+ SELECT DISTINCT
144+ c.id,
145+ l.external_id,
146+ l.latitude,
147+ l.longitude,
148+ l.quadkey,
149+ c.rounds
150+ FROM coverage c
151+ JOIN locations l ON l.id = c.location_id
152+ WHERE c.campaign_id = %s
153+ AND c.indicator_id = %s
154+ AND c.rounds && %s::integer[]
155+ ORDER BY l.external_id
156+ """ , (campaign_id , indicator_id , round_numbers ))
157+
158+ locations = []
159+ for row in cursor .fetchall ():
160+ cov_id , external_id , latitude , longitude , quadkey , rounds = row
161+ locations .append ({
162+ 'id' : str (cov_id ),
163+ 'external_id' : external_id or '' ,
164+ 'latitude' : float (latitude ) if latitude else None ,
165+ 'longitude' : float (longitude ) if longitude else None ,
166+ 'quadkey' : quadkey or '' ,
167+ 'rounds' : rounds or []
168+ })
169+
170+ return locations
171+
172+ finally :
173+ cursor .close ()
174+ return_db_connection (conn )
175+
176+
114177@activity .defn
115178async def create_odk_entity_activity (
116179 project_id : str ,
117- pixel_data : Dict [str , Any ],
118- geometry_type : str = 'centroid'
180+ entity_data : Dict [str , Any ],
181+ geometry_type : str = 'centroid' ,
182+ entity_type : str = 'pixel'
119183) -> Dict [str , Any ]:
120184 """
121185 Create a single ODK entity via Ona API.
122186
123187 Args:
124188 project_id: Project ID to get ODK credentials
125- pixel_data: Pixel data dict with id, quadkey, lat, lng, adm4_pcode
189+ entity_data: Entity data dict (pixel or location fields)
126190 geometry_type: 'centroid' or 'boundary' - how to represent pixel geometry
191+ entity_type: 'pixel' or 'location'
127192
128193 Returns:
129194 Dict with success status and created entity info
@@ -154,21 +219,27 @@ async def create_odk_entity_activity(
154219 # Remove trailing slash from host_url
155220 host_url = host_url .rstrip ('/' )
156221
157- # Format geometry based on project setting
158- if geometry_type == 'boundary' :
159- # Use pixel boundary (geoshape polygon)
160- geometry = get_pixel_boundary_coords (pixel_data ['quadkey' ])
222+ if entity_type == 'location' :
223+ # Locations always use point geometry
224+ label = entity_data ['external_id' ]
225+ geometry = f"{ entity_data ['latitude' ]} { entity_data ['longitude' ]} 0 0"
226+ details = entity_data ['quadkey' ]
161227 else :
162- # Use pixel centroid (geopoint)
163- geometry = f"{ pixel_data ['latitude' ]} { pixel_data ['longitude' ]} 0 0"
228+ # Pixels use configurable geometry
229+ label = entity_data ['quadkey' ]
230+ if geometry_type == 'boundary' :
231+ geometry = get_pixel_boundary_coords (entity_data ['quadkey' ])
232+ else :
233+ geometry = f"{ entity_data ['latitude' ]} { entity_data ['longitude' ]} 0 0"
234+ details = entity_data ['adm4_pcode' ]
164235
165236 # Build entity payload
166237 entity_payload = {
167- 'label' : pixel_data [ 'quadkey' ] ,
238+ 'label' : label ,
168239 'data' : {
169240 'geometry' : geometry ,
170241 'status' : 'not_visited' ,
171- 'details' : pixel_data [ 'adm4_pcode' ]
242+ 'details' : details
172243 }
173244 }
174245
@@ -199,7 +270,7 @@ async def create_odk_entity_activity(
199270
200271 return {
201272 'success' : True ,
202- 'quadkey ' : pixel_data [ 'quadkey' ] ,
273+ 'label ' : label ,
203274 'entity_uuid' : entity_result .get ('uuid' )
204275 }
205276
0 commit comments