validation.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. '''
  2. @author: olivier.massot, sept. 2018
  3. '''
  4. import time
  5. import zipfile
  6. from path import Path, TempDir
  7. from core import gis_
  8. from core.cerberus_extend import CerberusErrorHandler, \
  9. _translate_messages, ExtendedValidator
  10. from schemas.common import SRID
  11. class ValidatorInterruption(BaseException):
  12. pass
  13. class Checkpoint():
  14. def __init__(self, name, valid=True):
  15. self.name = name
  16. self.valid = valid
  17. ########### MODELES ################
  18. class BaseModel():
  19. filename = ""
  20. pk = ""
  21. schema = {}
  22. def __init__(self, **kwargs):
  23. self.__dict__.update(kwargs)
  24. class BaseGeoModel(gis_.Feature):
  25. filename = ""
  26. pk = ""
  27. geom_type = 0
  28. bounding_box = (0,0,1,1)
  29. schema = {}
  30. def __init__(self, feature):
  31. self.__dict__.update(feature.__dict__)
  32. ########### ERREURS DE VALIDATION ################
  33. VALIDATION_ERROR_LEVELS = {10: "MINEURE", 20: "AVERTISSEMENT", 30: "ERREUR", 40: "CRITIQUE"}
  34. MINOR = 10
  35. WARNING = 20
  36. ERROR = 30
  37. CRITICAL = 40
  38. class BaseValidationError():
  39. order_ = 0
  40. name = "Erreur"
  41. level = ERROR
  42. help = ""
  43. def __init__(self, message, filename="", field=""):
  44. self.message = message
  45. self.filename = filename
  46. self.field = field
  47. def __repr__(self):
  48. return " - ".join(filter(None, [self.name, self.filename, self.field, self.message]))
  49. # Erreurs dans le chargement des fichiers
  50. class InputError(BaseValidationError):
  51. order_ = 0
  52. level = CRITICAL
  53. name = "Erreur de chargement"
  54. class MissingFile(InputError):
  55. order_ = 1
  56. name = "Fichier Manquant"
  57. class UnreadableFile(InputError):
  58. order_ = 2
  59. name = "Fichier Illisible"
  60. class WrongSrid(InputError):
  61. order_ = 3
  62. name = "Mauvais SRID"
  63. ### Erreurs dans la structure des données
  64. class StructureError(BaseValidationError):
  65. order_ = 10
  66. name = "Erreur de structure"
  67. level = ERROR
  68. class GeomTypeError(StructureError):
  69. order_ = 12
  70. name = "Type de géométrie invalide"
  71. level = CRITICAL
  72. class BoundingBoxError(StructureError):
  73. order_ = 11
  74. name = "Coordonnées hors de la zone autorisée"
  75. class InvalidGeometry(StructureError):
  76. order_ = 13
  77. name = "Géométrie invalide"
  78. class DataError(StructureError):
  79. order_ = 14
  80. name = "Erreur de format"
  81. # Erreurs dans le contenu, erreurs métiers
  82. class TechnicalValidationError(BaseValidationError):
  83. order_ = 20
  84. level = ERROR
  85. name = "Erreur technique"
  86. class UniqueError(TechnicalValidationError):
  87. order_ = 21
  88. name = "Doublons dans le champs"
  89. class RelationError(TechnicalValidationError):
  90. order_ = 22
  91. level = CRITICAL
  92. name = "Un objet lié n'existe pas"
  93. class DuplicatedGeom(TechnicalValidationError):
  94. order_ = 23
  95. name = "Doublon graphique"
  96. class MissingItem(TechnicalValidationError):
  97. order_ = 24
  98. name = "Elément manquant"
  99. class DimensionError(TechnicalValidationError):
  100. order_ = 25
  101. name = "Elément de dimension"
  102. class PositionError(TechnicalValidationError):
  103. order_ = 26
  104. name = "Erreur de positionnement"
  105. ########### VALIDATION ################
  106. class BaseValidator():
  107. schema_name = ""
  108. models = {}
  109. dataset = {}
  110. def __init__(self):
  111. self.valid = True
  112. self.checkpoints = []
  113. self.errors = []
  114. self._current_checkpoint_valid = True
  115. self.dt = 0
  116. def checkpoint(self, title):
  117. self.checkpoints.append(Checkpoint(title, self._current_checkpoint_valid))
  118. self._current_checkpoint_valid = True
  119. if self.errors:
  120. self.valid = False
  121. if self.critical_happened():
  122. raise ValidatorInterruption()
  123. def critical_happened(self):
  124. return any([err.level == CRITICAL for err in self.errors])
  125. def log_error(self, validation_error):
  126. self._current_checkpoint_valid = False
  127. self.errors.append(validation_error)
  128. @classmethod
  129. def submit(cls, subject):
  130. """ prends un dossier ou une archive en entrée et vérifie son contenu """
  131. subject = Path(subject)
  132. if subject.isfile():
  133. with TempDir() as dirname:
  134. zip_ref = zipfile.ZipFile(subject, 'r')
  135. zip_ref.extractall(dirname)
  136. zip_ref.close()
  137. if Path(dirname / subject.stem).isdir(): # cas où l'archive contient un dossier qui lui-même contient les fichiers
  138. dirname /= subject.stem
  139. return cls._submit_folder(dirname)
  140. elif subject.isdir():
  141. return cls._submit_folder(subject)
  142. else:
  143. raise FileNotFoundError(f"Impossible de trouver le fichier ou répertoire: {subject}")
  144. @classmethod
  145. def _submit_folder(cls, folder):
  146. validator = cls()
  147. t0 = time.time()
  148. try:
  149. validator.validate(folder)
  150. except ValidatorInterruption:
  151. pass
  152. validator.dt = time.time() - t0
  153. report = validator.build_report(validator.schema_name, folder.name)
  154. return report
  155. def validate(self, folder):
  156. # Chargement des données en mémoire
  157. self._load_files(folder)
  158. self.checkpoint("Chargement des données")
  159. # Controle la structure des données (champs, formats et types)
  160. self._structure_validation()
  161. self.checkpoint("Contrôle de la structure des données")
  162. # Validation technique
  163. # try:
  164. self._technical_validation()
  165. self.checkpoint("Validation Métier")
  166. # except:
  167. # self.checkpoint("Validation Métier [interrompu]")
  168. def _load_files(self, folder):
  169. """ Charge les données du fichier et les associe à un modèle.
  170. Attention: pas de contrôle de format ou de validité à ce niveau! """
  171. raise NotImplementedError()
  172. def _structure_validation(self):
  173. for model in self.models:
  174. v = ExtendedValidator(model.schema, purge_unknown=True, error_handler=CerberusErrorHandler, require_all=True)
  175. for item in self.dataset[model]:
  176. v.validate(item.__dict__)
  177. for field, verrors in v.errors.items():
  178. for err in verrors:
  179. self.log_error(DataError(_translate_messages(err), filename=model.filename, field=field))
  180. @classmethod
  181. def _technical_validation(cls):
  182. raise NotImplementedError()
  183. def build_report(self, schema, filename):
  184. report = {}
  185. report["schema"] = schema
  186. report["filename"] = filename
  187. report["exec_time"] = "{:.3g} s.".format(self.dt)
  188. report["checkpoints"] = [{"name": chk.name, "valid": chk.valid} for chk in self.checkpoints]
  189. report["errors"] = {}
  190. for err in self.errors:
  191. if not err.name in report["errors"]:
  192. report["errors"][err.name] = {"help": err.help, "order_": err.order_, "list": []}
  193. err_report = {"filename": err.filename or "-",
  194. "field": err.field or "-",
  195. "message": err.message}
  196. if err_report not in report["errors"][err.name]["list"]:
  197. report["errors"][err.name]["list"].append(err_report)
  198. return report
  199. class NetgeoValidator(BaseValidator):
  200. def _load_files(self, folder):
  201. for model in self.models:
  202. filename = model.filename
  203. path_ = Path(folder) / filename
  204. if not path_.isfile():
  205. self.log_error(MissingFile("Fichier manquant: '{}'".format(filename)))
  206. continue
  207. self.dataset[model] = []
  208. try:
  209. ds = gis_.Datasource(path_)
  210. layer = ds.layer
  211. if layer.srid != SRID:
  212. self.log_error(WrongSrid("Mauvaise projection: {} (attendu: {})".format(layer.srid, SRID)))
  213. for feature in layer:
  214. item = model(feature)
  215. self.dataset[model].append(item)
  216. except IOError:
  217. self.log_error(UnreadableFile("Fichier illisible: {}".format(path_.name)))
  218. def _structure_validation(self):
  219. for model in self.models:
  220. v = ExtendedValidator(model.schema, purge_unknown=True, error_handler=CerberusErrorHandler, require_all=True)
  221. xmin, ymin, xmax, ymax = model.bounding_box
  222. for item in self.dataset[model]:
  223. # geom type
  224. if item.geom_type != model.geom_type:
  225. self.log_error(GeomTypeError("Type de géométrie invalide: {} (attendu: {})".format(item.geom_name, gis_.GEOM_NAMES[model.geom_type]), filename=model.filename, field="geom"))
  226. # bounding box
  227. x1, y1, x2, y2 = item.bounding_box
  228. if any(x < xmin or x > xmax for x in (x1, x2)) or \
  229. any(y < ymin or y > ymax for y in (y1, y2)):
  230. self.log_error(BoundingBoxError("Situé hors de l'emprise autorisée", filename=model.filename, field="geom"))
  231. v.validate(item.__dict__)
  232. for field, verrors in v.errors.items():
  233. for err in verrors:
  234. self.log_error(DataError(_translate_messages(err), filename=model.filename, field=field))
  235. def _technical_validation(self):
  236. # construction des index
  237. arteres = self.dataset[Artere]
  238. cables = self.dataset[Cable]
  239. tranchees = self.dataset[Tranchee]
  240. noeuds = {}
  241. for noeud in self.dataset[Noeud]:
  242. if not noeud.NO_NOM in noeuds:
  243. noeuds[noeud.NO_NOM] = noeud
  244. else:
  245. self.log_error(UniqueError("Doublons dans le champs: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
  246. equipements = {}
  247. for equipement in self.dataset[Equipement]:
  248. if not equipement.EQ_NOM in equipements:
  249. equipements[equipement.EQ_NOM] = equipement
  250. else:
  251. self.log_error(UniqueError("Doublons dans le champs: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
  252. zapbos = {}
  253. for zapbo in self.dataset[Zapbo]:
  254. if not zapbo.ID_ZAPBO in zapbos:
  255. zapbos[zapbo.ID_ZAPBO] = zapbo
  256. else:
  257. self.log_error(UniqueError("Doublons dans le champs: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
  258. # contrôle de la validité des géométries
  259. for artere in arteres:
  260. if not artere.valid:
  261. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(artere), filename=Artere.filename, field="geom"))
  262. for tranchee in tranchees:
  263. if not tranchee.valid:
  264. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(tranchee), filename=Tranchee.filename, field="geom"))
  265. for cable in cables:
  266. if not "baguette" in cable.CA_COMMENT.lower() and not cable.valid:
  267. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(cable), filename=Cable.filename, field="geom"))
  268. for noeud in noeuds.values():
  269. if not noeud.valid:
  270. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(noeud), filename=Noeud.filename, field="geom"))
  271. for equipement in equipements.values():
  272. if not equipement.valid:
  273. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(equipement), filename=Equipement.filename, field="geom"))
  274. for zapbo in zapbos.values():
  275. if not zapbo.valid:
  276. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
  277. # rattachement les noeuds aux artères
  278. for artere in arteres:
  279. try:
  280. artere.noeud_a = noeuds[artere.AR_NOEUD_A]
  281. except KeyError:
  282. artere.noeud_a = None
  283. self.log_error(RelationError("Le noeud '{}' n'existe pas".format(artere.AR_NOEUD_A), filename=Artere.filename, field="AR_NOEUD_A"))
  284. try:
  285. artere.noeud_b = noeuds[artere.AR_NOEUD_B]
  286. except KeyError:
  287. artere.noeud_b = None
  288. self.log_error(RelationError("Le noeud '{}' n'existe pas".format(artere.AR_NOEUD_B), filename=Artere.filename, field="AR_NOEUD_A"))
  289. # rattachement des equipements aux cables
  290. for cable in cables:
  291. try:
  292. cable.equipement_a = equipements[cable.CA_EQ_A]
  293. except KeyError:
  294. cable.equipement_a = None
  295. self.log_error(RelationError("L'équipement '{}' n'existe pas".format(cable.CA_EQ_A), filename=Cable.filename, field="CA_EQ_A"))
  296. try:
  297. cable.equipement_b = equipements[cable.CA_EQ_B]
  298. except KeyError:
  299. cable.equipement_b = None
  300. self.log_error(RelationError("L'équipement '{}' n'existe pas".format(cable.CA_EQ_B), filename=Cable.filename, field="CA_EQ_B"))
  301. # rattachement des equipements aux noeuds
  302. for equipement in equipements.values():
  303. try:
  304. equipement.noeud = noeuds[equipement.EQ_NOM_NOE]
  305. except KeyError:
  306. equipement.noeud = None
  307. self.log_error(RelationError("Le noeud '{}' n'existe pas".format(equipement.EQ_NOM_NOE, equipement.EQ_NOM), filename=Equipement.filename, field="EQ_NOM_NOE"))
  308. # verifie que tous les equipements sont l'equipement B d'au moins un cable
  309. equipements_b = [cable.CA_EQ_B for cable in cables]
  310. for eq_id in equipements:
  311. if equipements[eq_id].EQ_TYPE == "BAI":
  312. continue
  313. if not eq_id in equipements_b:
  314. self.log_error(RelationError("L'equipement '{}' n'est l'équipement B d'aucun cable".format(eq_id), filename=Equipement.filename, field="EQ_NOM"))
  315. # controle des doublons graphiques
  316. for i, tranchee in enumerate(tranchees):
  317. for other in tranchees[i+1:]:
  318. if tranchee.geom == other.geom:
  319. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée".format(tranchee), filename=Tranchee.filename, field="geom"))
  320. for i, artere in enumerate(arteres):
  321. for other in arteres[i+1:]:
  322. if artere.geom == other.geom:
  323. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(artere), filename=Artere.filename, field="geom"))
  324. for i, cable in enumerate(cables):
  325. for other in cables[i+1:]:
  326. if cable.geom == other.geom and cable.CA_EQ_A == other.CA_EQ_A and cable.CA_EQ_B == other.CA_EQ_B:
  327. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(cable), filename=Cable.filename, field="geom"))
  328. ls_noeuds = list(noeuds.values())
  329. for i, noeud in enumerate(ls_noeuds):
  330. for other in ls_noeuds[i+1:]:
  331. if noeud.geom == other.geom:
  332. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(noeud), filename=Noeud.filename, field="geom"))
  333. del ls_noeuds
  334. ls_zapbos = list(zapbos.values())
  335. for i, zapbo in enumerate(ls_zapbos):
  336. for other in ls_zapbos[i+1:]:
  337. if zapbo.geom == other.geom:
  338. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(zapbo), filename=Zapbo.filename, field="geom"))
  339. del ls_zapbos
  340. # Arteres: comparer la géométrie à celle des noeuds
  341. for artere in arteres:
  342. if not artere.noeud_a or not artere.noeud_b:
  343. continue
  344. buffer_a, buffer_b = artere.points[0].Buffer(TOLERANCE), artere.points[-1].Buffer(TOLERANCE)
  345. if not (buffer_a.Contains(artere.noeud_a.points[0]) and buffer_b.Contains(artere.noeud_b.points[0])) \
  346. and not (buffer_a.Contains(artere.noeud_b.points[0]) and buffer_b.Contains(artere.noeud_a.points[0])):
  347. self.log_error(MissingItem("Pas de noeud aux coordonnées attendues ('{}')".format(artere), filename=Artere.filename, field="geom"))
  348. # Cables: comparer la géométrie à celle des equipements (on utilise en fait la geom du noeud correspondant à l'équipement)
  349. for cable in cables:
  350. if not cable.equipement_a or not cable.equipement_b or not cable.valid or not cable.equipement_a.noeud or not cable.equipement_b.noeud:
  351. continue
  352. buffer_a, buffer_b = cable.points[0].Buffer(TOLERANCE), cable.points[-1].Buffer(TOLERANCE)
  353. if not (buffer_a.Contains(cable.equipement_a.noeud.points[0]) and buffer_b.Contains(cable.equipement_b.noeud.points[0])) \
  354. and not (buffer_a.Contains(cable.equipement_b.noeud.points[0]) and buffer_b.Contains(cable.equipement_a.noeud.points[0])):
  355. self.log_error(MissingItem("Pas d'equipement aux coordonnées attendues ('{}')".format(cable), filename=Cable.filename, field="geom"))
  356. del buffer_a, buffer_b
  357. # Verifie que chaque tranchée a au moins une artère
  358. arteres_emprise = Feature.buffered_union(arteres, TOLERANCE)
  359. for tranchee in tranchees:
  360. if not arteres_emprise.Contains(tranchee.geom):
  361. self.log_error(MissingItem("Tranchée sans artère ('{}')".format(tranchee), filename=Tranchee.filename, field="-"))
  362. # Verifie que chaque cable a au moins une artère (sauf si commentaire contient 'baguette')
  363. for cable in cables:
  364. if "baguette" in cable.CA_COMMENT.lower() or not cable.valid:
  365. continue
  366. if not arteres_emprise.Contains(cable.geom):
  367. self.log_error(MissingItem("Cable sans artère ('{}')".format(cable), filename=Cable.filename, field="-"))
  368. del arteres_emprise
  369. # Verifie que chaque artère a au moins un cable (sauf si commentaire contient un de ces mots 'racco client adductio attente bus 'sans cable'')
  370. cables_emprise = Feature.buffered_union(cables, TOLERANCE)
  371. for artere in arteres:
  372. if any(x in artere.AR_COMMENT.lower() for x in ['racco','client','adductio','attente','bus','sans cable']):
  373. continue
  374. if not cables_emprise.Contains(artere.geom):
  375. self.log_error(MissingItem("Artère sans cable ('{}')".format(artere), filename=Artere.filename, field="-"))
  376. del cables_emprise
  377. # Contrôle des dimensions logiques
  378. for artere in arteres:
  379. try:
  380. if not int(artere.AR_FOU_DIS) <= int(artere.AR_NB_FOUR):
  381. self.log_error(DimensionError("Le nombre de fourreaux disponibles doit être inférieur au nombre total ('{}')".format(artere), filename=Artere.filename, field="AR_FOU_DIS"))
  382. except (TypeError, ValueError):
  383. pass
  384. for cable in cables:
  385. try:
  386. if not int(cable.CA_NB_FO_U) <= int(cable.CA_NB_FO):
  387. self.log_error(DimensionError("Le nombre de fourreaux utilisés doit être inférieur au nombre total ('{}')".format(cable), filename=Cable.filename, field="CA_NB_FO_U"))
  388. if not int(cable.CA_NB_FO_D) <= int(cable.CA_NB_FO):
  389. self.log_error(DimensionError("Le nombre de fourreaux disponibles doit être inférieur au nombre total ('{}')".format(cable), filename=Cable.filename, field="CA_NB_FO_D"))
  390. except (TypeError, ValueError):
  391. pass
  392. ant_db = mn.ANTDb_0()
  393. ant_db.execute("alter session set NLS_NUMERIC_CHARACTERS = '.,';") # definit le separateur decimal sur '.'
  394. # Toutes les zapbo contiennent au moins une prise
  395. for zapbo in zapbos.values():
  396. if len(zapbo.points) >= 499:
  397. # passe l'erreur provoquée par la limite au nombre d'arguments en SQL
  398. zapbo.nb_prises = None
  399. continue
  400. sql = """Select SUM(NB_PRISE) AS NB_PRISES FROM SIG_ANT.FTTH_MN_PRISE_LOT z
  401. WHERE SDO_INSIDE(z.GEOMETRY,
  402. SDO_GEOMETRY(2003, 3949, SDO_POINT_TYPE(null,null,null), SDO_ELEM_INFO_ARRAY(1,1003,1), SDO_ORDINATE_ARRAY({}))
  403. )='TRUE';""".format(", ".join(["{},{}".format(p.GetX(), p.GetY()) for p in zapbo.points]))
  404. zapbo.nb_prises = int(ant_db.first(sql).NB_PRISES)
  405. if not zapbo.nb_prises:
  406. self.log_error(MissingItem("La Zapbo ne contient aucune prise: {}".format(zapbo), filename=Zapbo.filename, field="-"))
  407. # Toutes les prises de la ou les ZAPM impactées sont dans une zapbo
  408. zapms = {}
  409. # > on déduit la liste des zapms à partir de la position des zapbos
  410. for zapbo in zapbos.values():
  411. centre = zapbo.geom.Centroid()
  412. zapm = ant_db.first("""SELECT z.ID_ZAPM
  413. FROM SIG_ANT.FTTH_MN_ZAPM z
  414. WHERE sdo_contains(z.GEOMETRY,
  415. SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL)) = 'TRUE'
  416. """.format(centre.GetX(), centre.GetY()))
  417. try:
  418. zapms[zapm.ID_ZAPM].append(zapbo)
  419. except KeyError:
  420. zapms[zapm.ID_ZAPM] = [zapbo]
  421. for id_zapm in zapms:
  422. zapm_couverture = Feature.union(zapms[id_zapm])
  423. for prise in ant_db.read("""SELECT t.X AS x, t.Y AS y
  424. FROM SIG_ANT.FTTH_MN_PRISE_LOT z,
  425. TABLE(SDO_UTIL.GETVERTICES(z.GEOMETRY)) t
  426. WHERE T_ETAT<>'OBSOLETE' AND ID_ZAPM_PARTIELLE='{}';""".format(id_zapm)):
  427. point = ogr.Geometry(ogr.wkbPoint)
  428. point.AddPoint(prise.x, prise.y)
  429. if not zapm_couverture.Contains(point):
  430. self.log_error(MissingItem("Certaines prises de la ZAPM ne sont pas comprises dans une ZAPBO: {}".format(id_zapm), filename=Zapbo.filename, field="-"))
  431. # Verifier que chaque equipement de type PBO est contenu dans une zapbo, et que le nom de la zapbo contient le nom de l'equipement
  432. for equipement in equipements.values():
  433. if not equipement.EQ_TYPE == "PBO":
  434. continue
  435. #zapbos englobant l'equipement
  436. candidates = []
  437. for zapbo in zapbos.values():
  438. if zapbo.geom.Contains(equipement.geom):
  439. candidates.append(zapbo)
  440. # le pbo doit être contenu dans une zapbo
  441. if not candidates:
  442. self.log_error(MissingItem("Le PBO n'est contenu dans aucune ZAPBO: {}".format(equipement), filename=Equipement.filename, field="geom"))
  443. continue
  444. # On se base sur le nom pour trouver la zapbo correspondante
  445. try:
  446. equipement.zapbo = next((z for z in candidates if equipement.EQ_NOM in z.ID_ZAPBO))
  447. except StopIteration:
  448. self.log_error(MissingItem("Le nom du PBO ne coincide avec le nom d'aucune des ZAPBO qui le contient: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
  449. break
  450. # Controle du dimensionnement des PBO
  451. if equipement.zapbo.nb_prises is not None:
  452. if equipement.EQ_TYPE_PH == 'PBO 6' and not equipement.zapbo.nb_prises < 6:
  453. self.log_error(DimensionError("Le PBO 6 contient plus de 5 prises: {}".format(equipement), filename=Equipement.filename, field="-"))
  454. if equipement.EQ_TYPE_PH == 'PBO 12' and not equipement.zapbo.nb_prises >= 6 and equipement.zapbo.nb_prises <= 8:
  455. self.log_error(DimensionError("Le PBO 12 contient mois de 6 prises ou plus de 8 prises: {}".format(equipement), filename=Equipement.filename, field="-"))
  456. if equipement.zapbo.STATUT == "REC" and not equipement.EQ_STATUT == "REC":
  457. self.log_error(TechnicalValidationError("Le statut du PBO n'est pas cohérent avec le statut de sa ZAPBO: {}".format(equipement), filename=Equipement.filename, field="-"))
  458. if equipement.EQ_STATUT == "REC" and not equipement.zapbo.STATUT == "REC" and not equipement.zapbo.ID_ZAPBO[:4].lower() == "att_":
  459. self.log_error(TechnicalValidationError("Le statut du PBO n'est pas cohérent avec le statut de sa ZAPBO: {}".format(equipement), filename=Equipement.filename, field="-"))
  460. # Contrôler dans la base si des éléments portant ces codes existent à des emplacements différents
  461. for noeud in noeuds.values():
  462. sql = """SELECT z.NO_NOM, SDO_GEOM.SDO_DISTANCE(z.GEOMETRY, SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL),0.005) AS DIST
  463. FROM SIG_ANT.FTTH_MN_GR_NOEUD_GEO z
  464. WHERE z.NO_NOM='{}';""".format(noeud.geom.GetX(), noeud.geom.GetY(), noeud.NO_NOM)
  465. existing = ant_db.first(sql)
  466. if existing:
  467. if existing.DIST > TOLERANCE and existing.DIST < 20:
  468. self.log_error(PositionError("La position du noeud ne correspond pas à l'existant: {}".format(noeud), filename=Noeud.filename, field="geom"))
  469. elif existing.DIST > 20:
  470. self.log_error(DuplicatedGeom("Un noeud portant ce nom existe déjà ailleurs sur le territoire: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
  471. for zapbo in zapbos.values():
  472. sql = """SELECT z.ID_ZAPBO, SDO_GEOM.SDO_DISTANCE(SDO_GEOM.SDO_CENTROID(z.GEOMETRY,0.005), SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL),0.005) AS DIST
  473. FROM SIG_ANT.FTTH_MN_GR_ZAPBO_GEO z
  474. WHERE z.ID_ZAPBO='{}';""".format(zapbo.geom.Centroid().GetX(), zapbo.geom.Centroid().GetY(), zapbo.ID_ZAPBO)
  475. existing = ant_db.first(sql)
  476. if existing:
  477. if existing.DIST > TOLERANCE and existing.DIST < 20:
  478. self.log_error(PositionError("La position de la ZAPBO ne correspond pas à l'existant: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
  479. elif existing.DIST > 20:
  480. self.log_error(DuplicatedGeom("Une ZAPBO portant ce nom existe déjà ailleurs sur le territoire: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
  481. # Contrôle si un equipement portant ce nom existe, mais associé à un noeud différent
  482. for equipement in equipements.values():
  483. sql = """SELECT z.EQ_NOM, z.EQ_NOM_NOEUD
  484. FROM SIG_ANT.FTTH_MN_GR_EQ_PASSIF z
  485. WHERE z.EQ_NOM='{}';""".format(equipement.EQ_NOM)
  486. existing = ant_db.first(sql)
  487. if existing and existing.EQ_NOM_NOEUD != equipement.EQ_NOM_NOE:
  488. self.log_error(DuplicatedGeom("Un équipement portant ce nom ({}) existe déjà et est associé à un noeud différent ({})".format(equipement.NO_NOM, existing.EQ_NOM_NOEUD), filename=Noeud.filename, field="geom"))