Reserva de places d’Avió

Ens demanen dissenyar un sistema de reserves de les places d’un avió. Les reserves les efectuen un conjunt d’agències de viatges que atenen i gestionen les peticions que li arriben dels clients.

Les dades de les reserves de seients de l’avió es guarden en una base de dades única i centralitzada. Cada agència pot accedir a aquesta base de dades de forma concurrent. Quan s’atén la petició d’un client es cerca una plaça lliure, i si se’n troba, es reserva la plaça guardant en la base de dades, el nom del client i la data i hora en que es fa la reserva.

Per realitzar el sistema podrem comptar amb el disseny de la classe Avió:

La classe Avio

La classe Avió, especificada a continuació, està implementada al fitxer avio.py. Representa avions amb un cert nombre de files i sis seients per fila (lletres “A” a “F”). Cada seient està identificat pel seu número de fila (un enter entre 1 i el nombre de files totals de l’avió) i una lletra entre “A” i “F”. La referència d’un seient serà una etiqueta (str) amb primer el número, i després la lletra. Per exemple, "3B", "25C", …

class avio.Avió(identificador, nFiles=10)

En el cas que no existeixi, crea una base de dades al disc amb el nom de l”identificador (str) amb l’extensió .db. En el cas que aquesta existeixi, no la toca. El paràmetre nFiles és el nombre de files de 6 seients que tindrà el nou avió. En el cas que la base de dades existeixi, no es tindrà en compte nFiles, atès que la base de dades té el seu nombre de files propi.

Atributs

nom

Identificador de l’avió (str)

nFiles

Nombre de files de 6 seients de l’avió.

lliure(seient):

Retorna True si la plaça de l’etiqueta seient (str del tipus "NL" i \(0 \leq N \lt nFiles\) i \('A' \leq L \leq 'F'\)) està lliure. Retorna False en cas contrari. Un seient lliure és un seient que no té cap nom assignat.

places()

Retorna el nombre total de seients de l’avió.

Aquesta classe suporta les operacions següents:

Operació

Resultat

a[s]

Retorna un tuple essent el primer element el nom de la persona que ocupa el seient de l’etiqueta s (str) del tipus "NL" i \(0 \leq N \lt nFiles\) i \('A' \leq L \leq 'F'\)

a[s] = (nom, data)

Assigna el tuple composat per un nom (str) i una data (datetime.datetime) al seient d’etiqueta s (str) del tipus "NL" i \(0 \leq N \lt nFiles\) i \('A' \leq L \leq 'F'\)

Aquesta classe suporta la funció iter() que retorna un iterador d’etiquetes de seients de l’avió ("1A", "1B", ..., "1F", "2A", "2B", ...).

Proveu d’utilitzar aquesta classe. Disposeu d’un doctest per a inspirar-vos a test-avio.txt. Mireu la implementació (avio.py) i observeu que la classe té atributs de classe, i altres mètodes per ús intern. La classe Avió està connectada a una base de dades que guarda informació sobre els seus seients. La gestió de la base de dades es fa usant el mòdul mòdul sqlite3. Cada operació de lectura, escriptura de l’estat d’un seient es guarda sempre a la base de dades que hi ha al disc per no perdre cap dada en el cas que hagi una fallida d’alimentació elèctrica.

El plantejament del disseny serà el següent:

  1. Dissenyar en el fitxer agencia.py un objecte fil anomenat Agència que donat un objecte de classe Avió avió, una llista de noms dels clients procedirà a fer la reserva de seients de l”avió. Mantindrà un registre de les reserves que ha pogut satisfer.

  2. Dissenyar en el fitxer reserves.py el programa principal que crearà un objecte de la classe Avió. Donat un nombre d’agències crearà un fil Agencia per cada agència a considerar, i els engegarà i esperarà la finalització de tots els fils. Farà una estadística per comprovar que no ha hagut cap sobre-reserva comprovant els registres de cada fil i que cap fil ha estat en inanició (comprovant el nombre de reserves que ha fet). Finalment farà un llistat de tots els clients ordenat per data de reserva.

Agencia

En el fitxer agència agencia.py es dissenyarà la classe Agència que heretarà de la classe threading.Thread del mòdul threading:

class agencia.Agència(nomAg, avió, peticions)

Atributs

nomAg

Nom de l’agència de viatges (str)

avió

L’avió on s’han de fer les reserves de places (Avió)

peticions

Una llista (list) de noms (str) de clients que demanen plaça a l’avió.

reserva

Diccionari (dict) amb els noms de cliens com a claus, i la referència del seient (str) com a valor.

Mètodes

run()

Per cada petició de client, es cercarà si hi ha una plaça lliure en l’avió. En el cas que hagi la plaça lliure es reservarà la plaça a nom del client i es donarà d’alta el client en el registre reserva guardant també la referència del seient reservat.

En el mètode __init__() heu de tenir en compte cridar a l’init de Thread.

Reserves

En el fitxer reserves.py cal dissenyar la funció registreReserves():

reserves.registreReserves(idAvio='Boeing714', nFiles=30, nAgencies=10)

Donat la identificació d’un avió idAvio crearà un objecte avió de la classe Avió amb nFiles de 6 seients. Engegarà nAgencies fils. A cada fil se li passarà com a paràmetres, el nom de l’agència, l”avió i, les peticios de clients que ha d’atendre.Un cop han finalitzat els fils, invocarà la funció estadisAgències().

reserves.estadisAgències(avió, agències)

Donat un avió (Avió), i una llista de fils ja executats dek tipus Agència, examina el registre de reserves de places efectuats, fent un comptatge per seient. Si troba que un seient s’ha reservat més d’un cop ho mostra per pantalla. (Amb això controlarem si ha hagut «overbooking»). Compta també el total de reserves dels fils i els compara amb el nombre de places de l’avió per saber si hi ha plena ocupació. També mostra una llista de nombre de reserves fetes per fil. (Amb això podrem controlar si ha hagut inanició d’algun fil). Finalment mostrarà una llista de clients ordenada per data de reserva (Amb això podrem veure com s’ha anat fent sequencialment les reserves concurrentment).

Programa principal

A més, cal que reserves.py pugui invocar-se com un programa amb arguments en la línia de bash (No us oblideu del if __main__... per poder proves en l’íntèrpret de python si cal). Per exemple,

python3 reserves.py Boeing417 50 15

Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing417 de 50 files de 6 seients i competiran per omplir-lo 15 agències de viatge.

$ python3 reserves.py Boeing333 50

Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing333.db de 50 files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge.

$ python3 reserves.py Boeing333

Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing333.db de 30 (valor per omissió) files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge.

$ python3 reserves.py

Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing714.db de 30 (valor per omissió) files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge.

Si hi han més de tres arguments, mostrarà el missatge:

$ python3 reserves.py 1 2 3 4
Ús: python3 reserves.py [nomAvió] [nombre de files] [nombre d'agències]

Notes

A més de l’aportació de la classe Avió, aportem en el fitxer esborrany.py el disseny de la funció estadisAgències().

Generació automàtica de peticions

Exemple d’una llista de 10 fils amb generació automàtica de noms d’agències i llista de noms de clients (els noms de clients són números (str), cada agència té prous clients per omplir l’avió):

agencies = [Agència(f'age{i:03d}',
            avió,
            map(str, range(i*nPlaces , i*nPlaces + nPlaces))) for i in range(10)]

Visualització de les bases de dades d’Avió

Disposeu del programa mostra.py per visualitzar el contingut de la base de dades de l’avió. Per exemple, per mostrar el contingut de la base de dades Boeing714.db, feu:

python3 mostra.py Boeing714

Si us equivoqueu de nom us crearà una base de dades buida amb el nom equivocat.

Proves

Per les proves, recordeu que la base de dades guarda els resultats produits en anteriors sessions. Per exemple, si en l’avió està tot reservat, les agències no podran reservar res. Cal esborrar la base de dades corresponent.

Protecció de zones crítiques

Si no heu posat cap mecanisme de protecció (panys, semàfors) de les zones crítiques en el disseny us sortirà que alguns seients estan reservats més d’un cop, iper tant, no teniu un disseny correcte de l’aplicació. Cal protegir la modificació de les dades de l’avió amb semàfors (threading.Semaphore) o panys (threading.Lock). Heu de vigilar que el mecanisme de protecció a més d’evitar que un seient sigui reservat més d’un cop, no deixi en inanició cap fil-agència. Les lectures o consultes d’un seient de forma concurrent no son cap problema. En canvi la modificació d’un seient lliure per fer una reserva ha de fer-se de forma atòmica.

Un exemple de la comanda (executada sempre sense cap Boeing714.db existent) per eveure els efectes de protecció/no protecció de les zones crítiques:

produeix els següent resultat a pantalla

sense protecció zona crítica:

Exception in thread Thread-8:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
    self.avio[s] = (p, datetime.datetime.now())
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 77, in __setitem__
    self.connexió.commit()
sqlite3.DatabaseError: no more rows available

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
    self.avio[s] = (p, datetime.datetime.now())
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 76, in __setitem__
    c.execute(self.modifica, (nom, data, seient))
sqlite3.DatabaseError: another row available

Exception in thread Thread-5:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
    self.avio[s] = (p, datetime.datetime.now())
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 77, in __setitem__
    self.connexió.commit()
sqlite3.DatabaseError: no more rows available

Exception in thread Thread-9:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
    self.avio[s] = (p, datetime.datetime.now())
  File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 77, in __setitem__
    self.connexió.commit()
sqlite3.DatabaseError: no more rows available

Seients amb més d'una reserva:
1B 2
1D 2
1E 2
4E 2
7A 2
55 places OK de 60
Reserves per agència
age000:      9
age001:      0
age002:     11
age003:     12
age004:      1
age005:     10
age006:     10
age007:      0
age008:      1
age009:     11
Clients per ordre temporal de reserva:
seient: client                   data
    1A: 0                        2021-04-15 11:35:56.415365
    1B: 180                      2021-04-15 11:35:56.429113
    1C: 300                      2021-04-15 11:35:56.438607
    1D: 120                      2021-04-15 11:35:56.454074
    1E: 480                      2021-04-15 11:35:56.475228
    1F: 301                      2021-04-15 11:35:56.485902
    2A: 181                      2021-04-15 11:35:56.493298
    2B: 121                      2021-04-15 11:35:56.507818
    2C: 541                      2021-04-15 11:35:56.518379
    2D: 361                      2021-04-15 11:35:56.528985
    2E: 481                      2021-04-15 11:35:56.543518
    2F: 182                      2021-04-15 11:35:56.558052
    3A: 1                        2021-04-15 11:35:56.566463
    3B: 542                      2021-04-15 11:35:56.574433
    3C: 122                      2021-04-15 11:35:56.582675
    3D: 362                      2021-04-15 11:35:56.596311
    3E: 303                      2021-04-15 11:35:56.610778
    3F: 183                      2021-04-15 11:35:56.619879
    4A: 543                      2021-04-15 11:35:56.636655
    4B: 123                      2021-04-15 11:35:56.650997
    4C: 363                      2021-04-15 11:35:56.661869
    4D: 2                        2021-04-15 11:35:56.679214
    4E: 124                      2021-04-15 11:35:56.706305
    4F: 544                      2021-04-15 11:35:56.717270
    5A: 304                      2021-04-15 11:35:56.731909
    5B: 364                      2021-04-15 11:35:56.753018
    5C: 185                      2021-04-15 11:35:56.763985
    5D: 3                        2021-04-15 11:35:56.776006
    5E: 125                      2021-04-15 11:35:56.803137
    5F: 305                      2021-04-15 11:35:56.816639
    6A: 545                      2021-04-15 11:35:56.827375
    6B: 186                      2021-04-15 11:35:56.851167
    6C: 365                      2021-04-15 11:35:56.860224
    6D: 4                        2021-04-15 11:35:56.872357
    6E: 126                      2021-04-15 11:35:56.906578
    6F: 306                      2021-04-15 11:35:56.933268
    7A: 187                      2021-04-15 11:35:56.944288
    7B: 366                      2021-04-15 11:35:56.974656
    7C: 5                        2021-04-15 11:35:56.984082
    7D: 188                      2021-04-15 11:35:57.039537
    7E: 127                      2021-04-15 11:35:57.052910
    7F: 547                      2021-04-15 11:35:57.071954
    8A: 307                      2021-04-15 11:35:57.088853
    8B: 367                      2021-04-15 11:35:57.126609
    8C: 6                        2021-04-15 11:35:57.136234
    8D: 189                      2021-04-15 11:35:57.148083
    8E: 308                      2021-04-15 11:35:57.194276
    8F: 548                      2021-04-15 11:35:57.225066
    9A: 128                      2021-04-15 11:35:57.243125
    9B: 368                      2021-04-15 11:35:57.280971
    9C: 7                        2021-04-15 11:35:57.291416
    9D: 190                      2021-04-15 11:35:57.302569
    9E: 549                      2021-04-15 11:35:57.334949
    9F: 309                      2021-04-15 11:35:57.357888
   10A: 129                      2021-04-15 11:35:57.381085
   10B: 550                      2021-04-15 11:35:57.444012
   10C: 369                      2021-04-15 11:35:57.454294
   10D: 8                        2021-04-15 11:35:57.465641
   10E: 191                      2021-04-15 11:35:57.482016
   10F: 130                      2021-04-15 11:35:57.521119

amb protecció zona crítica:

Seients amb més d'una reserva:
Places reservades sense "overbooking"
Tot l'avió ocupat
Reserves per agència
age000:      7
age001:      7
age002:      6
age003:      6
age004:      5
age005:      4
age006:      7
age007:      8
age008:      5
age009:      5
Clients per ordre temporal de reserva:
seient: client                   data
    1A: 120                      2021-04-15 11:33:56.697482
    1B: 121                      2021-04-15 11:33:56.703578
    1C: 420                      2021-04-15 11:33:56.708988
    1D: 360                      2021-04-15 11:33:56.712665
    1E: 421                      2021-04-15 11:33:56.717462
    1F: 60                       2021-04-15 11:33:56.722207
    2A: 361                      2021-04-15 11:33:56.730978
    2B: 422                      2021-04-15 11:33:56.737455
    2C: 0                        2021-04-15 11:33:56.746065
    2D: 480                      2021-04-15 11:33:56.754503
    2E: 240                      2021-04-15 11:33:56.763325
    2F: 362                      2021-04-15 11:33:56.772483
    3A: 423                      2021-04-15 11:33:56.780114
    3B: 61                       2021-04-15 11:33:56.788100
    3C: 1                        2021-04-15 11:33:56.795386
    3D: 241                      2021-04-15 11:33:56.803574
    3E: 122                      2021-04-15 11:33:56.811042
    3F: 180                      2021-04-15 11:33:56.818524
    4A: 300                      2021-04-15 11:33:56.832600
    4B: 540                      2021-04-15 11:33:56.842167
    4C: 62                       2021-04-15 11:33:56.851745
    4D: 424                      2021-04-15 11:33:56.860037
    4E: 2                        2021-04-15 11:33:56.873273
    4F: 363                      2021-04-15 11:33:56.880307
    5A: 242                      2021-04-15 11:33:56.889080
    5B: 181                      2021-04-15 11:33:56.901334
    5C: 481                      2021-04-15 11:33:56.910263
    5D: 63                       2021-04-15 11:33:56.918640
    5E: 301                      2021-04-15 11:33:56.926129
    5F: 541                      2021-04-15 11:33:56.943315
    6A: 425                      2021-04-15 11:33:56.955006
    6B: 123                      2021-04-15 11:33:56.963479
    6C: 3                        2021-04-15 11:33:56.970202
    6D: 364                      2021-04-15 11:33:56.989061
    6E: 182                      2021-04-15 11:33:57.006979
    6F: 482                      2021-04-15 11:33:57.018385
    7A: 64                       2021-04-15 11:33:57.034885
    7B: 243                      2021-04-15 11:33:57.049167
    7C: 302                      2021-04-15 11:33:57.069741
    7D: 4                        2021-04-15 11:33:57.084110
    7E: 183                      2021-04-15 11:33:57.090392
    7F: 542                      2021-04-15 11:33:57.106806
    8A: 124                      2021-04-15 11:33:57.135982
    8B: 426                      2021-04-15 11:33:57.143282
    8C: 483                      2021-04-15 11:33:57.151060
    8D: 65                       2021-04-15 11:33:57.167389
    8E: 365                      2021-04-15 11:33:57.182145
    8F: 5                        2021-04-15 11:33:57.198881
    9A: 184                      2021-04-15 11:33:57.221536
    9B: 303                      2021-04-15 11:33:57.237455
    9C: 543                      2021-04-15 11:33:57.245070
    9D: 244                      2021-04-15 11:33:57.259423
    9E: 125                      2021-04-15 11:33:57.282479
    9F: 366                      2021-04-15 11:33:57.290265
   10A: 427                      2021-04-15 11:33:57.304745
   10B: 66                       2021-04-15 11:33:57.322370
   10C: 6                        2021-04-15 11:33:57.350723
   10D: 544                      2021-04-15 11:33:57.362959
   10E: 185                      2021-04-15 11:33:57.377844
   10F: 484                      2021-04-15 11:33:57.391248