User:Rysc00

From Simulace.info
Revision as of 19:23, 10 January 2025 by Rysc00 (talk | contribs) (Agents and Their Characteristics)
Jump to: navigation, search

Problem definition

The supermarket faces inefficiencies in its process at checkouts, leading to customer dissatisfaction. Currently, there are no easily applicable rules for opening new checkouts, which results in problems with too long queues during peak hours and low customer satisfaction.

The goal of this project is to develop data-based rules for opening new checkouts, ensuring that customer satisfaction remains consistently above 70%. The rule for opening checkouts must be easily applicable during store operations - it has to be based on visible elements.

Method

The simulation of the customer checkout process at the supermarket Tesco Vodňany is based on an agent-based simulation approach implemented in NetLogo. This methodology was chosen due to its ability to model complex systems and individual interactions among agents with varying behaviors, allowing for the abstraction and simplification of real-world dynamics. Netlogo was chosen due to its linkup on the course 4IT496.

Model

Overview of the Model

The model is designed to replicate the customer checkout processes, focusing on interactions between customers and the store environment. It incorporates dynamic customer behaviors, such as queue selection, willingness to switch checkouts, and the impact of these decisions on their satisfaction.

The simulation integrates varying customer arrival rates throughout the day and models the performance differences between regular and self-service checkouts. By analyzing the effects of these factors, the model enables exploration of strategies for improving customer satisfaction in a supermarket setting.

Agents and Their Characteristics

The model represents customers as individual agents with the following key characteristics:

1. **Number of Items in Purchase**

  - Each customer is assigned a number of items to purchase, following a lognormal distribution with parameters μ = 3 and σ = 0.3.  
  - This distribution reflects real-world variability in basket sizes among customers.

2. **Checkout Type Preference**

  - Customers have a preference for either regular or self-service checkouts.  
  - This preference is determined by the number of items in their purchase:  
    - Customers with more than 20 items strongly prefer regular checkouts.  
    - Customers with 10–20 items have a variable preference based on a lognormal distribution.  
    - Customers with fewer than 10 items tend to prefer self-service checkouts.  
  - Preferences are scaled from 1 (regular checkouts only) to 100 (self-service only).

3. **Willingness to Switch Checkouts**

  - This attribute reflects how likely a customer is to switch queues if it could reduce their waiting time.  
  - The initial value is drawn from a lognormal distribution scaled to 0–100 with parameters μ = 2.9 and σ = 0.4.  
  - Each switch reduces the willingness to switch by half, modeling the decreasing likelihood of repeated queue changes.

4. **Satisfaction**

  - Customers begin with an initial satisfaction value based on a normal distribution (mean = 95, standard deviation = 5).  
  - Satisfaction changes dynamically during the simulation:  
    - **Waiting Time**: Satisfaction decreases logarithmically if waiting exceeds 3 minutes.  
    - **Queue Switching**: Switching queues can decrease satisfaction, depending on the customer’s willingness to switch.  
    - **Checkout Type**: Satisfaction changes depending on whether the customer uses their preferred type of checkout.  
  - Satisfaction cannot fall below zero.

These characteristics ensure a realistic representation of customer behaviors and preferences, enabling the model to capture complex dynamics within the supermarket environment.

Processes in the Model

Assumptions and Limitations

Simulation Environment

Validation of the Model

Results

Conclusion

Code

globals [

 satisfaction-threshold ;; Prahová hodnota pro spokojenost
 total-satisfaction     ;; Celková průměrná spokojenost zákazníků
 total-satisfaction-population
 simulation-running     ;; Stav simulace (true = běží, false = zastaveno)
 shared-self-checkout-queue
 last-20-satisfactions

]


breed [customers customer] breed [checkouts checkout]

customers-own [

 items                    ;; Počet položek, které zákazník nakupuje
 checkout-preference      ;; Preference (regular/self-service)
 willingness-to-switch    ;; Ochota přejít k jiné pokladně
 satisfaction             ;; Aktuální úroveň spokojenosti
 time-spent-in-queue      ;; Čas strávený ve frontě
 current-checkout
 remaining-service-time   ;; Celková zbývající doba obsluhy

]

checkouts-own [

 queue-length             ;; Délka fronty na pokladně
 checkout-type            ;; Typ pokladny (regular nebo self-service)
 status                   ;; true = otevřená, false = zavřená
 queue-start-position     ;; Souřadnice začátku fronty jako seznam [x y]
 currently-served-customer ;; Zákazník, který je aktuálně obsluhován (nebo `nobody`, pokud nikdo)

]


to setup

 clear-all
 set satisfaction-threshold 70
 set total-satisfaction 0
 set last-20-satisfactions []
 set total-satisfaction-population 0
 set simulation-running true ;; Simulace je připravena k běhu
 setup-checkouts
 set last-20-satisfactions []
 reset-ticks

end


to setup-checkouts

 clear-patches
 set shared-self-checkout-queue (list 13 -5)
 ;; Regular checkouts (modré pokladny)
 let regular-checkout-positions [
   [-17 13] [-12 13] [-7 13] [-2 13] [3 13]
 ]
 create-checkouts 5 [
   set shape "square"
   set size 3.5
   set color blue
   set checkout-type "regular"
   set queue-length 0
   set currently-served-customer nobody
   ;; První regular checkout otevřená, ostatní zavřené
   ifelse who = 0 [
     set status true
   ] [
     set status false
   ]
   ;; Použij hardcodované souřadnice
   let pos item who regular-checkout-positions
   setxy item 0 pos item 1 pos
   ;; Nastav počáteční bod individuální fronty
   set queue-start-position (list item 0 pos (item 1 pos - 2)) ;; Fronta začíná 2 jednotky pod pokladnou
 ]
 ;; Self-service checkouts (zelené pokladny)
 let self-checkout-positions [
   [8 13] [18 13]    ;; První řada
   [8 8] [18 8]      ;; Druhá řada
   [8 3] [18 3]      ;; Třetí řada
   [8 -2] [18 -2]    ;; Čtvrtá řada
 ]
 create-checkouts 8 [
   set shape "square"
   set size 3.5
   set color green
   set checkout-type "self-service"
   set queue-length 0
   set currently-served-customer nobody
   set status true
   ;; Použij hardcodované souřadnice
   let pos item (who - 5) self-checkout-positions
   setxy item 0 pos item 1 pos
 ;; Nastav počáteční bod fronty na základě ycor a obarvi záplatu
 ifelse [ycor] of self = 13 [
   ;; Pokud je pokladna v první řadě (y = 13)
   let queue-x [xcor] of self
   let queue-y ([ycor] of self - 2)
   set queue-start-position (list queue-x queue-y)
 ] [
   ifelse [ycor] of self = 8 [
     ;; Pokud je pokladna v druhé řadě (y = 8)
     let queue-x [xcor] of self
     let queue-y ([ycor] of self - 2)
     set queue-start-position (list queue-x queue-y)
   ] [
     ifelse [ycor] of self = 3 [
       ;; Pokud je pokladna ve třetí řadě (y = 3)
       let queue-x [xcor] of self
       let queue-y ([ycor] of self - 2)
       set queue-start-position (list queue-x queue-y)
     ] [
       ;; Pokud je pokladna ve čtvrté řadě (y = -2)
       let queue-x [xcor] of self
       let queue-y ([ycor] of self - 2)
       set queue-start-position (list queue-x queue-y)
     ]
   ]
 ]

]


 update-checkout-colors

end


to update-checkout-colors

 ;; Iterace přes všechny pokladny
 ask checkouts [
   ifelse status [
     ;; Pokud je pokladna otevřená
     ifelse checkout-type = "regular" [
       set color blue ;; Modrá pro regular
     ] [
       set color green ;; Zelená pro self-service
     ]
   ] [
     ;; Pokud je pokladna zavřená
     set color gray ;; Šedá barva pro zavřenou pokladnu
   ]
 ]

end

to go

 if not simulation-running [stop]
 ifelse ticks <= 780 [
   if any? customers [
     consider-switching
   ]
   generate-customers
   assign-checkouts
   increment-time-spent-in-queue
   process-checkouts
   served-customers-leave
   update-queue
   adjust-checkouts
   update-checkout-colors ;; Dynamická aktualizace barev
   tick
 ] [
   ifelse ticks >= 795 [
     ask checkouts [
       set status false
       update-checkout-colors
     ]
     stop
   ] [
     increment-time-spent-in-queue
     process-checkouts
     served-customers-leave
     update-queue
     adjust-checkouts
     update-checkout-colors
     tick ;; Přidání volání tick v této části
   ]
 ]

end


to-report random-lognormal [mu sigma]

 ;; Vygeneruje náhodnou hodnotu z lognormálního rozdělení
 let normal-sample random-normal mu sigma
 report exp normal-sample

end

to-report random-poisson-lognormal [mu sigma]

 ;; Výpočet lognormálních parametrů
 let lognormal-std-dev sqrt (ln (1 + ((sigma ^ 2) / (mu ^ 2)))) ;; Přesná std dev pro lognormál
 let lognormal-mean (ln mu - ((lognormal-std-dev ^ 2) / 2))    ;; Přesná mean pro lognormál
 
 ;; Generování lognormálního vzorku
 let lognormal-sample exp (random-normal lognormal-mean lognormal-std-dev)
 
 ;; Zaokrouhlení na celá čísla (Poisson-lognormál výstup)
 let result round lognormal-sample
 
if result > 80 [
   set result random-poisson-lognormal mu sigma
]
 ;; Zajištění minimální hodnoty 1
 report ifelse-value (result < 1) [1] [result]

end


to generate-customers

 let arrivals max list 0 floor random-normal 
 (ifelse-value 
   (ticks mod 780 >= 0 and ticks mod 780 < 180) or (ticks mod 780 >= 540 and ticks mod 780 < 660) 
   [4] ; Špičkové hodiny: 8:00-10:00 a 16:00-18:00
   [2] ; Mimo špičku: 10:00-16:00 a 18:00-20:00
 ) 1


 ;; Vytvoř zákazníky specifického plemene "customers"
 create-customers arrivals [
   ;; Nastavení počtu položek
 set items random-poisson-lognormal 14 15
   set remaining-service-time -1
   ;; Generování preference pokladny na škále 1–100
   if items > 20 [
     set checkout-preference 1 ;; Pouze regular checkouts
   ]
   if items <= 10 [
    let preference random-lognormal 3.8 0.9
     set checkout-preference max list 1 (min list (100 - preference) 100)
   ]
   if items > 10 and items <= 20 [
     let preference random-lognormal 3.9 0.9
     set checkout-preference max list 1 (min list preference 100)
   ]


   ;; Generování willingness-to-switch (lognormální rozdělení)
   set willingness-to-switch min list 100 max list 0 round random-lognormal 2.9 0.4
   ;; Nastavení vzhledu zákazníků
   set shape "person"
   set size 1.5
   ifelse checkout-preference <= 50 [
   set color blue ;; Barva se může změnit dle pokladny
     ] [
   set color green
 ]
   ;; Umístění zákazníků na spodní stěnu
   let x-start random-xcor
   setxy x-start min-pycor
   ;; Další vlastnosti zákazníků
   set satisfaction random-normal 80 5
   set time-spent-in-queue 0
   set current-checkout nobody
 ]

end

to consider-switching

 ask customers [
   ;; Spočítají si, jak by byli spokojeni na všech dostupných pokladnách
   let best-checkout choose-checkout self ;; Vybere pokladnu s nejvyšším potenciálem spokojenosti
   if best-checkout != current-checkout [
     ;; Spočítá potenciální spokojenost na nejlepší pokladně
     let potential-satisfaction (satisfaction + potential-satisfaction-change-on-checkout-x best-checkout self)
     ;; Spočítá aktuální spokojenost v současné frontě na základě pozice
     let current-queue-satisfaction calculate-current-queue-satisfaction self
     ;; Spočítá penalizaci za přechod na základě willingness-to-switch
     let switch-penalty 0
     if willingness-to-switch > 80 [
       set switch-penalty 0 ;; Žádná penalizace
     ]
     if willingness-to-switch <= 80 and willingness-to-switch > 60 [
       set switch-penalty random 5 + 1 ;; Penalizace 1–5 bodů
     ]
     if willingness-to-switch <= 60 and willingness-to-switch > 40 [
       set switch-penalty random 5 + 3 ;; Penalizace 3–7 bodů
     ]
     if willingness-to-switch <= 40 and willingness-to-switch > 20 [
       set switch-penalty random 5 + 5 ;; Penalizace 5–9 bodů
     ]
     if willingness-to-switch <= 20 [
       set switch-penalty random 5 + 7 ;; Penalizace 7–11 bodů
     ]
     ;; Zvažuje, zda přejít: nová spokojenost musí být vyšší než aktuální
     let new-satisfaction (potential-satisfaction - switch-penalty)
     if new-satisfaction > current-queue-satisfaction [
       ;; Rozhodne se, zda přejde (pravděpodobnost dle willingness-to-switch)
       if random-float 100 <= willingness-to-switch [
         ;; Přechod na novou pokladnu
         move-to-checkout best-checkout
         ask current-checkout [
           set queue-length queue-length - 1
         ]
         set current-checkout best-checkout
         ask current-checkout [
           set queue-length queue-length + 1
         ]


         ;; Snížení willingness-to-switch na polovinu
         set willingness-to-switch willingness-to-switch / 2
         ;; Snížení spokojenosti podle penalizace
         set satisfaction current-queue-satisfaction - switch-penalty
         ;; Ujisti se, že spokojenost zůstane v rozsahu 0–100
         if satisfaction > 100 [ set satisfaction 100 ]
         if satisfaction < 0 [ set satisfaction 0 ]
       ]
     ]
   ]
 ]

end

to-report calculate-current-queue-satisfaction [currently-handled-customer]

 ;; Spočítá celkový odhadovaný čas, který zákazník stráví čekáním ve frontě
 let total-wait-time 0
 let handled-customer currently-handled-customer
 ;; Najdi aktuální pokladnu zákazníka
 let current-checkout-1 [current-checkout] of self
 ;; Spočítej zákazníky ve frontě před tímto zákazníkem
 let queue-start [queue-start-position] of current-checkout-1
 let queue-x item 0 queue-start
 let queue-y item 1 queue-start
 ;; Najdi zákazníky v aktuální frontě seřazené podle pozice ve frontě
 let queue-customers customers with [
   current-checkout-1 = myself and xcor = queue-x and ycor <= queue-y
 ]
 let sorted-queue sort-on [ycor] queue-customers
 ;; Iterace přes zákazníky ve frontě
 let position-in-customer-queue 1
 foreach sorted-queue [
   let customer-1 self ;;PROBLÉM?
   ifelse ycor <= [ycor] of handled-customer [
   ] [
    ifelse position-in-customer-queue = 1 [
      ;; První ve frontě: přičti zbývající dobu obsluhy
      set total-wait-time total-wait-time + [remaining-service-time] of customer-1
    ] [
      ;; Další zákazníci: přičti odhadovanou dobu obsluhy
      set total-wait-time total-wait-time + estimate-service-time current-checkout-1 customer-1
    ]
   ]
   set position-in-customer-queue position-in-customer-queue + 1
 ]
 ;; Spočítej satisfaction na základě čekací doby
 ifelse total-wait-time <= 3 [
   report satisfaction ;; Spokojenost se nezmění
 ] [
   report satisfaction - ((10 + (total-wait-time - 3)) * ln (1 + (total-wait-time - 3))) ;; Spokojenost klesá s čekací dobou
 ]

end

to assign-checkouts

 ask customers with [current-checkout = nobody] [
   let chosen-checkout choose-checkout self
   if chosen-checkout != nobody [
     set current-checkout chosen-checkout
     ;; Přesuň zákazníka na pozici na konci fronty vybrané pokladny
     move-to-checkout chosen-checkout
     ifelse [checkout-type] of chosen-checkout = "regular" [
       ;; Zvýšení délky fronty pouze u vybraného regular checkoutu
       ask chosen-checkout [
         set queue-length queue-length + 1
       ]
     ] [
       ;; Zvýšení délky fronty o 1 u všech self-service checkoutů
       ask checkouts with [checkout-type = "self-service"] [
         set queue-length queue-length + 1
         
       ]
     ]
   ]
 ]

end

to move-to-checkout [chosen-checkout]

 ;; Získání cílové souřadnice na konci fronty
 let queue-start-of-checkout [queue-start-position] of chosen-checkout
 let queue-position [queue-length] of chosen-checkout
 if [checkout-type] of chosen-checkout = "self-service" [
   set queue-start-of-checkout shared-self-checkout-queue
 ]
 let target-x (item 0 queue-start-of-checkout)
 let target-y (item 1 queue-start-of-checkout - queue-position * 0.5)
 if target-y <= min-pycor [
   set target-y min-pycor + 0.5
 ]
 ;; Pohyb směrem k cíli po malých krocích
 while [distancexy target-x target-y > 0.5] [
   facexy target-x target-y
   fd 0.5 ;; Délka kroku
   display ;; Aktualizuje vizuální zobrazení při každém kroku
 ]
 ;; Jakmile je dostatečně blízko, nastav souřadnice na cílové místo
 setxy target-x target-y

end

to move-to-self-checkout [chosen-checkout]

 ;; Získání cílové souřadnice na konci fronty
 let queue-start-of-checkout [queue-start-position] of chosen-checkout
 let target-x (item 0 queue-start-of-checkout)
 let target-y (item 1 queue-start-of-checkout)
 ;; Pohyb směrem k cíli po malých krocích
 while [distancexy target-x target-y > 0.5] [
   facexy target-x target-y
   fd 0.5 ;; Délka kroku
   display ;; Aktualizuje vizuální zobrazení při každém kroku
 ]
 ;; Jakmile je dostatečně blízko, nastav souřadnice na cílové místo
 setxy target-x target-y

end


to-report choose-checkout [customer-to-handle]

 let handled-customer customer-to-handle
 ;; Inicializace proměnných pro sledování nejlepší satisfaction a pokladen s touto hodnotou
 let best-satisfaction -1000
 let best-checkouts []
 ;; Projdi všechny otevřené pokladny
 ask checkouts with [status = true] [
   let potential-satisfaction potential-satisfaction-change-on-checkout-x self handled-customer
   ;; Pokud je satisfaction vyšší než aktuální nejlepší, aktualizuj seznam pokladen
   if potential-satisfaction > best-satisfaction [
     set best-satisfaction potential-satisfaction
     set best-checkouts (list self) ;; Resetuj seznam a přidej tuto pokladnu
   ]
   ;; Pokud je satisfaction stejná jako aktuální nejlepší, přidej pokladnu do seznamu
   if potential-satisfaction = best-satisfaction [
     set best-checkouts lput self best-checkouts
   ]
 ]
 ;; Jinak vrať náhodnou z nejlepších
 report one-of best-checkouts

end



to update-satisfaction [leaving-customer]

 let checkout-type-satisfaction 0
 ;; Výpočet satisfaction z typu pokladny
 ask leaving-customer[
 set checkout-type-satisfaction satisfaction-change-from-checkout-type [checkout-type] of current-checkout self
 ]
 ;; Výpočet satisfaction z čekací doby
 let wait-time-satisfaction 0
 if time-spent-in-queue > 3 [
   set wait-time-satisfaction (-(10 * ln (1 + (time-spent-in-queue - 3))))
 ]
 ;; Aktualizace spokojenosti
 set satisfaction (satisfaction + wait-time-satisfaction + checkout-type-satisfaction)
 
 if satisfaction > 100 [set satisfaction 100]
 if satisfaction < 0 [ set satisfaction 0 ]
 ;; Aktualizace celkové spokojenosti všech zákazníků
 set total-satisfaction (((total-satisfaction * total-satisfaction-population) + satisfaction) / (total-satisfaction-population + 1))
 set total-satisfaction-population (total-satisfaction-population + 1)
 
 set last-20-satisfactions lput satisfaction last-20-satisfactions
 if length last-20-satisfactions > 20 [
   set last-20-satisfactions but-first last-20-satisfactions
 ]
 

end


to increment-time-spent-in-queue

 ;; Pro všechny zákazníky zvyšte hodnotu time-spent-in-queue o 1
 ask customers [
   set time-spent-in-queue time-spent-in-queue + 1
 ]

end


to process-checkouts

 ;; Projdi všechny otevřené pokladny
 ask checkouts with [status = true] [
   ;; Ulož odkaz na aktuální pokladnu
   let current-checkout-1 self
   ifelse [checkout-type] of current-checkout-1 = "regular" [
     ;; Logika pro regular checkouts
     if currently-served-customer = nobody [
       ;; Najdi souřadnice začátku fronty
       let queue-start [queue-start-position] of current-checkout-1
       let queue-start-x item 0 queue-start
       let queue-start-y item 1 queue-start
       ;; Najdi zákazníka, který stojí na začátku fronty
       let first-in-line one-of customers with [
         xcor = queue-start-x and
         ycor = queue-start-y
       ]
       if first-in-line != nobody [
         ;; Nastav tohoto zákazníka jako obsluhovaného
         set currently-served-customer first-in-line
         ask first-in-line [
           let service-time estimate-service-time current-checkout-1 first-in-line
           set remaining-service-time service-time
           set willingness-to-switch 0
         ]
       ]
     ]
     ;; Načti a nastav zbývající dobu obsluhy zákazníka
     if currently-served-customer != nobody [
       ask currently-served-customer [
         set remaining-service-time (remaining-service-time - 1)
       ]
     ]
   ] [
     ;; Logika pro self-service checkouts
     if currently-served-customer = nobody [
       ;; Sdílená fronta: najdi zákazníka na vrcholu fronty
       let queue-start shared-self-checkout-queue
       let queue-start-x item 0 queue-start
       let queue-start-y item 1 queue-start
       ;; Najdi zákazníka na vrcholu sdílené fronty
       let first-in-line one-of customers with [
         xcor = queue-start-x and
         ycor = queue-start-y
       ]
       if first-in-line != nobody [
         ;; Nastav tohoto zákazníka jako obsluhovaného
         set currently-served-customer first-in-line
         ask first-in-line [
           move-to-self-checkout current-checkout-1
           let service-time estimate-service-time current-checkout-1 first-in-line
           set remaining-service-time service-time
           set willingness-to-switch 0
         ]
         update-self-checkouts
         ask checkouts with [checkout-type = "self-service"] [
         set queue-length (queue-length - 1)
         ]
       ]
     ]
     ;; Načti a nastav zbývající dobu obsluhy zákazníka
     if currently-served-customer != nobody [
       ask currently-served-customer [
         set remaining-service-time (remaining-service-time - 1)
       ]
     ]
   ]
 ]

end



to served-customers-leave

 ;; Najdi všechny zákazníky, jejichž zbývající doba obsluhy klesla na 0
 ask customers with [remaining-service-time = 0] [
   if current-checkout = nobody [
     die
   ]
   ;; Aktualizuj pokladny, které obsluhují tohoto zákazníka
   ask checkouts with [currently-served-customer = myself] [
     if [checkout-type] of self = "regular" [
       ;; Snížení délky fronty u regular pokladny
       set queue-length (queue-length - 1)
     ]
     set currently-served-customer nobody
   ]
   update-satisfaction self
   die
 ]

end


to update-queue

 update-regular-checkouts
 update-self-checkouts

end

to update-regular-checkouts

 ;; Iterace přes všechny regular pokladny
 ask checkouts with [checkout-type = "regular"] [
   ;; Ulož odkaz na aktuální pokladnu
   let checkout-reference self
   ;; Najdi pozici začátku fronty této pokladny
   let queue-start [queue-start-position] of checkout-reference
   let queue-start-x item 0 queue-start
   let queue-start-y item 1 queue-start
   if currently-served-customer = nobody [
     let current-ycor queue-start-y - 0.5
     ;; Iterace pro kontrolu fronty a posun zákazníků
     while [any? customers with [xcor = queue-start-x and ycor = current-ycor]] [
       ;; Posuň zákazníka na této pozici o 0.5 nahoru
        ask customers with [xcor = queue-start-x and ycor = current-ycor] [
           set ycor ycor + 0.5
        ]
        ;; Pokračuj kontrolou další pozice o 0.5 níže
        set current-ycor current-ycor - 0.5
       
     ]
   ]
 ]

end

to update-self-checkouts

 ;; Pohyb zákazníků ve sdílené frontě self-service pokladen
 let queue-start shared-self-checkout-queue
 let queue-start-x item 0 queue-start
 let queue-start-y item 1 queue-start
 let current-ycor queue-start-y
 
 ifelse any? customers with [xcor = queue-start-x and ycor = current-ycor] [
 ][
   set current-ycor current-ycor - 0.5
   
   ;; Procházej zákazníky ve sdílené frontě (od začátku fronty dolů)
   while [any? customers with [xcor = queue-start-x and ycor = current-ycor]] [
     let customer-on-spot one-of customers with [xcor = queue-start-x and ycor = current-ycor]
     ;; Najdi volnou pozici nad zákazníkem
     let target-ycor current-ycor + 0.5
     if not any? customers with [xcor = queue-start-x and ycor = target-ycor] [
       ;; Posuň zákazníka na volné místo
       ask customer-on-spot [
         set ycor target-ycor
       ]
     ]
     ;; Posuň se o 0.5 níže ve frontě
     set current-ycor current-ycor - 0.5
   ]
 ] 

end


to-report potential-satisfaction-change-on-checkout-x [target-checkout customer-to-handle]

 let handled-customer customer-to-handle
 ;; Spočítání satisfaction z čekací doby
 let satisfaction-from-wait satisfaction-change-from-wait-time target-checkout
 ;; Spočítání satisfaction z typu pokladny a preference
 let satisfaction-from-type satisfaction-change-from-checkout-type [checkout-type] of target-checkout handled-customer
 ;; Celková potenciální změna satisfaction
 report satisfaction-from-wait + satisfaction-from-type

end

to-report satisfaction-change-from-wait-time [target-checkout]

 ;; Inicializace proměnných
 let queue-start [queue-start-position] of target-checkout
 
 if [checkout-type] of target-checkout = "self-service" [
   set queue-start shared-self-checkout-queue
 ]
 
 let current-x item 0 queue-start
 let current-y item 1 queue-start
 let total-wait-time 0
 ;; Iterace přes pozice fronty
 while [any? customers with [xcor = current-x and ycor = current-y]] [
   ;; Najdi zákazníka na aktuální pozici
   let customer-on-spot one-of customers with [xcor = current-x and ycor = current-y]
   ;; Přidej odhadovaný čas obsluhy zákazníka k celkovému času čekání
   set total-wait-time total-wait-time + estimate-service-time target-checkout customer-on-spot
   ;; Posuň se na další pozici
   set current-y current-y - 0.5
 ]
 
if [checkout-type] of target-checkout = "self-service" [
   let served-customer [currently-served-customer] of target-checkout
   ifelse served-customer = nobody [
     set total-wait-time total-wait-time / 8
   ][
     let current-customer [currently-served-customer] of target-checkout
     let ticks-before-leave [remaining-service-time] of current-customer
     set total-wait-time ((total-wait-time / 8) + ticks-before-leave)
   ]
  ]
 ;; Výpočet satisfaction
 ifelse total-wait-time <= 3 [
   report 0
 ] [
   report (-((10 + (total-wait-time - 3)) * ln (1 + (total-wait-time - 3))))
 ]

end

to-report satisfaction-change-from-checkout-type [target-checkout-type customer-to-handle]

 let handled-customer customer-to-handle
 ;; Získání preference zákazníka
 let preference [checkout-preference] of handled-customer ;; Preference je atribut zákazníka
 let result 0
 ;; Výpočet vlivu typu pokladny na satisfaction
 if preference > 60 [
   if target-checkout-type = "self-service" [
     set result (random 2 + 2) ;; Zvýšení o 2–3 body
   ]
   if target-checkout-type = "regular" [
     set result (-(random 6 + 5)) ;; Snížení o 5–10 bodů
   ]
 ]
 if preference <= 60 and preference > 50 [
   if target-checkout-type = "self-service" [
     set result (random 2 + 1) ;; Zvýšení o 1–2 body
   ]
   if target-checkout-type = "regular" [
     set result (-(random 5 + 1)) ;; Snížení o 1–5 bodů
   ]
 ]
 if preference <= 50 and preference > 40 [
   if target-checkout-type = "self-service" [
     set result (-(random 5 + 1)) ;; Snížení o 1–5 bodů
   ]
   if target-checkout-type = "regular" [
     set result (random 2 + 1) ;; Zvýšení o 1–2 body
   ]
 ]
 if preference <= 40 [
   if target-checkout-type = "self-service" [
     set result (-(random 6 + 5)) ;; Snížení o 5–10 bodů
   ]
   if target-checkout-type = "regular" [
     set result (random 2 + 2) ;; Zvýšení o 2–3 body
   ]
 ]
 report result

end


to-report estimate-service-time [target-checkout target-customer]

 ;; Inicializace proměnné pro odhadovaný čas
 let estimated-time 0
 ;; Pravděpodobnost chyby a čas řešení
 let error-probability ifelse-value ([checkout-type] of target-checkout = "regular") [0.001] [0.01]
 ;; Výpočet na základě typu pokladny
 if [checkout-type] of target-checkout = "regular" [
   set estimated-time
     (random-normal 1 0.2 +
     0.8 * [items] of target-customer * random-normal 0.06 0.01 +
     0.2 * [items] of target-customer * random-normal 0.07 0.01)
 ]
 if [checkout-type] of target-checkout = "self-service" [
   set estimated-time
     (random-normal 1.2 0.3 +
     0.8 * [items] of target-customer * random-normal 0.07 0.02 +
     0.2 * [items] of target-customer * random-normal 0.08 0.02)
 ]
 ;; Přidání času na řešení chyby, pokud k ní dojde
 let error-opportunities [items] of target-customer
 if error-opportunities > 0 [
   if random-float 1 < error-probability [
     let error-resolution-time random-normal 0.5 0.1
     set estimated-time estimated-time + error-resolution-time
   ]
   set error-opportunities error-opportunities - 1
 ]
 ;; Vrácení odhadovaného času
 report ceiling(estimated-time)

end

to open-new-checkouts

 if not empty? last-20-satisfactions [
  let average-last-20 mean last-20-satisfactions

  if average-last-20 < satisfaction-threshold [
    let closed-checkouts checkouts with [status = false]
    if any? closed-checkouts [
      let checkout-to-open one-of closed-checkouts
      ask checkout-to-open [
        set status true
      ]
    ]
  ]
 ]

end

to close-checkouts

 ;; Najdi otevřené regular pokladny
 let opened-checkouts sort [self] of checkouts with [
   checkout-type = "regular" and
   status = true
 ]
 
 let opened-checkouts-without-customers []
 
 foreach opened-checkouts [checkout1 ->
   let queue-start [queue-start-position] of checkout1
   let queue-start-x item 0 queue-start
   let queue-start-y item 1 queue-start
   let first-in-line one-of customers with [xcor = queue-start-x and ycor = queue-start-y]
   if first-in-line = nobody [
     set opened-checkouts-without-customers lput checkout1 opened-checkouts-without-customers
   ]
 ]


 ;; Projdi všechny pokladny bez zákazníků
 foreach opened-checkouts-without-customers [checkout1 ->
   ;; Spočítej aktuální počet otevřených regular pokladen
   let num-opened-regular-checkouts length opened-checkouts
   ;; Zavři pokladnu, pokud je otevřených více než 1
   if num-opened-regular-checkouts > 1 [
     ask checkout1 [
       set status false
     ]
     ;; Aktualizuj seznam otevřených regular pokladen
     set opened-checkouts sort [self] of checkouts with [
       checkout-type = "regular" and
       status = true
     ]
   ]
 ]

end

to adjust-checkouts

 close-checkouts
 open-new-checkouts

end


to stop-simulation

 set simulation-running false ;; Nastaví stav simulace na zastaveno

end