Licence CC BY-SA

Quelques statistiques sur le champ "opening_hours" d'OpenStreetMap

Ou l'écart entre la théorie et la pratique

Publié :
Auteur :
Catégorie :
Temps de lecture estimé : 15 minutes

Vous l’avez peut-être vu passer si vous suivez les forums, je développe depuis quelques temps un module Python nommé Humanized Opening Hours servant à parser les champs opening_hours du projet OpenStreetMap.

En effet, le projet OpenStreetMap propose un champ ainsi nommé permettant de spécifier les heures d’ouverture d’un lieu, quel qu’ils soit (un magasin, un parking…), les heures auxquelles une route est éclairée, etc. Pour ce faire, des spécifications complètes et particulièrement chiadées indiquent un format d’écriture de ces informations, afin qu’elles soit utilisables par un programme pour déterminer si le lieu est ouvert ou non.

En développant ce module, il m’est venu le besoin de comparer ses possibilités à la réalité afin de mieux prioriser les tâches. À quoi sert-il de savoir parser un schéma très peu utilisé avant de savoir réaliser les opérations principales sur les champs les plus courants ? Parcourant manuellement quelques-uns de ces champs sur les magasins taggés par chez moi, j’ai ensuite commencé à regarder sur une liste un peu plus grande : celle de tous les tags opening_hours de la base d’OpenStreetMap, disponible ici. Pour aller plus vite, j’ai téléchargé ces données en JSON, que j’ai converti en un CSV contenant deux colonnes : le champ en question et son nombre d’apparition dans la base. Ainsi, pour tester mon module avec les 100 champs les plus utilisés, il me suffisait de le lancer sur les 100 premières lignes du CSV.

Seulement, sur une base de données ouverte et aussi grande, on peut s’attendre à ce que tout ne se passe pas aussi bien que dans la théorie. Constatant des erreurs fréquentes dans le champs, je commence à faire quelques essais sur de grands ensembles de champs. Pour faciliter ce processus, j’ai fais une base de données utilisant le module Peewee pour stocker les champs et les parties qui les composent. Je me suis alors retrouvé devant un outil ayant le potentiel de réaliser des statistiques rapidement sur un très grand ensemble de données. Je m’y suis mis par curiosité, je me suis dit que ça méritait un petit billet. :)

Format des champs "opening_hours"

Un champ opening_hours peut posséder une ou plusieurs parties, qui spécifient chacune, soit un commentaire, soit une ou plusieurs périodes d’ouverture pour une période temporelle donnée. Chaque partie est séparée par un point-virgule suivi d’une espace.

Les heures sont écrites au format HH:MM, de 0 à 24 heures. Les jours de la semaine s’écrivent par les deux premières lettres de leurs noms en anglais (« Mo » pour « Monday » (lundi), « Tu » pour « Tuesday » (mardi), etc). Les noms des mois s’écrivent avec les trois premières lettres de leurs noms (« Jan » pour « January » (janvier)). Une période d’ouverture s’écrit par deux heures séparées par un tiret. Pour indiquer plusieurs périodes d’ouverture, on les sépare par des virgules.

Ainsi, voici comment écrire « ouvert du lundi au vendredi, de 10h à 12h et de 13h à 19h » : Mo-Fr 10:00-12:00,13:00-19:00. Pour ajouter « ouvert le samedi de 10h à 12h », on écrirait ceci : Mo-Fr 10:00-12:00,13:00-19:00; Sa 10:00-12:00.

Mais les spécifications vont beaucoup plus loin. Ainsi est-il possible d’écrire des choses aussi précises que…

  • « De janvier à Août, ouvert du lundi au vendredi, du lever du soleil jusqu’à une heure et quart après le coucher du soleil » : Jan-Aug Mo-Fr sunrise-(sunset+01:15).
  • « De 2010 à 2020, tous les deux ans, en septembre, les lundis, ouvert de 20h jusqu’au lendemain matin à 2h » : 2010-2020/2 Sep Mo 20:00-02:00.
  • « Ouvert en continu (de minuit à minuit) de 18 jours après Pâques jusqu’au 13 Août » : easter + 18 days-Aug 13: 00:00-24:00.

Enfin, il est possible d’ajouter des commentaires, constitués par du texte libre entouré par des guillemets : "Sur réservation" ou Mo-Fr 10:00-20:00 "Fermé en cas de neige".

Usage des champs dans la base OpenStreetMap

Selon la page citée un peu plus haut, la valeur la plus utilisée de toutes est 24/7, qui signifie « ouvert 7 jours sur 7 et 24 heures sur 24 », elle apparait 177 888 fois (au moment de l’écriture de ce billet ; le fichier CSV mentionné ayant été créé environ deux semaines plus tôt, ces chiffres risquent de varier un peu dans les exemples suivants).

Les trois suivants sont ceux-ci : Mo-Su 09:00-21:00 et Mo-Sa 08:00-18:00 et Mo-Su 10:00-22:00, ils apparaissent respectivement 12 153, 9 720 et 9 465 fois. La dizaine de champs suivants sont très similaires. Au total, on recense 1 198 393 enregistrements de champs opening_hours.

Mais, en voyant ces exemples, on constate tout de suite quelque chose : le champ le plus utilisé l’est beaucoup plus que celui en seconde position. En fait, le champ le plus utilisé l’est 14,6 fois plus que le second ! Le second est quant à lui 1,25 fois plus utilisé que le troisième. On ne suit donc pas du tout la loi de Pareto (20% des champs les plus courants prennent 80% de la base de données) ou de Zipf (chaque champ est deux moins utilisé que le champ de niveau supérieur).

Validité des champs

Qui dit base de données en accès libre dit évidemment erreurs, et bien mal avisé serait celui qui tenterait de parser des champs de la base d’OpenStreetMap sans gestion d’erreurs.

Sur les 347 968 champs enregistrés dans le CSV (qui sont en fait les 1,2 millions de champs, comptés sans leur multiplicité), 252 504 (soit environ 72%) ont pu être parsés par le module. Un peu plus d’un quart d’entre-eux ne l’a donc pas été. Cependant, il faut prendre en compte le fait que le module HOH sus-cité n’est pas un parser "parfait", au sens où celui-ci ne peut parser que des champs qui indiquent des horaires d’ouverture connues.

Autrement dit, il ne peut pas parser des champs contenant des commentaires, des plages temporelles imprécises (Mo-Fr 10:00+ indique une ouverture du lundi au vendredi, de 10h jusqu’à une heure inconnue ou indéfinie) ou certains formats complexes (et rares), tels que Su[-1] 09:00-12:00 (indique une ouverture tous les derniers dimanches de chaque mois, de 9h à 12h). Malgré tout, cela laisse un grand nombre de champs qui ne respectent pas les spécifications.

Validité des règles

Les sous-parties d’un champ sont nommées "rules" (règles) par les spécifications. Par commodité, je reprendrais ce nom par la suite.

Parmi tous les champs, on dénombrait 239 356 règles. Parmi elles, 74 262 n’ont pas pu êtres parsées (le parsing se faisait pour chaque règle de chaque champ, et non pour un champ entier). En voici quelques-unes.

  • off et closed sont très courantes, mais ne sont guère signifiantes, puisqu’elles indiquent une fermeture permanente (dans ce cas, préciser des horaires d’ouverture semble peu utile).
  • Un schéma qui revient très souvent est celui des heures sous la forme H:MM, telles que 8:00-22:00 ou 7:00-23:00. C’est un problème qui est relativement simple à corriger (dans le cas du module sus-cité, une expression régulière corrige cela automatiquement), mais qui est très courant. Parmi les règles qui n’ont pas pu être parsées, ce format apparait 28 333 fois !
  • On retrouve plusieurs écritures qui ressemblent aux spécifications, mais qui semblent être plus proches du langage naturel, tel que 24 Hours (probablement pour 24/7).
  • Les noms des jours semblent poser problème, que ce soit pour la casse (on voit des champs tels que mo-fr 09:00-17:00, au lieu de Mo-Fr 09:00-17:00) ou pour la langue (lundi-samedi ou encore Lu-Do 06:00-23:00 (lunes et domingo, soit lundi et dimanche en espagnol)).
  • De nombreux champs utilisent le format "AM"/"PM" au lieu du format 24 heures, ce qui donne des champs tels que 9:00 am - 07:00 pm.
  • Beaucoup d’expressions qui devraient être des commentaires sont écrites telles-quelles, en langage naturel. Par exemple : Ouvert 24 heures sur 24.
  • Cas particulier du cas précédent : de nombreux champs sont écrits en langues asiatiques, tels que 17時~23時半 (« 17h00 - 23h30 » d’après Google) ou 월 - 토 10:00 - 24:00, 매 2,4째 일요일 휴무 (« Du lundi au samedi de 10h00 à minuit, fermé le 2ème et le 2ème dimanche »).
  • On retrouve parfois des adresses de sites web (alors qu’un champ dédié, website, est proposé par le wiki d’OpenStreetMap…).

On remarque également que 559 champs ont des règles identiques qui apparaissent plusieurs fois (par exemple, SH closed; Mo-Fr 10:00-20:00; SH closed, SH signifiant "School holidays", c’est-à-dire les vacances scolaires).

Schémas des règles

Pour les champs qui ont pu être parsés, j’ai modifié le code interprétant l’arbre syntaxique du champ pour qu’il retourne une chaine de caractère représentant le format utilisé par cette règle, d’après les spécifications. Par exemple, une règle qui indique 10:00-16:00 (ouvert tous les jours de 10h à 16h) est transformée en :hour_minutes: :hour_minutes:. Ainsi, cette règle est fusionnée avec d’autres comme 02:00-10:00 ou 13:00-22:00, qui présentent le même format.

165 094 règles ont pu être parsées. En les transformant pour obtenir leurs schémas, et en fusionnant celles qui ont des schémas identiques, on obtient seulement 443 règles différentes. Autrement dit, parmi les règles indiquant des horaires d’ouverture valides, on retrouve 443 formats différents dans la base de données d’OpenStreetMap.

Conclusion

Merci de m’avoir lu ! N’hésitez surtout pas à poster vos questions et remarques !

C’est la première fois que j’écris sur le sujet, donc il se peut que ce billet soit confus et désordonné, voire indigeste. Je vous prie de m’excuser si c’est le cas.

N’ayant pas que cela à faire en ce moment, et ayant des connaissances très limitées en SQL, je n’ai pour l’instant pas pu pousser les recherches plus loin, mais il est tout à fait possible que je sorte d’autres billets comme celui-ci, si je découvre des choses intéressantes.

J’aimerais partager l’intégralité de la base de données avec vous, mais celle-ci prenant près de 115Mo, son partage se révèlerait assez compliqué. Voici en revanche le code ayant permis de la créer (attention, c’est un peu quick and dirty). Il nécessite le module osm-humanized-opening-hours à la version 1.0.0a3.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
import csv
import sys
import datetime

import peewee as pw
import lark

import humanized_opening_hours as hoh


db = pw.SqliteDatabase('fields.db')


class Field(pw.Model):
    count = pw.IntegerField()
    original = pw.CharField()
    sanitized = pw.CharField()
    
    class Meta:
        database = db
    
    def __str__(self):
        return "{} ({}) ({} rules) (ID: {})".format(
            self.original, self.count, self.rules.count(), self.id
        )


class Rule(pw.Model):
    fields = pw.ManyToManyField(Field, backref="rules")
    pattern = pw.CharField()
    original = pw.CharField(unique=True)
    is_comment = pw.BooleanField()
    is_sanitized = pw.BooleanField(default=False)
    
    class Meta:
        database = db
    
    def __str__(self):
        return "{} ({}) ({} fields)".format(
            self.pattern, self.original, self.fields.count()
        )


FieldRule = Rule.fields.get_through_model()


class StatsTransformer(lark.Transformer):
    def time_domain(self, args):
        return args
    
    def rule_sequence(self, args):
        return ' '.join(args)
    
    def always_open(self, args):
        return ":always_open:"
    
    def selector_sequence(self, args):
        return ' '.join(args)
    
    def range_selectors(self, args):
        return ' '.join(args)
    
    # Dates
    def monthday_selector(self, args):
        return ' '.join(args)
    
    def monthday_range(self, args):
        return ' '.join(args)
    
    def monthday_date_monthday(self, args):
        return ":monthday_date_monthday:"
    
    def monthday_date_day_to_day(self, args):
        return ":monthday_date_day_to_day:"
    
    def monthday_date_month(self, args):
        return ":monthday_date_month:"
    
    def monthday_date_easter(self, args):
        return ":easter:"
    
    def day_offset(self, args):  # TODO : Make usable.
        return ":day_offset:"
    
    # weekday_selector
    def weekday_or_holiday_sequence_selector(self, args):
        try:  # TODO: Check for "[Tree(holiday, [Token(PUBLIC_HOLIDAY, 'PH')])]"
            args = set([tk.value for tk in args[0].children[0].children])
        except AttributeError:
            args = set([tk.value for tk in args[0].children])
        SH, PH = 'SH' in args, 'PH' in args
        if SH:
            args.remove('SH')
        if PH:
            args.remove('PH')
        if args and SH and PH:
            return ":weekdays-and-PH-SH:"
        elif args and SH:
            return ":weekdays-and-SH:"
        elif args and PH:
            return ":weekdays-and-PH:"
        elif args:
            return ":weekdays:"
        elif PH and SH:
            return ":PH-SH:"
        elif PH:
            return ":PH:"
        else:
            return ":SH:"
    
    def holiday_and_weekday_sequence_selector(self, args):
        args = set()
        for tree in args:
            for subtree in tree.children:
                if isinstance(lark.lark.Tree, subtree):
                    for subsubtree in subtree.children:
                        for tk in subtree.children:
                            args.add(tk.value)
                else:  # token
                    for tk in subtree.children:
                        args.add(tk.value)
        
        #print(args)
        # TODO: Check for "[Tree(weekday_sequence, [Tree(weekday_range, [Token(WDAY, 'Su')])]), Tree(holiday, [Token(PUBLIC_HOLIDAY, 'PH')])]"
        #args = set([tk.value for tk in args])
        #args = set([tk.value for tk in args])
        #print()
        #for tree in args:
        #    print(tree)
        #args = set([tk.value for tk in args])
        
        SH, PH = 'SH' in args, 'PH' in args
        if SH:
            args.remove('SH')
        if PH:
            args.remove('PH')
        if args and SH and PH:
            return ":weekdays-and-PH-SH:"
        elif args and SH:
            return ":weekdays-and-SH:"
        elif args and PH:
            return ":weekdays-and-PH:"
        elif args:
            return ":weekdays:"
        elif PH and SH:
            return ":PH-SH:"
        elif PH:
            return ":PH:"
        else:
            return ":SH:"
    
    def holiday_in_weekday_sequence_selector(self, args):
        return ":weekday_in_holidays:"
    
    # Year
    def year(self, args):
        return ' '.join(args)
    
    def year_range(self, args):
        if len(args) == 1:
            return ":year:"
        elif len(args) == 2:
            return ":year-range:"
        else:
            return ":year-range-step:"
    
    def year_selector(self, args):
        return ' '.join(args)
    
    # Week
    def week_selector(self, args):
        return ' '.join(args)
    
    def week(self, args):
        if len(args) == 1:
            return ":week:"
        elif len(args) == 2:
            return ":week-range:"
        else:
            return ":week-range-step:"
    
    # Time
    def time_selector(self, args):
        return ' '.join(args)
    
    def timespan(self, args):
        return ' '.join(args)
    
    def time(self, args):
        return args[0]
    
    def hour_minutes(self, args):
        return ":hour_minutes:"
    
    def variable_time(self, args):
        # ("event", "offset_sign", "hour_minutes")
        kind = args[0].value
        if len(args) == 1:
            return ":variable_time:{}:=:".format(kind)
        offset_sign = args[1].value
        return ":variable_time:{}:{}:".format(kind, offset_sign)
    
    def rule_modifier_open(self, args):
        return ":open:"
    
    def rule_modifier_closed(self, args):
        return ":closed:"


def main():
    db.connect()
    start_time = datetime.datetime.now()
    
    db.create_tables([Field, Rule, FieldRule], safe=True)
    
    if sys.argv[1] == "delete-all-rules":
        q = Rule.delete()
        q.execute()
        print("Done!")
    
    if sys.argv[1] == "write":
        with open("all_fields.csv", 'r') as f:
            all_fields = list(csv.reader(f, quotechar='"', escapechar='\\'))
        
        if True:
            db_fields = []
            with db.atomic():
                for i, f in enumerate(all_fields):
                    field, count, _ = f
                    db_field = Field.create(
                        original=field,
                        sanitized=hoh.sanitize(field),
                        count=count
                    )
                    db_fields.append(db_field)
                    
                    if i % 1000 == 0:
                        print('---', i)
                    
                    if i in (0, 1, 2):
                        print(f)
                        print(db_field, db_field.id)
                        print(db_fields)
                        input("...")
                        print()
            print("Fields adding done!")
            print()
        
        print(Field.raw("select original, id from field")[0].__dict__)
        print()
        
        if True:
            rules = {}  # raw: (pattern, comment)
            transformer = StatsTransformer()
            print("Starts to parse rules.")
            for i, field in enumerate(Field.raw("select original from field")):
                field_rules = [
                    part.strip() for part
                    in field.original.strip(' \n\t;').split(';')
                ]
                for rule in field_rules:
                    if rule not in rules:
                        if '"' in rule:
                            rules[rule] = (':contains-comment:', True)
                            continue
                        try:
                            tree = hoh.frequent_fields.FREQUENT_FIELDS.get(rule)
                            if not tree:
                                tree = hoh.frequent_fields.parse_simple_field(rule)
                            if not tree:
                                tree = hoh.field_parser.PARSER.parse(rule)
                            pattern = transformer.transform(tree)[0]
                            rules[rule] = (pattern, False)
                        except (
                            lark.lexer.UnexpectedInput,
                            lark.common.UnexpectedToken,
                            lark.common.ParseError
                        ) as e:
                            rules[rule] = ('', False)
                
                if i % 500 == 0:
                    print('===', i)
            print("Finished to parse rules!")
            print("Rules: ", len(rules))
            print()
        
        if True:
            with db.atomic():
                for i, (original, (pattern, is_comment)) in enumerate(rules.items()):
                    rule = Rule(
                        pattern=pattern,
                        original=original,
                        is_comment=is_comment
                    )
                    try:
                        rule.save()
                    except pw.IntegrityError:
                        print("Integrity error:", original, (pattern, is_comment))
                        raise
                    
                    if i % 500 == 0:
                        print('~~~', i)
            print("Finished to add rules in DB.")
            print()
        
        if True:
            errors = 0
            with db.atomic():
                for i, field in enumerate(Field.select()):
                    field_rules = [
                        part.strip() for part
                        in field.original.strip(' \n\t;').split(';')
                    ]
                    for rule in field_rules:
                        db_rule = Rule.select().where(Rule.original==rule).get()
                        try:
                            db_rule.fields.add(field)
                            db_rule.save()
                        except:  # When a part appears twice in a field.
                            #print(i, repr(db_rule), repr(field), field_rules)
                            #raise
                            errors += 1
                    
                    if i % 500 == 0:
                        print('***', i)
                print("Errors:", errors)
        
        db.close()
        print()
        print("Fields:", Field.select().count())
        print("Rules: ", len(rules))
        print("Started at:", repr(start_time))
        print("Ended at:", repr(datetime.datetime.now()))
        print("Duration (minutes):", (datetime.datetime.now() - start_time).total_seconds()/60)
        
        print("All done!")

if __name__ == "__main__":
    main()

Et voici un extrait du fichier all_fields.csv, que vous pouvez créer à partir du JSON disponible depuis cette page.

Format : champ,occurrences,fréquence.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
24/7,176048,0.14681567730706716
Mo-Su 09:00-21:00,12040,0.010040788618882853
Mo-Sa 08:00-18:00,9644,0.008042638325623443
Mo-Su 10:00-22:00,9332,0.007782445132177308
Mo-Fr 09:00-17:00,7427,0.006193765537578318
Mo-Su 08:00-22:00,6461,0.00538816738094702
Mo-Fr 08:00-17:00,6260,0.005220542919784607
Mo-Sa 08:00-20:00,6203,0.005173007624828102
Mo-Fr 09:00-18:00,6169,0.005144653238362818
Mo-Su 09:00-22:00,5163,0.004305696980007656
Mo-Su 08:00-20:00,4398,0.00366772328453877
Mo-Su 10:00-20:00,4364,0.0036393688980734863
Mo-Su 10:00-21:00,4250,0.003544298308160476
sunrise-sunset,4067,0.0033916849927738014
Mo-Su 09:00-20:00,3769,0.0031431671349310196
Mo-Su 09:00-18:00,3749,0.003126488084069088
Mo-Su 08:00-23:00,3470,0.0028938153245451413
Mo-Sa 09:00-20:00,3409,0.00284294421941625
09:00-21:00,3386,0.0028237633109250283
08:00-22:00,3377,0.002816257738037159
Mo-Su 07:00-22:00,3356,0.002798744734632131
10:00-22:00,3351,0.002794574971916648
Mo-Sa 09:00-18:00,3301,0.002752877344761819
Mo-Su 08:00-21:00,3299,0.002751209439675626
Mo-Su 06:00-22:00,3162,0.002636957941271394
Mo-Sa 07:00-20:00,3148,0.002625282605668042
Mo-Su 09:00-23:00,3099,0.0025844189310563095
Mo-Fr 08:00-18:00,2984,0.002488514388600202
Mo-Su 07:00-23:00,2941,0.0024526544292470492
Mo-Su 11:00-23:00,2825,0.002355915934247846


6 commentaires

Hello et merci pour cet article intéressant.

Du coup, les erreurs / incohérences / trucs qui font planter le parser que tu as détectés, tu as pu les remonter quelque part ? Ca leur serait certainement utile d’avoir une moulinette pour remettre au carré leur donnée, non ?

Happiness is a warm puppy

+0 -0

Hello et merci pour cet article intéressant.

Du coup, les erreurs / incohérences / trucs qui font planter le parser que tu as détectés, tu as pu les remonter quelque part ? Ca leur serait certainement utile d’avoir une moulinette pour remettre au carré leur donnée, non ?

Javier

À vrai dire, il y en a déjà quelques-unes signalées sur le wiki (ici), mais sinon, je ne sais pas trop où / à qui les signaler. Tu aurais une suggestion ?

Dans le module cité au début, j’ai écris une petite fonction à base de regexes qui peut en corriger pas mal, mais les spécifications sont tellement complexes qu’il me parait difficile de pouvoir corriger certains formats. Mais je vais avoir du temps ces prochaines semaines, donc je pourrais peut-être m’y pencher. :)

"Les accidents dans un système doivent se produire, mais il n’est pas obligatoire qu’ils produisent pour vous et moi." Laurence Gonzales - Deep Survival

+0 -0

Tu aurais une suggestion ?

Non malheureusement c’était une vraie question ouverte :)

Je ne sais pas trop si tu as un point de contact quelque part (une mailing list peut-être ? un google group ?) mais je suis sûr et certain que ça vaut le coup de leur présenter ton travail en mode "voilà, j’ai bricolé ça dans mon coin, ça vous intéresse ou pas ? vous pensez qu’on peut l’industrialiser ?"

Il faut y aller humblement hein, évidemment. "Peut-être que vous avez déjà un truc similaire que j’ai pas trouvé auquel je pourrais contribuer ? et puis j’ai peut-être oublié des trucs dans la spec du format vu qu’elle est très riche" et surtout pas en mode "lol heureusement que je suis là pour corriger vos données dégueulasses", bien évidemment.

Pour moi le taff que t’as produit est vraiment utile. Contrôler la qualité de la donnée ça doit forcément être le nerf de la guerre quand on en a autant…

Happiness is a warm puppy

+0 -0

Pourquoi pas, en effet. Il y a une petite communauté de mappeurs par chez moi que je n’ai pas encore rencontré, ce serait l’occasion. Je vous tiens au jus. :)

En reprenant l’idée de modifier l’arbre syntaxique du champ plutôt que de modifier le champ directement, j’ai aussi commencé à écrire une nouvelle fonction de correction bien plus puissante (et probablement plus lente, on n’a rien sans rien) que l’actuelle. Je rajouterais un commentaire avec le code si ça donne quelque chose d’intéressant.

Merci pour ton retour ! :)

"Les accidents dans un système doivent se produire, mais il n’est pas obligatoire qu’ils produisent pour vous et moi." Laurence Gonzales - Deep Survival

+0 -0

Bonjour

Et du coup, une fois que l’on n’a plus que 443 règles, est-ce qu’on a une répartition du type Pareto ?

Olivier44

Salut. Bonne question ! Cependant, je suis limité par ma connaissance en gestion de base de données. Je peux tenter quelque chose, mais je ne suis pas sur d’y parvenir. Si tu veux la base pour faire tes propres essais, fais-moi signe. ;)

"Les accidents dans un système doivent se produire, mais il n’est pas obligatoire qu’ils produisent pour vous et moi." Laurence Gonzales - Deep Survival

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte