fio-l1-paris-kml-pois
Map (reference ← swipe → agent)
0:00
A colleague handed me `paris_late_night_pois`. Our internal map server only takes flat GeoJSON or GeoParquet, so please convert it to `paris_pois.geojson`, one row per placemark. Keep `name`, include the source's classification/grouping as a `category` column, and pull each entry's 'last verified' date out of the HTML blurb into a `verified_date` column as an ISO date. We want to flag stale records.
0:00
System info
{
"adapter": "openrouter",
"model": "google/gemma-4-26b-a4b-it",
"base_url": "https://openrouter.ai/api/v1"
}
0:01
Bash
ls -F
ls -F
paris_late_night_pois.kml
0:06
Bash
file paris_late_night_pois.kml
file paris_late_night_pois.kml
bash: line 1: file: command not found
0:10
Write
/work/check_kml.py
content · 9 lines · py
import geopandas as gpd
try:
gdf = gpd.read_file('paris_late_night_pois.kml')
print(gdf.head())
print(gdf.columns)
except Exception as e:
print(f"Error: {e}")
Wrote /work/check_kml.py (176 chars).
0:14
Bash
python /work/check_kml.py
python /work/check_kml.py
id ... geometry
0 None ... POINT Z (2.32468 48.85416 0)
1 None ... POINT Z (2.32977 48.87058 0)
2 None ... POINT Z (2.34136 48.88076 0)
3 None ... POINT Z (2.37663 48.86626 0)
4 None ... POINT Z (2.37625 48.85317 0)
[5 rows x 13 columns]
Index(['id', 'Name', 'description', 'timestamp', 'begin', 'end',
'altitudeMode', 'tessellate', 'extrude', 'visibility', 'drawOrder',
'icon', 'geometry'],
dtype='str')
/usr/local/lib/python3.14/site-packages/pyogrio/geopandas.py:382: UserWarning: More than one layer found in 'paris_late_night_pois.kml': 'Cafés ouverts tard' (default), 'Bibliothèques de nuit', 'Tours et infos touristiques'. Specify layer parameter to avoid this warning. result = read_func(
0:53
Write
/work/convert_kml.py
content · 69 lines · py
import geopandas as gpd
import pandas as pd
import re
from datetime import datetime
def extract_date(text):
if not isinstance(text, str):
return None
# Look for patterns like "last verified: YYYY-MM-DD" or "vérifié le YYYY-MM-DD"
# Since it's a "blurb", let's try to find any date-like string.
# Common formats: 2023-01-01, 01/01/2023, etc.
# The prompt says "pull each entry's 'last verified' date out of the HTML blurb".
# Let's look for "last verified: <date>" or "vérifié le <date>"
match = re.search(r'(?:last verified:|vérifié le)\s*([\d\-/]+)', text, re.IGNORECASE)
if match:
date_str = match.group(1)
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y'):
try:
return datetime.strptime(date_str, fmt).date().isoformat()
except ValueError:
continue
return None
# We need to process all layers because the warning indicated multiple layers.
# Layers: 'Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques'
layers = ['Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques']
all_data = []
for layer in layers:
print(f"Processing layer: {layer}")
gdf_layer = gpd.read_file('paris_late_night_pois.kml', layer=layer)
# Convert to a standard format
# We need: name, category, verified_date, and geometry
# Input columns: Name, description, etc.
df = gdf_layer.copy()
# Create a temporary dataframe to store the processed rows
processed_rows = []
for _, row in df.iterrows():
name = row.get('Name', '')
description = row.get('description', '')
category = layer
verified_date = extract_date(description)
processed_rows.append({
'name': name,
'category': category,
'verified_date': verified_date,
'geometry': row['geometry']
})
all_data.append(pd.DataFrame(processed_rows))
# Combine all layers
final_df = pd.concat(all_data, ignore_index=True)
final_gdf = gpd.GeoDataFrame(final_df, geometry='geometry', crs=gdf_layer.crs)
# Ensure geometry is 2D if it's Point Z
final_gdf.geometry = final_gdf.geometry.map(lambda p: geometry_to_2d(p))
# Save to GeoJSON
final_gdf.to_file('paris_pois.geojson', driver='GeoJSON')
print("Conversion complete. Saved to paris_pois.geojson")
Wrote /work/convert_kml.py (2411 chars).
1:18
Write
/work/convert_kml.py
content · 90 lines · py
import geopandas as gpd
import pandas as pd
import re
from datetime import datetime
from shapely.geometry import Point
def geometry_to_2d(geom):
if geom.has_z:
return Point(geom.x, geom.y)
return geom
def extract_date(text):
if not isinstance(text, str):
return None
# Let's see what's in the description first.
# Usually, "last verified: 2023-05-10" or similar.
# The prompt says "last verified" date.
# It might be in HTML, so maybe <<pp>Last verified: 2023-05-10</p>
# Search for common patterns
# 1. last verified: YYYY-MM-DD
# 2. last verified: DD/MM/YYYY
# 3. Last verified on YYYY-MM-DD
patterns = [
r'last verified:\s*([\d\-/]+)',
r'vérifié le\s*([\d\-/]+)',
r'last verified on\s*([\d\-/]+)'
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
date_str = match.group(1)
# Try different date formats
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y'):
try:
return datetime.strptime(date_str, fmt).date().isoformat()
except ValueError:
continue
return None
layers = ['Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques']
all_data = []
for layer in layers:
print(f"Processing layer: {layer}")
try:
gdf_layer = gpd.read_file('paris_late_night_pois.kml', layer=layer)
except Exception as e:
print(f"Error reading layer {layer}: {e}")
continue
df = gdf_layer.copy()
processed_rows = []
for _, row in df.iterrows():
name = row.get('Name', '')
description = row.get('description', '')
category = layer
verified_date = extract_date(description)
processed_rows.append({
'name': name,
'category': category,
'verified_date': verified_date,
'geometry': row['geometry']
})
if processed_rows:
all_data.append(pd.DataFrame(processed_rows))
if all_data:
final_df = pd.concat(all_data, ignore_index=True)
final_gdf = gpd.GeoDataFrame(final_df, geometry='geometry')
# Ensure we have a CRS. If none, default to WGS84 as is common for KML
if final_gdf.crs is None:
final_gdf.set_crs(epsg=4326, inplace=True)
# Convert to 2D geometry (remove Z)
final_gdf.geometry = final_gdf.geometry.map(geometry_to_2d)
# Save to GeoJSON
final_gdf.to_file('paris_pois.geojson', driver='GeoJSON')
print("Conversion complete. Saved to paris_pois.geojson")
print(final_gdf.head())
else:
print("No data found.")
Wrote /work/convert_kml.py (2733 chars).
1:21
Bash
python /work/convert_kml.py
python /work/convert_kml.py
Processing layer: Cafés ouverts tard
Processing layer: Bibliothèques de nuit
Processing layer: Tours et infos touristiques
Conversion complete. Saved to paris_pois.geojson
name ... geometry
0 % Arabica ... POINT (2.32468 48.85416)
1 1T. rue Scribe ... POINT (2.32977 48.87058)
2 1ndix Café ... POINT (2.34136 48.88076)
3 2 Steps Café ... POINT (2.37663 48.86626)
4 3234 Café - Trente Deux Trente Quatre ... POINT (2.37625 48.85317)
[5 rows x 4 columns]
1:25
Bash
head -n 20 paris_pois.geojson
head -n 20 paris_pois.geojson
{
"type": "FeatureCollection",
"name": "paris_pois",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "name": "% Arabica", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.324676, 48.854156 ] } },
{ "type": "Feature", "properties": { "name": "1T. rue Scribe", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.329774, 48.870581 ] } },
{ "type": "Feature", "properties": { "name": "1ndix Café", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.341361, 48.880764 ] } },
{ "type": "Feature", "properties": { "name": "2 Steps Café", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.376626, 48.866261 ] } },
{ "type": "Feature", "properties": { "name": "3234 Café - Trente Deux Trente Quatre", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.376249, 48.853167 ] } },
{ "type": "Feature", "properties": { "name": "360 Cafe", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.321838, 48.842293 ] } },
{ "type": "Feature", "properties": { "name": "3w Cafe Paris", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.32753, 48.85156 ] } },
{ "type": "Feature", "properties": { "name": "48 Collagen Caf√©", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.34046, 48.87484 ] } },
{ "type": "Feature", "properties": { "name": "86 Champs Elysées", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.303767, 48.871387 ] } },
{ "type": "Feature", "properties": { "name": "A la Ville de Paris", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.34428, 48.87053 ] } },
{ "type": "Feature", "properties": { "name": "A minima cafe", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.38129, 48.8453 ] } },
{ "type": "Feature", "properties": { "name": "AD DictCafé", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.390577, 48.849002 ] } },
{ "type": "Feature", "properties": { "name": "ANGIOLO", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.331682, 48.854538 ] } },
{ "type": "Feature", "properties": { "name": "Abaita", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.368446, 48.864406 ] } },
{ "type": "Feature", "properties": { "name": "Absinthe Café", "category": "Cafés ouverts tard", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.357228, 48.865564 ] } },
1:59
Write
/work/convert_kml.py
content · 94 lines · py
import geopandas as gpd
import pandas as pd
import re
from datetime import datetime
from shapely.geometry import Point
def geometry_to_2d(geom):
if geom.has_z:
return Point(geom.x, geom.y)
return geom
def extract_date(text):
if not isinstance(text, str):
return None
# Try to find the date in various formats.
# Since we don't know the exact HTML structure, we look for "last verified" or "vérifié le"
# or just any date that follows a similar phrasing.
# Clean the HTML a bit to make searching easier
clean_text = re.sub(r'<<[^[^>]+>', ' ', text)
# Look for patterns: "last verified: 2023-01-01", "last verified 2023-01-01",
# "vérifié le 01/01/2023", etc.
patterns = [
r'(?:last\s+verified|vérifié\s+le)[:\s]+([\d\-/]{8,10})',
r'([\d\-/]{8,10})' # Fallback: just look for any date-like string if the previous fails
]
for pattern in patterns:
match = re.search(pattern, clean_text, re.IGNORECASE)
if match:
date_str = match.group(1).strip()
# Try to parse the date
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d-%m-%Y'):
try:
return datetime.strptime(date_str, fmt).date().isoformat()
except ValueError:
continue
return None
layers = ['Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques']
all_data = []
for layer in layers:
print(f"Processing layer: {layer}")
try:
gdf_layer = gpd.read_file('paris_late_night_pois.kml', layer=layer)
except Exception as e:
print(f"Error reading layer {layer}: {e}")
continue
df = gdf_layer.copy()
processed_rows = []
for _, row in df.iterrows():
name = row.get('Name', '')
description = row.get('description', '')
category = layer
verified_date = extract_date(description)
processed_rows.append({
'name': name,
'category': category,
'verified_date': verified_date,
'geometry': row['geometry']
})
if processed_rows:
all_data.append(pd.DataFrame(processed_rows))
if all_data:
final_df = pd.concat(all_data, ignore_index=True)
final_gdf = gpd.GeoDataFrame(final_df, geometry='geometry')
if final_gdf.crs is None:
final_gdf.set_crs(epsg=4326, inplace=True)
final_gdf.geometry = final_gdf.geometry.map(geometry_to_2d)
# Save to GeoJSON
final_gdf.to_file('paris_pois.geojson', driver='GeoJSON')
print("Conversion complete. Saved to paris_pois.geojson")
print("Sample of processed data:")
print(final_gdf[['name', 'category', 'verified_date']].head(20))
# Check if any dates were actually found
found_dates = final_gdf['verified_date'].notnull().sum()
print(f"\nTotal records: {len(final_gdf)}")
print(f"Records with verified_date: {found_dates}")
else:
print("No data found.")
Wrote /work/convert_kml.py (3050 chars).
2:01
Bash
python /work/convert_kml.py
python /work/convert_kml.py
Processing layer: Cafés ouverts tard
Processing layer: Bibliothèques de nuit
Processing layer: Tours et infos touristiques
Conversion complete. Saved to paris_pois.geojson
Sample of processed data:
name category verified_date
0 % Arabica Cafés ouverts tard 2026-01-01
1 1T. rue Scribe Cafés ouverts tard 2026-08-14
2 1ndix Café Cafés ouverts tard 2026-03-27
3 2 Steps Café Cafés ouverts tard 2026-10-12
4 3234 Café - Trente Deux Trente Quatre Cafés ouverts tard 2026-05-25
5 360 Cafe Cafés ouverts tard 2026-12-10
6 3w Cafe Paris Cafés ouverts tard 2026-07-23
7 48 Collagen Caf√© Cafés ouverts tard 2026-02-08
8 86 Champs Elysées Cafés ouverts tard 2026-09-21
9 A la Ville de Paris Cafés ouverts tard 2026-04-06
10 A minima cafe Cafés ouverts tard 2026-11-19
11 AD DictCafé Cafés ouverts tard 2026-06-04
12 ANGIOLO Cafés ouverts tard 2026-01-17
13 Abaita Cafés ouverts tard 2026-08-02
14 Absinthe Café Cafés ouverts tard 2026-03-15
15 Acai & You Cafés ouverts tard 2026-10-28
16 Adulis Cafe Abyssinien Cafés ouverts tard 2026-05-13
17 After Place Cafés ouverts tard 2026-12-26
18 After Place Cafés ouverts tard 2026-07-11
19 After Place Cafés ouverts tard 2026-02-24
Total records: 45
Records with verified_date: 44
2:49
Write
/work/convert_kml.py
content · 90 lines · py
import geopandas as gpd
import pandas as pd
import re
from datetime import datetime
from shapely.geometry import Point
def geometry_to_2d(geom):
if geom.has_z:
return Point(geom.x, geom.y)
return geom
def extract_date(text):
if not isinstance(text, str):
return None
# The previous run showed dates like 2026-01-01 being extracted.
# It seems the pattern works, but we should be careful about HTML tags.
# Let's clean the HTML more robustly.
clean_text = re.sub(r'<[^>]+>', ' ', text)
# Look for "last verified: YYYY-MM-DD" or "vérifié le YYYY-MM-DD"
# Using more specific patterns to avoid accidental matches.
patterns = [
r'(?:last\s+verified|vérifié\s+le)[:\s]+([\d\-/]{8,10})',
]
for pattern in patterns:
match = re.search(pattern, clean_text, re.IGNORECASE)
if match:
date_str = match.group(1).strip()
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d-%m-%Y'):
try:
return datetime.strptime(date_str, fmt).date().isoformat()
except ValueError:
continue
return None
layers = ['Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques']
all_data = []
for layer in layers:
print(f"Processing layer: {layer}")
try:
gdf_layer = gpd.read_file('paris_late_night_pois.kml', layer=layer)
except Exception as e:
print(f"Error reading layer {layer}: {e}")
continue
df = gdf_layer.copy()
processed_rows = []
for _, row in df.iterrows():
name = row.get('Name', '')
description = row.get('description', '')
category = layer
verified_date = extract_date(description)
processed_rows.append({
'name': name,
'category': category,
'verified_date': verified_date,
'geometry': row['geometry']
})
if processed_rows:
all_data.append(pd.DataFrame(processed_rows))
if all_data:
final_df = pd.concat(all_data, ignore_index=True)
final_gdf = gpd.GeoDataFrame(final_df, geometry='geometry')
if final_gdf.crs is None:
final_gdf.set_crs(epsg=4326, inplace=True)
final_gdf.geometry = final_gdf.geometry.map(geometry_to_2d)
# Save to GeoJSON
final_gdf.to_file('paris_pois.geojson', driver='GeoJSON')
print("Conversion complete. Saved to paris_pois.geojson")
print("Sample of processed data:")
print(final_gdf[['name', 'category', 'verified_date']].head(20))
# Check if any dates were actually found
found_dates = final_gdf['verified_date'].notnull().sum()
print(f"\nTotal records: {len(final_gdf)}")
print(f"Records with verified_date: {found_dates}")
else:
print("No data found.")
Wrote /work/convert_kml.py (2860 chars).
2:51
Bash
python /work/convert_kml.py
python /work/convert_kml.py
Processing layer: Cafés ouverts tard
Processing layer: Bibliothèques de nuit
Processing layer: Tours et infos touristiques
Conversion complete. Saved to paris_pois.geojson
Sample of processed data:
name category verified_date
0 % Arabica Cafés ouverts tard None
1 1T. rue Scribe Cafés ouverts tard None
2 1ndix Café Cafés ouverts tard None
3 2 Steps Café Cafés ouverts tard None
4 3234 Café - Trente Deux Trente Quatre Cafés ouverts tard None
5 360 Cafe Cafés ouverts tard None
6 3w Cafe Paris Cafés ouverts tard None
7 48 Collagen Caf√© Cafés ouverts tard None
8 86 Champs Elysées Cafés ouverts tard None
9 A la Ville de Paris Cafés ouverts tard None
10 A minima cafe Cafés ouverts tard None
11 AD DictCafé Cafés ouverts tard None
12 ANGIOLO Cafés ouverts tard None
13 Abaita Cafés ouverts tard None
14 Absinthe Café Cafés ouverts tard None
15 Acai & You Cafés ouverts tard None
16 Adulis Cafe Abyssinien Cafés ouverts tard None
17 After Place Cafés ouverts tard None
18 After Place Cafés ouverts tard None
19 After Place Cafés ouverts tard None
Total records: 45
Records with verified_date: 0
3:27
Write
/work/convert_kml.py
content · 111 lines · py
import geopandas as gpd
import pandas as pd
import re
from datetime import datetime
from shapely.geometry import Point
def geometry_to_2d(geom):
if geom.has_z:
return Point(geom.x, geom.y)
return geom
def extract_date(text):
if not isinstance(text, str):
return None
# Print a sample for debugging if needed
# print(f"DEBUG description: {text[:100]}")
# Remove HTML tags
clean_text = re.sub(r'<<[^[^>]+>', ' ', text)
# Let's try to find any date-like thing in the text if specific phrases fail
# Format: YYYY-MM-DD or DD/MM/YYYY
# Pattern 1: Specific phrases
patterns = [
r'(?:last\s+verified|vérifié\s+le)[:\s]+([\d\-/]{8,10})',
]
for pattern in patterns:
match = re.search(pattern, clean_text, re.IGNORECASE)
if match:
date_str = match.group(1).strip()
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d-%m-%Y'):
try:
return datetime.strptime(date_str, fmt).date().isoformat()
except ValueError:
continue
# Pattern 2: Fallback - any date-like string in the text
# This is more aggressive.
fallback_pattern = r'([\d\-/]{8,10})'
match = re.search(fallback_pattern, clean_text)
if match:
date_str = match.group(1).strip()
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d-%m-%Y'):
try:
return datetime.strptime(date_str, fmt).date().isoformat()
except ValueError:
continue
return None
layers = ['Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques']
all_data = []
# Read KML once to inspect a few descriptions if none are found
gdf_test = gpd.read_file('paris_late_night_pois.kml', layer=layers[0])
print("Sample description from first layer:")
print(gdf_test['description'].iloc[0])
for layer in layers:
print(f"Processing layer: {layer}")
try:
gdf_layer = gpd.read_file('paris_late_night_pois.kml', layer=layer)
except Exception as e:
print(f"Error reading layer {layer}: {e}")
continue
df = gdf_layer.copy()
processed_rows = []
for _, row in df.iterrows():
name = row.get('Name', '')
description = row.get('description', '')
category = layer
verified_date = extract_date(description)
processed_rows.append({
'name': name,
'category': category,
'verified_date': verified_date,
'geometry': row['geometry']
})
if processed_rows:
all_data.append(pd.DataFrame(processed_rows))
if all_data:
final_df = pd.concat(all_data, ignore_index=True)
final_gdf = gpd.GeoDataFrame(final_df, geometry='geometry')
if final_gdf.crs is None:
final_gdf.set_crs(epsg=4326, inplace=True)
final_gdf.geometry = final_gdf.geometry.map(geometry_to_2d)
# Save to GeoJSON
final_gdf.to_file('paris_pois.geojson', driver='GeoJSON')
print("Conversion complete. Saved to paris_pois.geojson")
print("Sample of processed data:")
print(final_gdf[['name', 'category', 'verified_date']].head(20))
# Check if any dates were actually found
found_dates = final_gdf['verified_date'].notnull().sum()
print(f"\nTotal records: {len(final_gdf)}")
print(f"Records with verified_date: {found_dates}")
else:
print("No data found.")
Wrote /work/convert_kml.py (3540 chars).
3:29
Bash
python /work/convert_kml.py
python /work/convert_kml.py
Sample description from first layer:
<b>% Arabica</b><br/>Catégorie : Cafés ouverts tard<br/><a href="https://example.org/poi/0">Voir la fiche</a><br/>Dernière vérification : 2026-01-01
Processing layer: Cafés ouverts tard
Processing layer: Bibliothèques de nuit
Processing layer: Tours et infos touristiques
Conversion complete. Saved to paris_pois.geojson
Sample of processed data:
name category verified_date
0 % Arabica Cafés ouverts tard 2026-01-01
1 1T. rue Scribe Cafés ouverts tard 2026-08-14
2 1ndix Café Cafés ouverts tard 2026-03-27
3 2 Steps Café Cafés ouverts tard 2026-10-12
4 3234 Café - Trente Deux Trente Quatre Cafés ouverts tard 2026-05-25
5 360 Cafe Cafés ouverts tard 2026-12-10
6 3w Cafe Paris Cafés ouverts tard 2026-07-23
7 48 Collagen Caf√© Cafés ouverts tard 2026-02-08
8 86 Champs Elysées Cafés ouverts tard 2026-09-21
9 A la Ville de Paris Cafés ouverts tard 2026-04-06
10 A minima cafe Cafés ouverts tard 2026-11-19
11 AD DictCafé Cafés ouverts tard 2026-06-04
12 ANGIOLO Cafés ouverts tard 2026-01-17
13 Abaita Cafés ouverts tard 2026-08-02
14 Absinthe Café Cafés ouverts tard 2026-03-15
15 Acai & You Cafés ouverts tard 2026-10-28
16 Adulis Cafe Abyssinien Cafés ouverts tard 2026-05-13
17 After Place Cafés ouverts tard 2026-12-26
18 After Place Cafés ouverts tard 2026-07-11
19 After Place Cafés ouverts tard 2026-02-24
Total records: 45
Records with verified_date: 44
3:33
Bash
ls -l paris_pois.geojson
ls -l paris_pois.geojson
-rw-r--r-- 1 agent agent 9783 Jun 18 08:27 paris_pois.geojson
3:36
Bash
head -n 50 paris_pois.geojson
head -n 50 paris_pois.geojson
{
"type": "FeatureCollection",
"name": "paris_pois",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "name": "% Arabica", "category": "Cafés ouverts tard", "verified_date": "2026-01-01" }, "geometry": { "type": "Point", "coordinates": [ 2.324676, 48.854156 ] } },
{ "type": "Feature", "properties": { "name": "1T. rue Scribe", "category": "Cafés ouverts tard", "verified_date": "2026-08-14" }, "geometry": { "type": "Point", "coordinates": [ 2.329774, 48.870581 ] } },
{ "type": "Feature", "properties": { "name": "1ndix Café", "category": "Cafés ouverts tard", "verified_date": "2026-03-27" }, "geometry": { "type": "Point", "coordinates": [ 2.341361, 48.880764 ] } },
{ "type": "Feature", "properties": { "name": "2 Steps Café", "category": "Cafés ouverts tard", "verified_date": "2026-10-12" }, "geometry": { "type": "Point", "coordinates": [ 2.376626, 48.866261 ] } },
{ "type": "Feature", "properties": { "name": "3234 Café - Trente Deux Trente Quatre", "category": "Cafés ouverts tard", "verified_date": "2026-05-25" }, "geometry": { "type": "Point", "coordinates": [ 2.376249, 48.853167 ] } },
{ "type": "Feature", "properties": { "name": "360 Cafe", "category": "Cafés ouverts tard", "verified_date": "2026-12-10" }, "geometry": { "type": "Point", "coordinates": [ 2.321838, 48.842293 ] } },
{ "type": "Feature", "properties": { "name": "3w Cafe Paris", "category": "Cafés ouverts tard", "verified_date": "2026-07-23" }, "geometry": { "type": "Point", "coordinates": [ 2.32753, 48.85156 ] } },
{ "type": "Feature", "properties": { "name": "48 Collagen Caf√©", "category": "Cafés ouverts tard", "verified_date": "2026-02-08" }, "geometry": { "type": "Point", "coordinates": [ 2.34046, 48.87484 ] } },
{ "type": "Feature", "properties": { "name": "86 Champs Elysées", "category": "Cafés ouverts tard", "verified_date": "2026-09-21" }, "geometry": { "type": "Point", "coordinates": [ 2.303767, 48.871387 ] } },
{ "type": "Feature", "properties": { "name": "A la Ville de Paris", "category": "Cafés ouverts tard", "verified_date": "2026-04-06" }, "geometry": { "type": "Point", "coordinates": [ 2.34428, 48.87053 ] } },
{ "type": "Feature", "properties": { "name": "A minima cafe", "category": "Cafés ouverts tard", "verified_date": "2026-11-19" }, "geometry": { "type": "Point", "coordinates": [ 2.38129, 48.8453 ] } },
{ "type": "Feature", "properties": { "name": "AD DictCafé", "category": "Cafés ouverts tard", "verified_date": "2026-06-04" }, "geometry": { "type": "Point", "coordinates": [ 2.390577, 48.849002 ] } },
{ "type": "Feature", "properties": { "name": "ANGIOLO", "category": "Cafés ouverts tard", "verified_date": "2026-01-17" }, "geometry": { "type": "Point", "coordinates": [ 2.331682, 48.854538 ] } },
{ "type": "Feature", "properties": { "name": "Abaita", "category": "Cafés ouverts tard", "verified_date": "2026-08-02" }, "geometry": { "type": "Point", "coordinates": [ 2.368446, 48.864406 ] } },
{ "type": "Feature", "properties": { "name": "Absinthe Café", "category": "Cafés ouverts tard", "verified_date": "2026-03-15" }, "geometry": { "type": "Point", "coordinates": [ 2.357228, 48.865564 ] } },
{ "type": "Feature", "properties": { "name": "Acai & You", "category": "Cafés ouverts tard", "verified_date": "2026-10-28" }, "geometry": { "type": "Point", "coordinates": [ 2.339001, 48.854247 ] } },
{ "type": "Feature", "properties": { "name": "Adulis Cafe Abyssinien", "category": "Cafés ouverts tard", "verified_date": "2026-05-13" }, "geometry": { "type": "Point", "coordinates": [ 2.346929, 48.851763 ] } },
{ "type": "Feature", "properties": { "name": "After Place", "category": "Cafés ouverts tard", "verified_date": "2026-12-26" }, "geometry": { "type": "Point", "coordinates": [ 2.336121, 48.864266 ] } },
{ "type": "Feature", "properties": { "name": "After Place", "category": "Cafés ouverts tard", "verified_date": "2026-07-11" }, "geometry": { "type": "Point", "coordinates": [ 2.351101, 48.862294 ] } },
{ "type": "Feature", "properties": { "name": "After Place", "category": "Cafés ouverts tard", "verified_date": "2026-02-24" }, "geometry": { "type": "Point", "coordinates": [ 2.36156, 48.8695 ] } },
{ "type": "Feature", "properties": { "name": "AUP Housing", "category": "Bibliothèques de nuit", "verified_date": "2026-09-09" }, "geometry": { "type": "Point", "coordinates": [ 2.306008, 48.861754 ] } },
{ "type": "Feature", "properties": { "name": "Academie de Medical", "category": "Bibliothèques de nuit", "verified_date": "2026-04-22" }, "geometry": { "type": "Point", "coordinates": [ 2.334208, 48.856342 ] } },
{ "type": "Feature", "properties": { "name": "Ancêtres Italiens", "category": "Bibliothèques de nuit", "verified_date": "2026-11-07" }, "geometry": { "type": "Point", "coordinates": [ 2.363314, 48.873246 ] } },
{ "type": "Feature", "properties": { "name": "Apfelbaum Library", "category": "Bibliothèques de nuit", "verified_date": "2026-06-20" }, "geometry": { "type": "Point", "coordinates": [ 2.330685, 48.849518 ] } },
{ "type": "Feature", "properties": { "name": "Archives de l'AP-HP", "category": "Bibliothèques de nuit", "verified_date": "2026-01-05" }, "geometry": { "type": "Point", "coordinates": [ 2.366407, 48.857094 ] } },
{ "type": "Feature", "properties": { "name": "Arthur Rimbaud Library", "category": "Bibliothèques de nuit", "verified_date": "2026-08-18" }, "geometry": { "type": "Point", "coordinates": [ 2.355874, 48.856134 ] } },
{ "type": "Feature", "properties": { "name": "Association Bibliotheque Braille Enfantine", "category": "Bibliothèques de nuit", "verified_date": "2026-03-03" }, "geometry": { "type": "Point", "coordinates": [ 2.372404, 48.868038 ] } },
{ "type": "Feature", "properties": { "name": "Association Francaise de Therapie", "category": "Bibliothèques de nuit", "verified_date": "2026-10-16" }, "geometry": { "type": "Point", "coordinates": [ 2.308679, 48.87096 ] } },
{ "type": "Feature", "properties": { "name": "Attractive", "category": "Bibliothèques de nuit", "verified_date": "2026-05-01" }, "geometry": { "type": "Point", "coordinates": [ 2.326704, 48.866369 ] } },
{ "type": "Feature", "properties": { "name": "BNF", "category": "Bibliothèques de nuit", "verified_date": "2026-12-14" }, "geometry": { "type": "Point", "coordinates": [ 2.338076, 48.867811 ] } },
{ "type": "Feature", "properties": { "name": "BPI Bibliotheque Publique D Information", "category": "Bibliothèques de nuit", "verified_date": "2026-07-27" }, "geometry": { "type": "Point", "coordinates": [ 2.352378, 48.859478 ] } },
{ "type": "Feature", "properties": { "name": "BPI-Beaubourg", "category": "Bibliothèques de nuit", "verified_date": "2026-02-12" }, "geometry": { "type": "Point", "coordinates": [ 2.351867, 48.860532 ] } },
{ "type": "Feature", "properties": { "name": "Beaubourg Librairie des Arts", "category": "Bibliothèques de nuit", "verified_date": "2026-09-25" }, "geometry": { "type": "Point", "coordinates": [ 2.356791, 48.865189 ] } },
{ "type": "Feature", "properties": { "name": "Bib Cent du Service de Sante Des Armees", "category": "Bibliothèques de nuit", "verified_date": "2026-04-10" }, "geometry": { "type": "Point", "coordinates": [ 2.341252, 48.841187 ] } },
{ "type": "Feature", "properties": { "name": "Bibliothe que Centrale - Muse'um national d'histoire naturelle (MNHN)", "category": "Bibliothèques de nuit", "verified_date": "2026-11-23" }, "geometry": { "type": "Point", "coordinates": [ 2.355838, 48.84266 ] } },
{ "type": "Feature", "properties": { "name": "24hourstravel", "category": "Tours et infos touristiques", "verified_date": "2026-06-08" }, "geometry": { "type": "Point", "coordinates": [ 2.34781, 48.864739 ] } },
{ "type": "Feature", "properties": { "name": "2CVPrivateParis", "category": "Tours et infos touristiques", "verified_date": "2026-01-21" }, "geometry": { "type": "Point", "coordinates": [ 2.337967, 48.889998 ] } },
{ "type": "Feature", "properties": { "name": "382444404", "category": "Tours et infos touristiques", "verified_date": null }, "geometry": { "type": "Point", "coordinates": [ 2.32691, 48.87068 ] } },
{ "type": "Feature", "properties": { "name": "A French Frye in Paris", "category": "Tours et infos touristiques", "verified_date": "2026-03-19" }, "geometry": { "type": "Point", "coordinates": [ 2.34083, 48.86124 ] } },
{ "type": "Feature", "properties": { "name": "Aeon Tours of Paris", "category": "Tours et infos touristiques", "verified_date": "2026-10-04" }, "geometry": { "type": "Point", "coordinates": [ 2.35859, 48.86378 ] } },
{ "type": "Feature", "properties": { "name": "Agence ORNIcom : location de gyropode Segway à Paris et sur toute la France", "category": "Tours et infos touristiques", "verified_date": "2026-05-17" }, "geometry": { "type": "Point", "coordinates": [ 2.303244, 48.852662 ] } },
{ "type": "Feature", "properties": { "name": "Al Abrar Voyages", "category": "Tours et infos touristiques", "verified_date": "2026-12-02" }, "geometry": { "type": "Point", "coordinates": [ 2.31277, 48.88631 ] } },
{ "type": "Feature", "properties": { "name": "Allison Zinder", "category": "Tours et infos touristiques", "verified_date": "2026-07-15" }, "geometry": { "type": "Point", "coordinates": [ 2.398851, 48.862635 ] } },
{ "type": "Feature", "properties": { "name": "Association Touristique Cheminots", "category": "Tours et infos touristiques", "verified_date": "2026-02-28" }, "geometry": { "type": "Point", "coordinates": [ 2.371287, 48.846263 ] } },
{ "type": "Feature", "properties": { "name": "Avenir Tourisme", "category": "Tours et infos touristiques", "verified_date": "2026-09-13" }, "geometry": { "type": "Point", "coordinates": [ 2.35804, 48.868509 ] } }
3:41
Bash
rm /work/convert_kml.py
rm /work/convert_kml.py(no output)
4:10
Write
/work/solve.py
content · 82 lines · py
import geopandas as gpd
import pandas as pd
import re
from datetime import datetime
from shapely.geometry import Point
def geometry_to_2d(geom):
if geom.has_z:
return Point(geom.x, geom.y)
return geom
def extract_date(text):
if not isinstance(text, str):
return None
# The sample showed: "Dernière vérification : 2026-01-01"
# After stripping HTML-like tags or decoding, it looks like a date follows a colon.
# Let's try to find any date pattern YYYY-MM-DD in the string.
# 1. Try to find the date directly with regex
date_match = re.search(r'(\d{4}-\d{2}-\d{2})', text)
if date_match:
return date_match.group(1)
# 2. Fallback: look for DD/MM/YYYY
date_match_alt = re.search(r'(\d{2}/\d{2}/\d{4})', text)
if date_match_alt:
date_str = date_match_alt.group(1)
try:
return datetime.strptime(date_str, '%d/%m/%Y').date().isoformat()
except ValueError:
pass
return None
layers = ['Cafés ouverts tard', 'Bibliothèques de nuit', 'Tours et infos touristiques']
all_data = []
for layer in layers:
print(f"Processing layer: {layer}")
try:
gdf_layer = gpd.read_file('paris_late_night_pois.kml', layer=layer)
except Exception as e:
print(f"Error reading layer {layer}: {e}")
continue
processed_rows = []
for _, row in gdf_layer.iterrows():
name = row.get('Name', '')
description = row.get('description', '')
category = layer
verified_date = extract_date(description)
processed_rows.append({
'name': name,
'category': category,
'verified_date': verified_date,
'geometry': row['geometry']
})
if processed_rows:
all_data.append(pd.DataFrame(processed_rows))
if all_data:
final_df = pd.concat(all_data, ignore_index=True)
final_gdf = gpd.GeoDataFrame(final_df, geometry='geometry')
if final_gdf.crs is None:
final_gdf.set_crs(epsg=4326, inplace=True)
final_gdf.geometry = final_gdf.geometry.map(geometry_to_2d)
# Save to GeoJSON
final_gdf.to_file('paris_pois.geojson', driver='GeoJSON')
print("Conversion complete. Saved to paris_pois.geojson")
print(f"Total records: {len(final_gdf)}")
print(f"Records with verified_date: {final_gdf['verified_date'].notnull().sum()}")
print(final_gdf[['name', 'category', 'verified_date']].head(10))
else:
print("No data found.")
Wrote /work/solve.py (2565 chars).
4:13
Bash
python /work/solve.py
python /work/solve.py
Processing layer: Cafés ouverts tard
Processing layer: Bibliothèques de nuit
Processing layer: Tours et infos touristiques
Conversion complete. Saved to paris_pois.geojson
Total records: 45
Records with verified_date: 45
name category verified_date
0 % Arabica Cafés ouverts tard 2026-01-01
1 1T. rue Scribe Cafés ouverts tard 2026-08-14
2 1ndix Café Cafés ouverts tard 2026-03-27
3 2 Steps Café Cafés ouverts tard 2026-10-12
4 3234 Café - Trente Deux Trente Quatre Cafés ouverts tard 2026-05-25
5 360 Cafe Cafés ouverts tard 2026-12-10
6 3w Cafe Paris Cafés ouverts tard 2026-07-23
7 48 Collagen Caf√© Cafés ouverts tard 2026-02-08
8 86 Champs Elysées Cafés ouverts tard 2026-09-21
9 A la Ville de Paris Cafés ouverts tard 2026-04-06
4:19