La division SIG de notre ville avait besoin d'un moyen pour que les résidents signalent des nids-de-poule, des fuites d'eau, des graffitis, des dépôts illégaux, des problèmes de code et autres préoccupations similaires, et pour que le personnel puisse traiter ces tickets sans utiliser un produit tiers 311. Nous l'avons construit sur l'ArcGIS Enterprise que nous utilisons déjà, et nous avons maintenant publié tout le système sur GitHub, généralisé pour que toute organisation puisse le déployer.
https://community.esri.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fgithub.com%2Fbrianmcleer%2Freport-a-concern
Ce post est une présentation technique de la façon dont il est assemblé et pourquoi certains choix ont été faits. Le dépôt contient le guide complet de déploiement.
Ce que contient la boîte
Deux widgets Experience Builder 1.21 (assistant de soumission public et gestionnaire de tickets pour le personnel), un schéma de géodatabase d'entreprise avec règles d'attributs, un petit proxy Flask qui sert de façade au service de fonctionnalités public, sept scripts Python qui s'exécutent via le Planificateur de tâches Windows, des modèles d'e-mails HTML accessibles, des définitions de tâches du Planificateur, et la documentation. Rien dans le code n'est lié à notre organisation. Les noms d'hôtes, adresses e-mail, noms de départements et identifiants d'éléments proviennent de deux fichiers de configuration ignorés par git et des panneaux de paramètres des widgets.
Le modèle de données
Tout réside dans une seule géodatabase d'entreprise SQL Server. Le système principal est une classe d'entités ponctuelles, Tickets, avec un identifiant GUID du ticket, un numéro lisible par l'humain, une catégorie en tant que sous-type (13 codes), un champ texte sous-catégorie dont le domaine des valeurs codées change selon le sous-type, le statut, le département assigné, la limite dans laquelle se trouve le point, les champs de contact du soumissionnaire et deux indicateurs que les scripts sondent : notification_sent et survey_sent. Les pièces jointes sont activées sur Tickets pour les photos.
Autour se trouvent des tables liées jointes par identifiant de ticket avec des classes de relation composites afin qu'une suppression de ticket soit en cascade : Ticket_Comments (avec un indicateur is_public et un indicateur email_sent), Ticket_Photos_Meta et Survey_Responses. Trois tables de référence pilotent l'acheminement : Service_Boundaries (polygones avec un indicateur is_active), Category_Boundary_Lookup (quelles catégories sont valides dans quelle limite, plus un message de redirection pour celles qui ne le sont pas) et Ticket_Routing (catégorie, sous-catégorie optionnelle, identifiant limite, département par défaut, e-mail du département). Notification_Log est une table d'audit en mode ajout uniquement à laquelle chaque script et le proxy écrivent.
Les tickets, commentaires et réponses aux enquêtes sont versionnés et archivés. Les tables de référence et Notification_Log ne le sont pas intentionnellement, ce qui est important ci-dessous.
Flux de soumission, du début à la fin
- Le résident ouvre l'application publique Experience Builder et place une épingle. Le widget de soumission interroge côté client Service_Boundaries pour trouver quels polygones actifs contiennent le point, puis interroge Category_Boundary_Lookup afin que la liste des catégories affiche uniquement ce qui est valide là. Un ticket eau ne peut pas être déposé dans un district d'eau voisin, et le résident voit les coordonnées de ce district au lieu d'une impasse.
- Le widget poste un applyEdits vers une URL même origine sous l'application, pas directement vers le service de fonctionnalités. IIS URL Rewrite (au niveau du site, donc une republication Experience Builder ne peut pas l'effacer) redirige ce chemin vers un proxy Flask sur localhost via Application Request Routing. Une seconde règle au niveau du site renvoie 403 à tout POST direct vers FeatureServer depuis l'extérieur.
- Le proxy limite le débit par IP client, rejette plus d'une entité par requête, vérifie la géométrie contre une boîte englobante, valide la catégorie, la longueur de description, la forme des e-mails et téléphones, puis seulement transmet au FeatureServer local. Pour les photos il lit les premiers octets et n'accepte que les contenus JPEG réels, PNG, WebP et HEIC indépendamment du type déclaré, avec une limite de taille. Chaque requête acceptée ou rejetée est écrite dans Notification_Log avec l'IP client pour avoir une piste d'audit sans journalisation fichier.
- Cinq règles d'attribut Arcade s'exécutent à l'insertion dans la géodatabase : une contrainte géofencée (bloque les soumissions hors limites ou catégorie invalide avec message issu du lookup), un calcul d'acheminement qui trouve la limite, tente une correspondance catégorie plus sous-catégorie plus limite dans Ticket_Routing puis retombe sur la ligne générique catégorie pour définir le département assigné, un calcul du numéro du ticket tiré d'une séquence SQL commençant à 10000, et deux contraintes de validation pour champs obligatoires et règles métier. L'acheminement est piloté par les données, pas par le code : ajouter un département ou changer qui reçoit les tickets eau dans un district est une modification ligne.
- Toutes les cinq minutes le script notificateur nouveaux tickets trouve les tickets avec notification_sent = 0, envoie au résident un e-mail de confirmation avec lien statut et envoie au département acheminé un avis travail avec lien profond vers l'app gestionnaire.
Côté personnel
Le widget gestionnaire fonctionne dans une app Experience Builder interne derrière connexion Portal. Il lit la couche Tickets et les tables liées depuis la carte web donc aucun jeton ou URL supplémentaire n'est configuré. Le personnel filtre par statut, badges catégorie et département, ouvre un ticket, change son statut (un changement nécessite un commentaire ; reculer une date résolue écrit une note interne d'audit), ajoute commentaires publics ou internes, visualise photos en lightbox et voit la réponse enquête si reçue. Les commentaires publics déclenchent un e-mail au résident via script mailer commentaires. Il y a une exportation Excel avec feuille résumé et enregistrements liés. Un guide aide intégré suit notre modèle habituel sur tous nos widgets.
Les scripts et deux erreurs initiales
Sept scripts partagent un module commun pour configurer journalisation, alertes échec, e-mailing, rendu modèles et écritures audit afin que chaque script contienne uniquement sa logique propre : notificateur nouveaux tickets, notificateur réaffectation (détecte changement assigned_to et envoie e-mail au nouveau département), mailer commentaires publics, mailer invitation enquête (lancé quand ticket atteint Résolu ou Clos), extraction réponses Survey123 (écrit dans Survey_Responses et avertit par e-mail celui qui a résolu si suivi demandé), rapport directeur jours ouvrés sur tickets en retard par catégorie et rapport mensuel tous départements. Chaque échec lors d'une exécution est collecté puis envoyé en un seul e-mail alerte à la fin ; processus sort avec code 1 pour que Planificateur affiche erreur.
Deux enseignements intégrés au code. D'abord les doublons d'e-mails. Le notificateur original envoyait tous les mails dans une session large applyEdits puis validait l'indicateur à la fin. Si cette validation échouait (un membre du personnel avait ouvert ce ticket dans gestionnaire donc conflit version), tous les mails étaient déjà partis mais indicateurs annulés donc exécution suivante renvoyait tout encore. Maintenant l'indicateur est validé ticket par ticket dans sa propre courte opération avant tout envoi mail. Si validation échoue pas d'envoi mail ; réessai sûr au prochain passage. Si validation réussit mais envoi échoue c'est journalisé visible mais jamais renvoyé.
Ensuite la table audit. Notification_Log était versionnée initialement ; scripts écrivant via curseurs étaient lents et dépendants de Compress. Elle n'est plus enregistrée en versionnement ; chaque écriture se fait via SQL direct avec OBJECTID explicite MAX+1 avec court réessai donc aucune écriture ne dépend du compteur ID ligne géodatabase ni collision entre scripts. Lié à cela : mailer enquête lisait table base non versionnée donc ne voyait résolution qu'après Compress suivant ; enquêtes envoyées avec retard complet cycle. Chaque script lit maintenant via classe entités versionnée.
Boucle enquête
Quand un ticket est résolu le résident reçoit lien vers formulaire Survey123 avec id ticket dans URL. Le script extraction lit nouvelles réponses depuis ArcGIS Online puis écrit dans Survey_Responses géodatabase ; si résident a demandé rappel il avertit par e-mail celui qui a résolu (résolution issue du suivi éditeur avec repli département). Widget gestionnaire affiche note et commentaires sur ticket.
Sécurité et confidentialité
Les utilisateurs anonymes ont lecture sur vue publique et peuvent créer uniquement via proxy. En-têtes IIS niveau site définissent HSTS, nosniff et options cadre. Le service fonctionnalités restreint types fichiers uploadés et taille. Proxy ne fait jamais confiance au type contenu déclaré. Conservation gérée par tables résumé comptant par mois catégorie département sans id ticket ni texte libre ; anciens tickets contenant données personnelles peuvent être purgés selon planning tandis que statistiques survivent.
Déploiement
Le guide docs/deployment.md liste étapes ordonnées : construire schéma avec script arcpy (exécution test préalable), publier trois services (public écriture, public lecture, personnel), ajouter règles attributs, déposer deux widgets dans your-extensions puis construire apps, appliquer règles IIS niveau site, déployer proxy dans environnement Python ArcGIS Pro cloné , remplir config.py et rac_secrets.py , importer tâches Planificateur Windows . Chaque script inclut mode test activé donc aucun mail réel envoyé avant bascule . Doc dépannage liste symptômes causes solutions rencontrés lors migration entre serveurs : erreur IIS HTML 500 signale proxy non actif , 404 signale ARR non installé , échec CORS signale URL widget non même origine page , erreur Planificateur Windows Server 2025 signale tâche pointant vers Python ArcGIS Pro base au lieu clone .
Les archives zip des widgets sont jointes à chaque release GitHub . Issues et pull requests sont bienvenus sur le dépôt .