From 68ada8dfaa0eff46f54af94e41d0ce3f816f937a Mon Sep 17 00:00:00 2001 From: codewithmayor Date: Wed, 21 May 2025 11:35:47 +0200 Subject: [PATCH 1/3] Implementation of ML-ready elevator domain model with business rules and robust test coverage --- app/__init__.py | 0 app/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 144 bytes app/__pycache__/crud.cpython-311.pyc | Bin 0 -> 4398 bytes app/__pycache__/database.cpython-311.pyc | Bin 0 -> 587 bytes app/__pycache__/main.cpython-311.pyc | Bin 0 -> 4526 bytes app/__pycache__/models.cpython-311.pyc | Bin 0 -> 2608 bytes app/__pycache__/schemas.cpython-311.pyc | Bin 0 -> 3339 bytes app/crud.py | 60 +++++++++++ app/database.py | 7 ++ app/main.py | 51 ++++++++++ app/models.py | 34 +++++++ app/schemas.py | 49 +++++++++ app/tests/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 150 bytes .../test_app.cpython-311-pytest-7.3.2.pyc | Bin 0 -> 11011 bytes app/tests/test_app.py | 93 ++++++++++++++++++ requirements.txt | 6 ++ 17 files changed, 300 insertions(+) create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-311.pyc create mode 100644 app/__pycache__/crud.cpython-311.pyc create mode 100644 app/__pycache__/database.cpython-311.pyc create mode 100644 app/__pycache__/main.cpython-311.pyc create mode 100644 app/__pycache__/models.cpython-311.pyc create mode 100644 app/__pycache__/schemas.cpython-311.pyc create mode 100644 app/crud.py create mode 100644 app/database.py create mode 100644 app/main.py create mode 100644 app/models.py create mode 100644 app/schemas.py create mode 100644 app/tests/__init__.py create mode 100644 app/tests/__pycache__/__init__.cpython-311.pyc create mode 100644 app/tests/__pycache__/test_app.cpython-311-pytest-7.3.2.pyc create mode 100644 app/tests/test_app.py create mode 100644 requirements.txt diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-311.pyc b/app/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e935b0ec43523c0ef04f8d3a2f30b113743e0c20 GIT binary patch literal 144 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 literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-311.pyc b/app/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3bf3c33076506fd8e9fbcda2a46ac3039a6e6ed GIT binary patch literal 4526 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 E0b*fxH~;_u literal 0 HcmV?d00001 diff --git a/app/__pycache__/schemas.cpython-311.pyc b/app/__pycache__/schemas.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..beed3d71ccc9bc086ca8892771f5d1f4afc79822 GIT binary patch literal 3339 zcmbVO&2QT_6qhVYmcP?D%}1LyL5B@9wk88QU>_)YSXTrIFsIno!+a4mlSz!ql1C}U zvXcVrkYf%5y2H*LiedkY4eTKBx?Oe|5;&)x_8#Tfkz~wWW$I^&KZ@jg@AoMFTB#Hj zxGMba!C#u9{D}{jmp-Zt|AESLg(_5al&;!RRjlWnTvuypvaUI#OIk!#a>|xM$&U)v znfe08`wi~+YUz?QfXin%Lvki?g$$RMToJfZhBGBs2CkCf3X-b=SIclk$;|eWp^Cz1w$HxsD0AS!-0cM&&$VTp+5rnXUDh61DE}_)9o~S)=L%C=Dpgv! zCkoRTq3V5Tr8yX16V*o!du9h7f5;h(B#3KwS+k)F)3UH?S)yoJU5_3*ST9@FcZaq! zVl+SCqGnmP>w1BWYw|7Yr82}dyt=db#An>!?Aq=2CS}h8<_DX0uea&94_MdsH+n}r z4;>|7ko^^a@X9YLR>JLY`xx$Uo7XP;CM+e;ir4S~z;sKQ4mRh|X6bJY{7qiuKoxMu zSIeL}c*79pr8h*bL%9iTVls8qe>e}7$z)V*X|X$B)h-9vc<8x%o&B$yF9|O|Z;k@z zB>-W1yo(-Xyoznot~n%(_#J*79@R9`wV@fW|c3}myBPmiv3+<;er<7EWw zm70g+>Ls9o{$qg2B%uXY3>z~bfJBNm4paGa5Rgkpk}a{3gsl!0`kv!?Tof=R{J`$^M1giVYoj=` z-r|@avA0lxvCEQC;j2g>V7vb*K*C$s7WxFsfGk9%YPb{b9PdQ6r9QzjAWKngK5T}~ zW4Oa+>=zs?;g_+9_kfw{7fj*UFDPv37wI7#J;d_RW?)TKAtOJOGf5`Wl71;ZU)y~d>N<8v72lyh6newYt=G#ErL=toVs;&zuT~CXY zAm4hPGgabua5#hoxcwgh5(1A3_6Bd2AvWJ zo-Qh1g1kHKew+i7@QEU*o(C4{zu7Qmcrg`{cVQ(Q0fpe-14wvnsw#bZ?J6+0*_h4?0@D|)Y+LAD%y4WXJ1`?qXuNYrQ ztazN2zW_5cK-05QM_tDk1TfA}gK3FYnXNK!^WzLQ$F;&Zm*wFjlgnk0LuD+7$BcJ8 znoj8ktbo4{Aj5wM5Uy7G8Ibj8{>IPue!ANyYfuJcExNwaCs+n#B~f2q?Gr2mvihc*2(G``C=0W9 zL~S?dw7CW?!U&FfuorOY0ht!^U?sovtI$F2gfg*ZT1Ypfw&Pdk{rHggfPVyUU|jvD zfM=Sjs*!TLpDdAbv!5)Ha;u*#XGB%2aBRrH={In2P^)loxTFlC!T}=-Cr9VldNHBO zM6F}bG@O2dJ?q$WM3qe(GYcm$JG5R*s0B@3Ia9K5x_yqV7Za+es!M258ctWxq9wFw TL>VQVJqtJ{v|dc1= five_min_ago + ).count() + surge = recent >= 2 #Third demand triggers it + db_demand = models.DemandEvent(**demand.dict(), surge_tag=surge) + db.add(db_demand) + db.commit() + db.refresh(db_demand) + all_demands = db.query(models.DemandEvent).all() + #print(f"ALL DEMANDS IN DB: {[{'floor': d.floor, 'timestamp': d.timestamp} for d in all_demands]}") + #print(f"DEBUG: floor={demand.floor}, timestamp={demand.timestamp}, recent={recent}, surge={surge} ") + return db_demand + +def create_resting(db: Session, resting: schemas.RestingCreate): + #Business Rule: Peak Hours & non-optimal rest + hour = resting.start_time.hour + peak = (7 <= hour < 10) or (16 <= hour < 19) + non_optimal = peak and resting.floor != 1 + db_resting = models.RestingPeriod( + **resting.dict(), + peak_hours_flag=peak, + non_optimal_rested=non_optimal + ) + db.add(db_resting) + db.commit() + db.refresh(db_resting) + return db_resting + +def end_resting(db: Session, resting_id: int, end_time): + from datetime import timezone + rest = db.query(models.RestingPeriod).filter(models.RestingPeriod.id == resting_id).first() + if rest: + rest.end_time = end_time + rest.duration_sec = int((end_time - rest.start_time).total_seconds()) + #Business Rule: Idle Relocation + rest.relocation_flag = rest.duration_sec > 600 + db.commit() + db.refresh(rest) + return rest + +def get_demands(db: Session): + return db.query(models.DemandEvent).all() + +def get_restings(db: Session): + return db.query(models.RestingPeriod).all() \ No newline at end of file diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..b954a0c --- /dev/null +++ b/app/database.py @@ -0,0 +1,7 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +DATABASE_URL = "sqlite:///./elevator.db" +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..fa2b4e8 --- /dev/null +++ b/app/main.py @@ -0,0 +1,51 @@ +from fastapi import FastAPI, Depends, HTTPException +from sqlalchemy.orm import Session +from . import models, schemas, crud +from .database import Base, engine, SessionLocal + +Base.metadata.create_all(bind=engine) +app = FastAPI(title="Elevator ML Data Logger") + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +@app.post("/elevators/", response_model=schemas.Elevator) +def create_elevator(elevator: schemas.ElevatorCreate, db: Session = Depends(get_db)): + return crud.create_elevator(db, elevator) + +@app.post("/demand/", response_model=schemas.Demand) +def create_demand(demand: schemas.DemandCreate, db: Session = Depends(get_db)): + return crud.create_demand(db, demand) + +@app.post("/resting/", response_model=schemas.RestingPeriod) +def create_resting(resting: schemas.RestingCreate, db: Session = Depends(get_db)): + return crud.create_resting(db, resting) + +@app.patch("/resting/{resting_id}/end", response_model=schemas.RestingPeriod) +def end_resting(resting_id: int, end: schemas.RestingEnd, db: Session = Depends(get_db)): + result = crud.end_resting(db, resting_id, end.end_time) + if not result: + raise HTTPException(status_code=404, detail="Resting not found") + return result + +@app.get("/demands/", response_model=list[schemas.Demand]) +def get_demands(db: Session = Depends(get_db)): + return crud.get_demands(db) + +@app.get("/restings/", response_model=list[schemas.RestingPeriod]) +def get_restings(db: Session = Depends(get_db)): + return crud.get_restings(db) + +@app.get("/export/") +def export_all(db: Session = Depends(get_db)): + demands = crud.get_demands(db) + restings = crud.get_restings(db) + #Return as flat JSON for ML + return { + "demands": [d.__dict__ for d in demands], + "restings": [r.__dict__ for r in restings] + } \ No newline at end of file diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..4f5bd35 --- /dev/null +++ b/app/models.py @@ -0,0 +1,34 @@ +from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey +from sqlalchemy.orm import relationship +from .database import Base + +class Elevator(Base): + __tablename__ = "elevators" + id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=True, index=True) + demands = relationship("DemandEvent", back_populates="elevator") + restings = relationship("RestingPeriod", back_populates="elevator") + +class DemandEvent(Base): + __tablename__ = "demands" + id = Column(Integer, primary_key=True, index=True) + elevator_id = Column(Integer, ForeignKey("elevators.id")) + floor = Column(Integer) + timestamp = Column(DateTime) + direction = Column(String) + surge_tag = Column(Boolean, default=False) + elevator = relationship("Elevator", back_populates="demands") + +class RestingPeriod(Base): + __tablename__ = "restings" + id = Column(Integer, primary_key=True, index=True) + elevator_id = Column(Integer, ForeignKey("elevators.id")) + floor = Column(Integer) + start_time = Column(DateTime) + end_time = Column(DateTime, nullable=True) + duration_sec = Column(Integer, nullable=True) + peak_hours_flag = Column(Boolean, default=False) + relocation_flag = Column(Boolean, default=False) + non_optimal_rested = Column(Boolean, default=False) + elevator = relationship("Elevator", back_populates="restings") + \ No newline at end of file diff --git a/app/schemas.py b/app/schemas.py new file mode 100644 index 0000000..4cc3a40 --- /dev/null +++ b/app/schemas.py @@ -0,0 +1,49 @@ +from pydantic import BaseModel +from typing import Optional +from datetime import datetime + +class ElevatorCreate(BaseModel): + name: str + +class Elevator(BaseModel): + id: int + name: str + class Config: + orm_mode = True + +class DemandCreate(BaseModel): + elevator_id: int + floor: int + timestamp: datetime + direction: str + +class Demand(BaseModel): + id: int + elevator_id: int + floor: int + timestamp: datetime + direction: str + surge_tag: bool + class Config: + orm_mode = True + +class RestingCreate(BaseModel): + elevator_id: int + floor: int + start_time: datetime + +class RestingEnd(BaseModel): + end_time: datetime + +class RestingPeriod(BaseModel): + id: int + elevator_id: int + floor: int + start_time: datetime + end_time: Optional[datetime] + duration_sec: Optional[int] + peak_hours_flag: bool + relocation_flag: bool + non_optimal_rested: bool + class Config: + orm_mode = True \ No newline at end of file diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/__pycache__/__init__.cpython-311.pyc b/app/tests/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..876767197b1e3871f48bdc63890c1f0421fb8fad GIT binary patch literal 150 zcmZ3^%ge<81l138GC=fW5CH>>P{wCAAY(d13PUi1CZpd)tREkrnU`4-AFo$X`HRCQH$SB`C)KWq6{r(rb}>JY R_`uA_$oPQ)Miemv#Q-52Ay@za literal 0 HcmV?d00001 diff --git a/app/tests/__pycache__/test_app.cpython-311-pytest-7.3.2.pyc b/app/tests/__pycache__/test_app.cpython-311-pytest-7.3.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b5dd5a888029391f50f3c5ea982891b78e3ce34 GIT binary patch literal 11011 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 CNyfPV literal 0 HcmV?d00001 diff --git a/app/tests/test_app.py b/app/tests/test_app.py new file mode 100644 index 0000000..fc04a24 --- /dev/null +++ b/app/tests/test_app.py @@ -0,0 +1,93 @@ +import os +import pytest +from fastapi.testclient import TestClient +from app.main import app +from app.database import Base, engine +import uuid + + +@pytest.fixture(autouse=True, scope="function") +def clean_db(): + Base.metadata.drop_all(bind=engine) + Base.metadata.create_all(bind=engine) + yield + + +def create_elevator(client, name=None): + if name is None: + import uuid + name = f"TestElevator_{uuid.uuid4()}" + resp = client.post("/elevators/", json={"name": name}) + assert resp.status_code == 200 + return resp.json()["id"] + +def test_create_demand(): + client = TestClient(app) + eid = create_elevator(client) + payload = { + "elevator_id": eid, + "floor": 3, + "timestamp": "2024-06-27T09:00:00", + "direction": "up" + } + resp = client.post("/demand/", json=payload) + assert resp.status_code == 200 + assert resp.json()["floor"] == 3 + +def test_create_resting(): + client = TestClient(app) + eid = create_elevator(client) + payload = { + "elevator_id": eid, + "floor": 2, + "start_time": "2024-06-27T08:00:00" + } + resp = client.post("/resting/", json=payload) + assert resp.status_code == 200 + assert resp.json()["floor"] == 2 + +def test_idle_relocation(): + client = TestClient(app) + eid = create_elevator(client) + resp = client.post("/resting/", json={ + "elevator_id": eid, + "floor": 2, + "start_time": "2024-06-27T08:00:00" + }).json() + rid = resp["id"] + end_resp = client.patch(f"/resting/{rid}/end", json={"end_time": "2024-06-27T08:12:00"}).json() + assert end_resp["relocation_flag"] is True + +def test_peak_hours_non_optimal(): + client = TestClient(app) + eid = create_elevator(client) + resp = client.post("/resting/", json={ + "elevator_id": eid, + "floor": 4, + "start_time": "2024-06-27T08:15:00" + }).json() + assert resp ["non_optimal_rested"] is True + +def test_demand_surge(): + client = TestClient(app) + eid = create_elevator(client) + for minute in [0, 1]: + client.post("/demand/", json={ + "elevator_id": eid, + "floor": 6, + "timestamp": f"2024-06-27T09:0{minute}:00", + "direction": "up" + }) + resp = client.post("/demand/", json={ + "elevator_id": eid, + "floor": 6, + "timestamp": "2024-06-27T09:02:00", + "direction": "up" + }).json() + assert resp["surge_tag"] is True + +def test_export(): + client = TestClient(app) + resp = client.get("/export/") + assert resp.status_code == 200 + assert "demands" in resp.json() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..17950d4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +fastapi +uvicorn +sqlalchemy +pydantic +pytest +httpx \ No newline at end of file From d060e341d3e24b4eafb98cba271886c83c4f3b34 Mon Sep 17 00:00:00 2001 From: codewithmayor Date: Wed, 21 May 2025 11:38:32 +0200 Subject: [PATCH 2/3] Implementation of ML-ready elevator domain model with business rules and robust test coverage --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3906734 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +__pycache__/ +*.py[cod] +*$py.class + +.pytest_cache/ + +elevator.db + +.vscode/ \ No newline at end of file From c82340dfa1a1594ae0cc8da04c05bdf3c3b85a67 Mon Sep 17 00:00:00 2001 From: codewithmayor Date: Wed, 21 May 2025 11:50:27 +0200 Subject: [PATCH 3/3] Removed __pycache__ files from repo, added to .gitignore but after initiial commit --- app/__pycache__/__init__.cpython-311.pyc | Bin 144 -> 0 bytes app/__pycache__/crud.cpython-311.pyc | Bin 4398 -> 0 bytes app/__pycache__/database.cpython-311.pyc | Bin 587 -> 0 bytes app/__pycache__/main.cpython-311.pyc | Bin 4526 -> 0 bytes app/__pycache__/models.cpython-311.pyc | Bin 2608 -> 0 bytes app/__pycache__/schemas.cpython-311.pyc | Bin 3339 -> 0 bytes app/tests/__pycache__/__init__.cpython-311.pyc | Bin 150 -> 0 bytes .../test_app.cpython-311-pytest-7.3.2.pyc | Bin 11011 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/__pycache__/__init__.cpython-311.pyc delete mode 100644 app/__pycache__/crud.cpython-311.pyc delete mode 100644 app/__pycache__/database.cpython-311.pyc delete mode 100644 app/__pycache__/main.cpython-311.pyc delete mode 100644 app/__pycache__/models.cpython-311.pyc delete mode 100644 app/__pycache__/schemas.cpython-311.pyc delete mode 100644 app/tests/__pycache__/__init__.cpython-311.pyc delete mode 100644 app/tests/__pycache__/test_app.cpython-311-pytest-7.3.2.pyc diff --git a/app/__pycache__/__init__.cpython-311.pyc b/app/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e935b0ec43523c0ef04f8d3a2f30b113743e0c20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 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 diff --git a/app/__pycache__/main.cpython-311.pyc b/app/__pycache__/main.cpython-311.pyc deleted file mode 100644 index f3bf3c33076506fd8e9fbcda2a46ac3039a6e6ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4526 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 E0b*fxH~;_u diff --git a/app/__pycache__/schemas.cpython-311.pyc b/app/__pycache__/schemas.cpython-311.pyc deleted file mode 100644 index beed3d71ccc9bc086ca8892771f5d1f4afc79822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3339 zcmbVO&2QT_6qhVYmcP?D%}1LyL5B@9wk88QU>_)YSXTrIFsIno!+a4mlSz!ql1C}U zvXcVrkYf%5y2H*LiedkY4eTKBx?Oe|5;&)x_8#Tfkz~wWW$I^&KZ@jg@AoMFTB#Hj zxGMba!C#u9{D}{jmp-Zt|AESLg(_5al&;!RRjlWnTvuypvaUI#OIk!#a>|xM$&U)v znfe08`wi~+YUz?QfXin%Lvki?g$$RMToJfZhBGBs2CkCf3X-b=SIclk$;|eWp^Cz1w$HxsD0AS!-0cM&&$VTp+5rnXUDh61DE}_)9o~S)=L%C=Dpgv! zCkoRTq3V5Tr8yX16V*o!du9h7f5;h(B#3KwS+k)F)3UH?S)yoJU5_3*ST9@FcZaq! zVl+SCqGnmP>w1BWYw|7Yr82}dyt=db#An>!?Aq=2CS}h8<_DX0uea&94_MdsH+n}r z4;>|7ko^^a@X9YLR>JLY`xx$Uo7XP;CM+e;ir4S~z;sKQ4mRh|X6bJY{7qiuKoxMu zSIeL}c*79pr8h*bL%9iTVls8qe>e}7$z)V*X|X$B)h-9vc<8x%o&B$yF9|O|Z;k@z zB>-W1yo(-Xyoznot~n%(_#J*79@R9`wV@fW|c3}myBPmiv3+<;er<7EWw zm70g+>Ls9o{$qg2B%uXY3>z~bfJBNm4paGa5Rgkpk}a{3gsl!0`kv!?Tof=R{J`$^M1giVYoj=` z-r|@avA0lxvCEQC;j2g>V7vb*K*C$s7WxFsfGk9%YPb{b9PdQ6r9QzjAWKngK5T}~ zW4Oa+>=zs?;g_+9_kfw{7fj*UFDPv37wI7#J;d_RW?)TKAtOJOGf5`Wl71;ZU)y~d>N<8v72lyh6newYt=G#ErL=toVs;&zuT~CXY zAm4hPGgabua5#hoxcwgh5(1A3_6Bd2AvWJ zo-Qh1g1kHKew+i7@QEU*o(C4{zu7Qmcrg`{cVQ(Q0fpe-14wvnsw#bZ?J6+0*_h4?0@D|)Y+LAD%y4WXJ1`?qXuNYrQ ztazN2zW_5cK-05QM_tDk1TfA}gK3FYnXNK!^WzLQ$F;&Zm*wFjlgnk0LuD+7$BcJ8 znoj8ktbo4{Aj5wM5Uy7G8Ibj8{>IPue!ANyYfuJcExNwaCs+n#B~f2q?Gr2mvihc*2(G``C=0W9 zL~S?dw7CW?!U&FfuorOY0ht!^U?sovtI$F2gfg*ZT1Ypfw&Pdk{rHggfPVyUU|jvD zfM=Sjs*!TLpDdAbv!5)Ha;u*#XGB%2aBRrH={In2P^)loxTFlC!T}=-Cr9VldNHBO zM6F}bG@O2dJ?q$WM3qe(GYcm$JG5R*s0B@3Ia9K5x_yqV7Za+es!M258ctWxq9wFw TL>VQVJqtJ{v|dc1>P{wCAAY(d13PUi1CZpd)tREkrnU`4-AFo$X`HRCQH$SB`C)KWq6{r(rb}>JY R_`uA_$oPQ)Miemv#Q-52Ay@za diff --git a/app/tests/__pycache__/test_app.cpython-311-pytest-7.3.2.pyc b/app/tests/__pycache__/test_app.cpython-311-pytest-7.3.2.pyc deleted file mode 100644 index 5b5dd5a888029391f50f3c5ea982891b78e3ce34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11011 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 CNyfPV