Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/refactor machine stats #4

Merged
merged 7 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/PR_BE.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: MVP Python Tests

on:
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r mvp/server/requirements.txt

- name: Run tests
run: |
cd mvp/server
python -m pytest tests/
27 changes: 27 additions & 0 deletions .github/workflows/PR_FE.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: MVP Frontend Build

on:
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Node.js
uses: actions/setup-node@v2

- name: Install dependencies
run: |
cd mvp/client
npm install

- name: Build frontend
run: |
cd mvp/client
npm run build
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
![image](https://github.com/linomp/pdm-game/assets/40581019/0bda82aa-9577-4127-8fee-c9a4981cd4cd)
![image](./media/29_12_2023.PNG)
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ server {
ssl_certificate_key /etc/letsencrypt/live/api.pdmgame.xmp.systems/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
if ($host = api.pdmgame.xmp.systems) {
Expand All @@ -25,6 +24,4 @@ server {
listen 80;
server_name api.pdmgame.xmp.systems;
return 404; # managed by Certbot


}
26 changes: 26 additions & 0 deletions conf-backups/app.pdmgame.xmp.systems.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
server {
server_name app.pdmgame.xmp.systems;

location / {
proxy_pass http://127.0.0.1:8000; # Change this to the address where your app is running
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/app.pdmgame.xmp.systems/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/app.pdmgame.xmp.systems/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = app.pdmgame.xmp.systems) {
return 301 https://$host$request_uri;
} # managed by Certbot

listen 80;
server_name app.pdmgame.xmp.systems;
return 404; # managed by Certbot
}
Binary file added media/29_12_2023.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mvp/client/src/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type { OpenAPIConfig } from './core/OpenAPI';
export type { GameSessionDTO } from './models/GameSessionDTO';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { MachineStats } from './models/MachineStats';
export type { OperationalParameters } from './models/OperationalParameters';
export type { ValidationError } from './models/ValidationError';

export { DefaultService } from './services/DefaultService';
Expand Down
5 changes: 4 additions & 1 deletion mvp/client/src/generated/models/MachineStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
/* tslint:disable */
/* eslint-disable */

import type { OperationalParameters } from './OperationalParameters';

export type MachineStats = {
rul?: (number | null);
predicted_rul?: (number | null);
health_percentage: number;
operational_parameters?: (OperationalParameters | null);
};
10 changes: 10 additions & 0 deletions mvp/client/src/generated/models/OperationalParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export type OperationalParameters = {
temperature: number;
oil_age: number;
mechanical_wear: number;
};
4 changes: 2 additions & 2 deletions mvp/client/src/generated/services/SessionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ sessionId: string,
}

/**
* Advance Session Turn
* Advance
* @param sessionId
* @returns GameSessionDTO Successful Response
* @throws ApiError
*/
public static advanceSessionTurnSessionTurnsPut(
public static advanceSessionTurnsPut(
sessionId: string,
): CancelablePromise<GameSessionDTO> {
return __request(OpenAPI, {
Expand Down
71 changes: 36 additions & 35 deletions mvp/client/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,13 @@
};

const fetchExistingSession = async () => {
if (!gameSession) {
return;
}

if (gameSession?.machine_stats && gameSession.machine_stats.health_percentage <= 0) {
gameOver = true;
if (!gameSession || gameOver) {
return;
}

try {
gameSession = await SessionsService.getSessionSessionGet(gameSession?.id);
checkForGameOver();
} catch (error) {
console.error('Error fetching session:', error);
}
Expand All @@ -47,54 +43,59 @@
return;
}

try {
// start fetching machine health every second while the day is advancing
const intervalId = setInterval(fetchExistingSession, 700);

advanceButtonDisabled = true;
gameSession = await SessionsService.advanceSessionTurnSessionTurnsPut(gameSession?.id);
advanceButtonDisabled = false;
// TODO: migrate this polling strategy to a websocket connection
// start fetching machine health every second while the day is advancing
const intervalId = setInterval(fetchExistingSession, 500);
advanceButtonDisabled = true;

try {
gameSession = await SessionsService.advanceSessionTurnsPut(gameSession?.id);
} catch (error) {
console.error('Error advancing day:', error);
} finally {
await fetchExistingSession();
// stop fetching machine health until the player advances to next day again
clearInterval(intervalId);
advanceButtonDisabled = false;
}
};

// TODO: migrate this polling strategy to a websocket connection
} catch (error) {
console.error('Error advancing day:', error);
const checkForGameOver = () => {
if (!gameSession) {
return;
}

if (gameSession?.machine_stats && gameSession.machine_stats.health_percentage <= 0) {
gameOver = true;
}
};
</script>

<div>
<h2>The Predictive Maintenance Game</h2>

{#if !gameSession}
<button on:click={startSession}>Start Session</button>
{/if}

{#if gameSession}
<img
src={stopAnimation ? stoppedMachineSrc : runningMachineSrc}
alt="Machine"
width="369"
height="276"
/>
{/if}

{#if gameSession && !gameOver}
<div>
<h3>Game Session Details</h3>
{#if gameOver}
<h3>Game Over</h3>
<pre>{JSON.stringify(gameSession, null, 2)}</pre>
</div>
<button on:click={advanceToNextDay} disabled={advanceButtonDisabled}>Advance to next day</button
>
{/if}

<!-- TODO show section with available predictions / stats -->

{#if gameOver}
<h3>Game Over</h3>
<pre>{JSON.stringify(gameSession, null, 2)}</pre>
<p>Machine health has reached 0%.</p>
<p>Machine health has reached 0%.</p>
{:else}
<div>
<h3>Game Session Details</h3>
<pre>{JSON.stringify(gameSession, null, 2)}</pre>
</div>
<button on:click={advanceToNextDay} disabled={advanceButtonDisabled}
>Advance to next day</button
>
{/if}
{:else}
<button on:click={startSession}>Start Session</button>
{/if}
</div>
25 changes: 18 additions & 7 deletions mvp/server/analysis.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import math
from mvp.server.constants import OIL_AGE_MAPPING_MAX, TEMPERATURE_MAPPING_MAX, \
MECHANICAL_WEAR_MAPPING_MAX
from mvp.server.math_utils import map_value


def exponential_decay(t: int, start: int, decay_speed: float) -> float:
return start - math.exp(t * decay_speed)
def compute_decay_speed(parameter_values: 'OperationalParameters') -> float:
# TODO: calibrate these weights
temperature_weight = 0.001
oil_age_weight = 0.001
mechanical_wear_weight = 0.01

# Made-up calculation involving operational parameters: temperature, oil age, mechanical wear
computed = parameter_values.temperature * temperature_weight + \
parameter_values.oil_age * oil_age_weight + \
parameter_values.mechanical_wear * mechanical_wear_weight

def get_health_percentage(current_step: int) -> int:
raw_val = round(exponential_decay(current_step, start=100, decay_speed=0.2))
return min(100, max(0, raw_val))
mapping_max = TEMPERATURE_MAPPING_MAX * temperature_weight + \
OIL_AGE_MAPPING_MAX * oil_age_weight + \
MECHANICAL_WEAR_MAPPING_MAX * mechanical_wear_weight

return map_value(computed, from_low=0, from_high=mapping_max, to_low=0, to_high=0.3)

def default_rul_prediction_fn(current_step: int) -> int | None:

def default_rul_prediction_fn(current_timestep: int) -> int | None:
return None
9 changes: 3 additions & 6 deletions mvp/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from fastapi.middleware.cors import CORSMiddleware
from starlette.responses import RedirectResponse, JSONResponse

from mvp.server.data_models import GameSession, GameSessionDTO
from mvp.server.data_models.GameSession import GameSession, GameSessionDTO

app = FastAPI()
app.add_middleware(
Expand All @@ -19,9 +19,6 @@

@app.on_event("shutdown")
async def cleanup_sessions():
for session in sessions.values():
session.stop_incrementing()

sessions.clear()


Expand All @@ -35,7 +32,7 @@ async def create_session() -> GameSessionDTO:
new_session_id = uuid.uuid4().hex

if new_session_id not in sessions:
session = GameSession(id=new_session_id)
session = GameSession.new_game_session(_id=new_session_id)
sessions[new_session_id] = session

return GameSessionDTO.from_session(sessions[new_session_id])
Expand All @@ -58,7 +55,7 @@ async def advance(session_id: str) -> GameSessionDTO | JSONResponse:

session = sessions[session_id]

# TODO: do something with the returned list of MachineStats
# TODO: do something with the returned list of MachineStats. May be useful for prediction functionality.
await session.advance_one_turn()

return GameSessionDTO.from_session(session)
6 changes: 5 additions & 1 deletion mvp/server/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
DEFAULT_SESSION_ID = 'test'
GAME_TICK_INTERVAL = 0.5
STEPS_PER_MOVE = 8
TIMESTEPS_PER_MOVE = 8 # we conceptualize every player move as a full working day, or "8 hours"
TEMPERATURE_STARTING_POINT = 20
OIL_AGE_MAPPING_MAX = 365
TEMPERATURE_MAPPING_MAX = 200
MECHANICAL_WEAR_MAPPING_MAX = 1000
Loading
Loading