Trafic / traficFranceBleuParis.py
tyriaa's picture
initial commit 0333222333
bd323f8
import json
import requests
import re
def normalize_line_name(line_name):
"""
Normalise les noms de lignes pour éviter les doublons causés par des préfixes ou variations.
Exemples :
- "RATP Métro 14" devient "Métro 14"
- "SNCF RER A" devient "RER A"
"""
line_name = line_name.lower() # Convertir en minuscule pour uniformiser
line_name = re.sub(r"\b(ratp|sncf)\b", "", line_name) # Supprimer les préfixes "RATP" ou "SNCF"
line_name = re.sub(r"\s+", " ", line_name).strip() # Supprimer les espaces multiples et les espaces de début/fin
return line_name.capitalize() # Capitaliser la première lettre pour cohérence
# Configurer l'API
headers = {
'accept': 'application/json',
'apiKey': '3WsgOWybmrTiEwa3q8ZsvovvwPkrctnX' # Remplacez par votre clé API
}
base_url_api = 'https://prim.iledefrance-mobilites.fr/marketplace/v2/navitia/line_reports/physical_modes/physical_mode%3A'
categories = {
"Métro": "Metro",
"RER": "RapidTransit",
"Bus": "Bus",
"Transilien": "LocalTrain",
"Tram": "Tramway" # Nouvelle catégorie
}
# Fonction pour récupérer les perturbations par catégorie
def fetch_disruptions(category_key, category_value):
url_api = f"{base_url_api}{category_value}/line_reports?"
try:
response_api = requests.get(url_api, headers=headers)
response_api.raise_for_status() # Vérifie les erreurs HTTP
data = response_api.json() # Convertit la réponse en JSON
print(f"Données récupérées avec succès pour {category_key}.")
return data.get("disruptions", [])
except requests.exceptions.RequestException as e:
print(f"Erreur lors de la récupération des données pour {category_key}: {e}")
return []
def extract_lines_from_message(messages):
"""
Extraire les lignes mentionnées dans les messages d'une perturbation.
"""
lines = []
for msg in messages:
text = msg.get("text", "")
# Rechercher des noms de lignes tels que "Métro 1", "RER A", "Bus 72", etc.
matches = re.findall(r"(Métro \d+|RER [A-Z]|Bus \d+|Transilien \w+|Tram \d+)", text)
lines.extend(matches)
return lines or ["Toutes les lignes"]
# Extraire les perturbations actives pour chaque catégorie
all_disruptions = {}
for category, api_value in categories.items():
disruptions = fetch_disruptions(category, api_value)
active_disruptions = [
{
"id": disruption.get("id"),
"cause": disruption.get("cause"),
"severity": disruption.get("severity", {}).get("name", "N/A"),
"effect": disruption.get("effect"),
"message": disruption.get("messages")[0].get("text") if disruption.get("messages") else "No message",
# Compléter les lignes impactées avec celles extraites des messages si nécessaire
"impacted_lines": [
obj.get("pt_object", {}).get("name", "Unknown Line")
for obj in disruption.get("impacted_objects", [])
if obj.get("pt_object", {}).get("embedded_type") == "line"
] or extract_lines_from_message(disruption.get("messages", [])),
}
for disruption in disruptions
if disruption.get("status") == "active"
and all(excluded not in (disruption.get("cause", "").lower() + " " +
" ".join([msg.get("text", "").lower() for msg in disruption.get("messages", [])]))
for excluded in ["ascenseur", "transdev"])
]
all_disruptions[category] = active_disruptions
# Générer le fichier HTML avec un récapitulatif filtré
html_output_path = "./tmp/traffic_report.html"
with open(html_output_path, "w", encoding="utf-8") as html_file:
# Collecter les lignes ayant des problèmes sans doublons et filtrer par gravité pour le récapitulatif
seen_lines = set()
summary_data = []
# Gravités à inclure uniquement pour le récapitulatif
allowed_severities = {"perturbée", "bloquante"}
for category, disruptions in all_disruptions.items():
for disruption in disruptions:
# Filtrer par gravité uniquement pour le récapitulatif
severity = disruption.get("severity", "").lower()
if severity not in allowed_severities:
continue # Ignorer dans le récapitulatif si la gravité ne correspond pas
for line in disruption["impacted_lines"]:
# Normaliser le nom de la ligne avant de vérifier les doublons
normalized_line = normalize_line_name(line)
if normalized_line not in seen_lines:
summary_data.append({
"category": category,
"line": normalized_line, # Utiliser la version normalisée
"cause": disruption["cause"],
"severity": disruption["severity"]
})
seen_lines.add(normalized_line) # Ajouter la version normalisée à l'ensemble
# Générer le fichier HTML
html_file.write(f"""
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rapport des Perturbations</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f9;
}}
h1 {{
text-align: center;
padding: 20px 0;
background-color: #007BFF;
color: white;
margin: 0;
}}
h2 {{
color: #007BFF;
text-align: left;
margin: 20px 40px;
}}
table {{
width: 90%;
margin: 20px auto;
border-collapse: collapse;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}}
th, td {{
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}}
th {{
background-color: #007BFF;
color: white;
}}
/* Suppression de l'alternance des couleurs */
/* tr:nth-child(even) {{
background-color: #f9f9f9;
}} */
tr:hover {{
background-color: #f1f1f1;
}}
.summary {{
margin: 20px 40px;
padding: 20px;
background-color: #FFF9C4;
border: 1px solid #FFEB3B;
border-radius: 5px;
}}
.severity-perturbee {{
background-color: #F9A972; /* Orange */
color: black;
}}
.severity-bloquante {{
background-color: #EC2828; /* Rouge */
color: white;
}}
.category-bold {{
font-weight: bold;
}}
</style>
</head>
<body>
<h1>Rapport des Perturbations</h1>
<!-- Section Récapitulatif -->
<div class="summary">
<h2>Récapitulatif des Lignes Impactées (Perturbée / Bloquante)</h2>
<table>
<thead>
<tr>
<th>Catégorie</th>
<th>Ligne</th>
<th>Cause</th>
<th>Gravité</th>
</tr>
</thead>
<tbody>
""")
# Ajouter les lignes impactées uniques au récapitulatif avec des couleurs selon la gravité
for item in summary_data:
severity_class = "severity-perturbee" if item["severity"].lower() == "perturbée" else "severity-bloquante"
html_file.write(f"""
<tr class="{severity_class}">
<td class="category-bold">{item["category"]}</td>
<td>{item["line"]}</td>
<td>{item["cause"]}</td>
<td>{item["severity"]}</td>
</tr>
""")
# Fermer la section récapitulative
html_file.write("""
</tbody>
</table>
</div>
""")
# Reste de la génération des détails des perturbations
for category, disruptions in all_disruptions.items():
html_file.write(f"<h2>{category}</h2>")
html_file.write("""
<table>
<thead>
<tr>
<th>Ligne</th>
<th>Cause</th>
<th>Gravité</th>
<th>Message</th>
</tr>
</thead>
<tbody>
""")
for disruption in disruptions:
for line in disruption["impacted_lines"]:
html_file.write(f"""
<tr>
<td>{line}</td>
<td>{disruption["cause"]}</td>
<td>{disruption["severity"]}</td>
<td>{disruption["message"]}</td>
</tr>
""")
html_file.write("""
</tbody>
</table>
""")
# Fermer les balises HTML
html_file.write("""
</body>
</html>
""")
print(f"HTML report generated at {html_output_path}")
def generate_traffic_html():
all_disruptions = {}
for category, api_value in categories.items():
disruptions = fetch_disruptions(category, api_value)
active_disruptions = [
{
"id": disruption.get("id"),
"cause": disruption.get("cause"),
"severity": disruption.get("severity", {}).get("name", "N/A"),
"effect": disruption.get("effect"),
"message": disruption["messages"][0].get("text") if disruption.get("messages") else "No message",
"impacted_lines": [
obj.get("pt_object", {}).get("name", "Unknown Line")
for obj in disruption.get("impacted_objects", [])
if obj.get("pt_object", {}).get("embedded_type") == "line"
] or extract_lines_from_message(disruption.get("messages", [])),
}
for disruption in disruptions
if disruption.get("status") == "active"
and all(excluded not in (disruption.get("cause", "").lower() + " " +
" ".join([msg.get("text", "").lower() for msg in disruption.get("messages", [])]))
for excluded in ["ascenseur", "transdev"])
]
all_disruptions[category] = active_disruptions
# Construction des données du récapitulatif
seen_lines = set()
summary_data = []
allowed_severities = {"perturbée", "bloquante"}
for category, disruptions in all_disruptions.items():
for disruption in disruptions:
severity = disruption.get("severity", "").lower()
if severity not in allowed_severities:
continue
for line in disruption["impacted_lines"]:
normalized_line = normalize_line_name(line)
if normalized_line not in seen_lines:
summary_data.append({
"category": category,
"line": normalized_line,
"cause": disruption["cause"],
"severity": disruption["severity"]
})
seen_lines.add(normalized_line)
# Génération du HTML avec un menu déroulant pour filtrer par catégorie
# Récupérer la liste unique des catégories présentes
categories_presentes = sorted(list(all_disruptions.keys()))
html_content = f"""
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rapport des Perturbations</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f9;
}}
h1 {{
text-align: center;
padding: 20px 0;
background-color: #007BFF;
color: white;
margin: 0;
}}
h2 {{
color: #007BFF;
text-align: left;
margin: 20px 40px;
}}
.filter-container {{
margin: 20px 40px;
}}
table {{
width: 90%;
margin: 20px auto;
border-collapse: collapse;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}}
th, td {{
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}}
th {{
background-color: #007BFF;
color: white;
}}
tr:hover {{
background-color: #f1f1f1;
}}
.summary {{
margin: 20px 40px;
padding: 20px;
background-color: #FFF9C4;
border: 1px solid #FFEB3B;
border-radius: 5px;
}}
.severity-perturbee {{
background-color: #F9A972; /* Orange */
color: black;
}}
.severity-bloquante {{
background-color: #EC2828; /* Rouge */
color: white;
}}
.category-bold {{
font-weight: bold;
}}
</style>
</head>
<body>
<h1>Rapport des Perturbations</h1>
<div class="filter-container">
<label for="categoryFilter">Filtrer par catégorie :</label>
<select id="categoryFilter">
<option value="all">Toutes</option>
"""
# Ajouter options pour chaque catégorie présente
for cat in categories_presentes:
html_content += f'<option value="{cat}">{cat}</option>'
html_content += """
</select>
</div>
<div class="summary">
<h2>Récapitulatif des Lignes Impactées (Perturbée / Bloquante)</h2>
<table id="summaryTable">
<thead>
<tr>
<th>Catégorie</th>
<th>Ligne</th>
<th>Cause</th>
<th>Gravité</th>
</tr>
</thead>
<tbody>
"""
# Ajouter les lignes impactées uniques au récapitulatif
for item in summary_data:
severity_class = "severity-perturbee" if item["severity"].lower() == "perturbée" else "severity-bloquante"
# Ajouter un data-attribute pour la catégorie
html_content += f"""
<tr class="{severity_class}" data-category="{item["category"]}">
<td class="category-bold">{item["category"]}</td>
<td>{item["line"]}</td>
<td>{item["cause"]}</td>
<td>{item["severity"]}</td>
</tr>
"""
html_content += """
</tbody>
</table>
</div>
"""
# Ajouter les détails des perturbations
for category, disruptions in all_disruptions.items():
# Ajouter un data-category au <h2>
html_content += f'<h2 data-category="{category}">{category}</h2>'
html_content += f"""
<table data-category="{category}">
<thead>
<tr>
<th>Ligne</th>
<th>Cause</th>
<th>Gravité</th>
<th>Message</th>
</tr>
</thead>
<tbody>
"""
for disruption in disruptions:
for line in disruption["impacted_lines"]:
sev_class = "severity-perturbee" if disruption["severity"].lower() == "perturbée" else "severity-bloquante"
html_content += f"""
<tr class="{sev_class}" data-category="{category}">
<td>{line}</td>
<td>{disruption["cause"]}</td>
<td>{disruption["severity"]}</td>
<td>{disruption["message"]}</td>
</tr>
"""
html_content += """
</tbody>
</table>
"""
# Après avoir fini de générer le HTML, ajouter le script de filtrage
html_content += """
<script>
const categoryFilter = document.getElementById('categoryFilter');
categoryFilter.addEventListener('change', () => {
const selectedCategory = categoryFilter.value;
// Filtrer les lignes du récapitulatif
const summaryRows = document.querySelectorAll('#summaryTable tbody tr');
summaryRows.forEach(row => {
const rowCategory = row.getAttribute('data-category');
if (selectedCategory === 'all' || rowCategory === selectedCategory) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
// Filtrer les tables de détails
const detailTables = document.querySelectorAll('table[data-category]');
detailTables.forEach(table => {
const tableCat = table.getAttribute('data-category');
if (selectedCategory === 'all' || tableCat === selectedCategory) {
table.style.display = '';
} else {
table.style.display = 'none';
}
});
// Filtrer aussi les titres des catégories
const headings = document.querySelectorAll('h2[data-category]');
headings.forEach(heading => {
const headingCat = heading.getAttribute('data-category');
if (selectedCategory === 'all' || headingCat === selectedCategory) {
heading.style.display = '';
} else {
heading.style.display = 'none';
}
});
});
</script>
"""
return html_content