Update maximo

feature/reliability_stat
Cizz22 5 months ago
parent 8ae93be047
commit 536c8fc889

@ -63,10 +63,18 @@ DATABASE_ENGINE_POOL_SIZE = config("DATABASE_ENGINE_POOL_SIZE", cast=int, defaul
DATABASE_ENGINE_MAX_OVERFLOW = config( DATABASE_ENGINE_MAX_OVERFLOW = config(
"DATABASE_ENGINE_MAX_OVERFLOW", cast=int, default=0 "DATABASE_ENGINE_MAX_OVERFLOW", cast=int, default=0
) )
# Deal with DB disconnects
# https://docs.sqlalchemy.org/en/20/core/pooling.html#pool-disconnects COLLECTOR_HOSTNAME = config("COLLECTOR_HOSTNAME")
DATABASE_ENGINE_POOL_PING = config("DATABASE_ENGINE_POOL_PING", default=False) COLLECTOR_PORT = config("COLLECTOR_PORT", default="5432")
COLLECTOR_CREDENTIAL_USER = config("COLLECTOR_CREDENTIAL_USER")
COLLECTOR_CREDENTIAL_PASSWORD = config("COLLECTOR_CREDENTIAL_PASSWORD")
QUOTED_COLLECTOR_CREDENTIAL_PASSWORD = parse.quote(str(COLLECTOR_CREDENTIAL_PASSWORD))
COLLECTOR_NAME = config("COLLECTOR_NAME")
# Deal w
SQLALCHEMY_DATABASE_URI = f"postgresql+asyncpg://{_DATABASE_CREDENTIAL_USER}:{_QUOTED_DATABASE_PASSWORD}@{DATABASE_HOSTNAME}:{DATABASE_PORT}/{DATABASE_NAME}" SQLALCHEMY_DATABASE_URI = f"postgresql+asyncpg://{_DATABASE_CREDENTIAL_USER}:{_QUOTED_DATABASE_PASSWORD}@{DATABASE_HOSTNAME}:{DATABASE_PORT}/{DATABASE_NAME}"
SQLALCHEMY_COLLECTOR_URI = f"postgresql+asyncpg://{COLLECTOR_CREDENTIAL_USER}:{QUOTED_COLLECTOR_CREDENTIAL_PASSWORD}@{COLLECTOR_HOSTNAME}:{COLLECTOR_PORT}/{COLLECTOR_NAME}"
TIMEZONE = "Asia/Jakarta" TIMEZONE = "Asia/Jakarta"

@ -23,7 +23,7 @@ from starlette.routing import compile_path
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
from src.api import api_router from src.api import api_router
from src.database.core import async_session, engine from src.database.core import async_session, engine, async_collector_session
from src.enums import ResponseStatus from src.enums import ResponseStatus
from src.exceptions import handle_exception from src.exceptions import handle_exception
from src.logging import configure_logging from src.logging import configure_logging
@ -72,11 +72,16 @@ async def db_session_middleware(request: Request, call_next):
try: try:
session = async_scoped_session(async_session, scopefunc=get_request_id) session = async_scoped_session(async_session, scopefunc=get_request_id)
request.state.db = session() request.state.db = session()
collector_session = async_scoped_session(async_collector_session, scopefunc=get_request_id)
request.state.collector_db = collector_session()
response = await call_next(request) response = await call_next(request)
except Exception as e: except Exception as e:
raise e from None raise e from None
finally: finally:
await request.state.db.close() await request.state.db.close()
await request.state.collector_db.close()
_request_id_ctx_var.reset(ctx_token) _request_id_ctx_var.reset(ctx_token)
return response return response

@ -0,0 +1,42 @@
from sqlalchemy import Column, BigInteger, Integer, Float, String, Text, DateTime
from src.database.core import CollectorBase
class WorkOrderData(CollectorBase):
__tablename__ = "wo_staging_3"
id = Column(BigInteger, primary_key=True, autoincrement=True)
assetnum = Column(Text, nullable=True)
description1 = Column(Text, nullable=True)
unit = Column(Integer, nullable=True)
location = Column(String(25), nullable=True)
system_tag = Column(String(25), nullable=True)
wonum = Column(String(10), nullable=True)
description2 = Column(Text, nullable=True)
wo_complt_comment = Column(Text, nullable=True)
worktype = Column(String(10), nullable=True)
jpnum = Column(String(10), nullable=True)
workgroup = Column(String(30), nullable=True)
mat_cost_max = Column(Float, nullable=True)
serv_cost_max = Column(Float, nullable=True)
total_cost_max = Column(Float, nullable=True)
wo_start = Column(DateTime, nullable=True)
wo_finish = Column(DateTime, nullable=True)
wo_start_olah = Column(DateTime, nullable=True)
wo_finish_olah = Column(DateTime, nullable=True)
reportdate = Column(DateTime, nullable=True)
reportdate_olah = Column(DateTime, nullable=True)
time_to_event = Column(Float, nullable=True)
actstart = Column(DateTime, nullable=True)
actfinish = Column(DateTime, nullable=True)
actstart_olah = Column(DateTime, nullable=True)
actfinish_olah = Column(DateTime, nullable=True)
act_repair_time = Column(Float, nullable=True)
jumlah_labor = Column(Integer, nullable=True)
need_downtime = Column(String(100), nullable=True)
validation_downtime = Column(Integer, nullable=True)
down_0_and_not_oh = Column(Integer, nullable=True)
downtime = Column(Integer, nullable=True)
failure_code = Column(String(10), nullable=True)
problem_code = Column(String(10), nullable=True)
act_finish_wo_start = Column(Float, nullable=True)

@ -1,154 +1,43 @@
from datetime import datetime, timedelta from datetime import datetime
from typing import Any, Dict from sqlalchemy import select, func, cast, Numeric
from sqlalchemy.orm import Session
import httpx from sqlalchemy import and_
from fastapi import HTTPException from sqlalchemy.sql import not_
from starlette.config import Config from src.maximo.model import WorkOrderData # Assuming this is where your model is
from src.database.core import CollectorDbSession
from src.config import MAXIMO_API_KEY, MAXIMO_BASE_URL
async def get_cm_cost_summary(collector_db: CollectorDbSession, last_oh_date:datetime, upcoming_oh_date:datetime):
class MaximoDataMapper: query = select(
""" WorkOrderData.location,
Helper class to map MAXIMO API response to our data structure. (func.sum(WorkOrderData.total_cost_max).cast(Numeric) / func.count(WorkOrderData.wonum)).label('avg_cost')
Update these mappings according to actual MAXIMO API documentation. ).where(
""" and_(
# WorkOrderData.wo_start >= last_oh_date,
def __init__(self, maximo_data: Dict[Any, Any]): # WorkOrderData.wo_start <= upcoming_oh_date,
self.data = maximo_data WorkOrderData.worktype.in_(['CM', 'EM', 'PROACTIVE']),
WorkOrderData.system_tag.in_(['HPB', 'AH', 'APC', 'SCR', 'CL', 'DM', 'CRH', 'ASH', 'BAD', 'DS', 'WTP',
def get_start_date(self) -> datetime: 'MT', 'SUP', 'DCS', 'FF', 'EG', 'AI', 'SPS', 'EVM', 'SCW', 'KLH', 'CH',
""" 'TUR', 'LOT', 'HRH', 'ESP', 'CAE', 'GMC', 'BFT', 'LSH', 'CHB', 'BSS',
Extract start date from MAXIMO data. 'LOS', 'LPB', 'SAC', 'CP', 'EHS', 'RO', 'GG', 'MS', 'CW', 'SO', 'ATT',
TODO: Update this based on actual MAXIMO API response structure 'AFG', 'EHB', 'RP', 'FO', 'PC', 'APE', 'AF', 'DMW', 'BRS', 'GEN', 'ABS',
Example: might be data['startDate'] or data['SCHEDSTART'] etc. 'CHA', 'TR', 'H2', 'BDW', 'LOM', 'ACR', 'AL', 'FW', 'COND', 'CCCW', 'IA',
""" 'GSS', 'BOL', 'SSB', 'CO', 'OA', 'CTH-UPD', 'AS', 'DP']),
# This is a placeholder - update with actual MAXIMO field name WorkOrderData.reportdate.is_not(None),
start_date_str = self.data.get("scheduleStart") WorkOrderData.actstart.is_not(None),
if not start_date_str: WorkOrderData.actfinish.is_not(None),
raise ValueError("Start date not found in MAXIMO data") WorkOrderData.unit.in_([3, 0]),
return datetime.fromisoformat(start_date_str) WorkOrderData.reportdate >= datetime.strptime('2015-01-01', '%Y-%m-%d'),
not_(WorkOrderData.wonum.like('T%'))
def get_end_date(self) -> datetime:
"""
Extract end date from MAXIMO data.
TODO: Update this based on actual MAXIMO API response structure
"""
# This is a placeholder - update with actual MAXIMO field name
end_date_str = self.data.get("scheduleEnd")
if not end_date_str:
raise ValueError("End date not found in MAXIMO data")
return datetime.fromisoformat(end_date_str)
def get_maximo_id(self) -> str:
"""
Extract MAXIMO ID from response.
TODO: Update this based on actual MAXIMO API response structure
"""
# This is a placeholder - update with actual MAXIMO field name
maximo_id = self.data.get("workOrderId")
if not maximo_id:
raise ValueError("MAXIMO ID not found in response")
return str(maximo_id)
def get_status(self) -> str:
"""
Extract status from MAXIMO data.
TODO: Update this based on actual MAXIMO API response structure
"""
# This is a placeholder - update with actual MAXIMO status field and values
status = self.data.get("status", "").upper()
return status
def get_total_cost(self) -> float:
"""
Extract total cost from MAXIMO data.
TODO: Update this based on actual MAXIMO API response structure
"""
# This is a placeholder - update with actual MAXIMO field name
cost = self.data.get("totalCost", 0)
return float(cost)
def get_scope_name(self) -> str:
scope_name = self.data.get("location", "A")
return scope_name
class MaximoService:
def __init__(self):
# TODO: Update these settings based on actual MAXIMO API configuration
self.base_url = MAXIMO_BASE_URL
self.api_key = MAXIMO_API_KEY
async def get_recent_overhaul(self) -> dict:
"""
Fetch most recent overhaul from MAXIMO.
TODO: Update this method based on actual MAXIMO API endpoints and parameters
"""
current_date = datetime.now()
schedule_start = current_date + timedelta(days=30) # Starting in 30 days
schedule_end = schedule_start + timedelta(days=90) # 90 day overhaul period
return {
"scheduleStart": schedule_start.isoformat(),
"scheduleEnd": schedule_end.isoformat(),
"workOrderId": "WO-2024-12345",
"status": "PLAN", # Common Maximo statuses: SCHEDULED, INPRG, COMP, CLOSE
"totalCost": 10000000.00,
"description": "Annual Turbine Overhaul",
"priority": 1,
"location": "A",
"assetDetails": [
{
"assetnum": "ASSET001",
"description": "Gas Turbine",
"status": "OPERATING",
},
{
"assetnum": "ASSET002",
"description": "Steam Turbine",
"status": "OPERATING",
},
],
"workType": "OH", # OH for Overhaul
"createdBy": "MAXADMIN",
"createdDate": (current_date - timedelta(days=10)).isoformat(),
"lastModifiedBy": "MAXADMIN",
"lastModifiedDate": current_date.isoformat(),
}
async with httpx.AsyncClient() as client:
try:
# TODO: Update endpoint and parameters based on actual MAXIMO API
response = await client.get(
f"{self.base_url}/your-endpoint-here",
headers={
"Authorization": f"Bearer {self.api_key}",
# Add any other required headers
},
params={
# Update these parameters based on actual MAXIMO API
"orderBy": "-scheduleEnd", # Example parameter
"limit": 1,
},
)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail=f"MAXIMO API error: {response.text}",
) )
).group_by(
data = response.json() WorkOrderData.location
if not data: ).order_by(
raise HTTPException( func.count(WorkOrderData.wonum).desc()
status_code=404, detail="No recent overhaul found"
) )
result = await collector_db.execute(query)
data = result.all()
# TODO: Update this based on actual MAXIMO response structure return {
return data[0] if isinstance(data, list) else data data.location: data.avg_cost for data in data
}
except httpx.RequestError as e:
raise HTTPException(
status_code=503, detail=f"Failed to connect to MAXIMO: {str(e)}"
)

@ -38,6 +38,31 @@ async def get_overhaul_schedules(*, db_session: DbSession):
def get_overhaul_system_components(): def get_overhaul_system_components():
"""Get all overhaul system components with dummy data.""" """Get all overhaul system components with dummy data."""
powerplant_reliability = {
"Plant Control": 98,
"SPS": 98,
"Turbine": 98,
"Generator": 98,
"Condensate Water": 98,
"Feedwater System": 98,
"Cooling Water": 98,
"SCR": 98,
"Ash Handling": 98,
"Air Flue Gas": 98,
"Boiler": 98,
"SAC-IAC": 98,
"KLH": 98,
"CL": 98,
"Desalination": 98,
"FGD": 98,
"CHS": 98,
"SSB": 98,
"WTP": 98,
}
return powerplant_reliability
return { return {
"HPT": { "HPT": {
"efficiency": "92%", "efficiency": "92%",

@ -3,6 +3,7 @@ from uuid import UUID
from fastapi import APIRouter, HTTPException, Query, status from fastapi import APIRouter, HTTPException, Query, status
from src.database.core import CollectorDbSession
from src.database.service import (CommonParameters, DbSession, from src.database.service import (CommonParameters, DbSession,
search_filter_sort_paginate) search_filter_sort_paginate)
from src.models import StandardResponse from src.models import StandardResponse
@ -20,6 +21,7 @@ router = APIRouter()
async def get_scope_equipments( async def get_scope_equipments(
common: CommonParameters, common: CommonParameters,
overhaul_session: str, overhaul_session: str,
collector_db: CollectorDbSession,
location_tag: Optional[str] = Query(None), location_tag: Optional[str] = Query(None),
scope_name: Optional[str] = Query(None), scope_name: Optional[str] = Query(None),
): ):
@ -30,6 +32,7 @@ async def get_scope_equipments(
location_tag=location_tag, location_tag=location_tag,
scope_name=scope_name, scope_name=scope_name,
overhaul_session_id=overhaul_session, overhaul_session_id=overhaul_session,
collector_db=collector_db,
) )

@ -12,7 +12,7 @@ from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate from src.database.service import CommonParameters, search_filter_sort_paginate
from src.overhaul_activity.utils import get_material_cost, get_service_cost from src.overhaul_activity.utils import get_material_cost, get_service_cost
from src.overhaul_scope.model import OverhaulScope from src.overhaul_scope.model import OverhaulScope
from src.overhaul_scope.service import get as get_session from src.overhaul_scope.service import get as get_session, get_prev_oh
from src.standard_scope.model import MasterEquipment, StandardScope from src.standard_scope.model import MasterEquipment, StandardScope
from src.standard_scope.service import get_by_oh_session_id from src.standard_scope.service import get_by_oh_session_id
from src.workscope_group.model import MasterActivity from src.workscope_group.model import MasterActivity
@ -26,6 +26,8 @@ from .schema import (OverhaulActivityCreate, OverhaulActivityRead,
OverhaulActivityUpdate) OverhaulActivityUpdate)
import json import json
from src.database.core import CollectorDbSession
from src.maximo.service import get_cm_cost_summary
async def get( async def get(
*, db_session: DbSession, assetnum: str, overhaul_session_id: Optional[UUID] = None *, db_session: DbSession, assetnum: str, overhaul_session_id: Optional[UUID] = None
@ -55,7 +57,8 @@ async def get_all(
overhaul_session_id: UUID, overhaul_session_id: UUID,
location_tag: Optional[str] = None, location_tag: Optional[str] = None,
scope_name: Optional[str] = None, scope_name: Optional[str] = None,
all: bool = False all: bool = False,
collector_db: CollectorDbSession
): ):
# query = ( # query = (
# Select(OverhaulActivity) # Select(OverhaulActivity)
@ -84,6 +87,7 @@ async def get_all(
# ) # )
overhaul = await get_overhaul(db_session=common['db_session'], overhaul_session_id=overhaul_session_id) overhaul = await get_overhaul(db_session=common['db_session'], overhaul_session_id=overhaul_session_id)
prev_oh_scope = await get_prev_oh(db_session=common['db_session'], overhaul_session=overhaul)
query = ( query = (
Select(StandardScope) Select(StandardScope)
@ -102,25 +106,25 @@ async def get_all(
num_equipments = len((await common['db_session'].execute(query)).scalars().all()) num_equipments = len((await common['db_session'].execute(query)).scalars().all())
data_cost = get_cost_per_failute() material_cost = await get_cm_cost_summary(collector_db=collector_db, last_oh_date=prev_oh_scope.end_date, upcoming_oh_date=overhaul.start_date)
service_cost = get_service_cost(scope=overhaul.maintenance_type.name, total_equipment=num_equipments)
equipments = await search_filter_sort_paginate(model=query, **common) equipments = await search_filter_sort_paginate(model=query, **common)
data = equipments['items'] data = equipments['items']
results = [] results = []
for equipment in data: for equipment in data:
if not equipment.master_equipment: if not equipment.master_equipment:
continue continue
cost = next((item for item in data_cost if item['location'] == equipment.location_tag), None) cost = material_cost.get(equipment.location_tag, 0)
res = OverhaulActivityRead( res = OverhaulActivityRead(
id=equipment.id, id=equipment.id,
material_cost=cost.get('avg_cost', 0) if cost else 0, material_cost=float(cost),
service_cost=200000000, service_cost=float(service_cost),
location_tag=equipment.location_tag, location_tag=equipment.location_tag,
equipment_name=equipment.master_equipment.name if equipment.master_equipment else None, equipment_name=equipment.master_equipment.name if equipment.master_equipment else None,
oh_scope=overhaul.maintenance_type.name, oh_scope=overhaul.maintenance_type.name,
@ -132,8 +136,9 @@ async def get_all(
return equipments return equipments
async def get_standard_scope_by_session_id(*, db_session: DbSession, overhaul_session_id: UUID): async def get_standard_scope_by_session_id(*, db_session: DbSession, overhaul_session_id: UUID, collector_db: CollectorDbSession):
overhaul = await get_overhaul(db_session=db_session, overhaul_session_id=overhaul_session_id) overhaul = await get_session(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
prev_oh_scope = await get_prev_oh(db_session=db_session, overhaul_session=overhaul)
query = ( query = (
Select(StandardScope) Select(StandardScope)
@ -153,16 +158,17 @@ async def get_standard_scope_by_session_id(*, db_session: DbSession, overhaul_se
data = await db_session.execute(query) data = await db_session.execute(query)
eqs = data.scalars().all() eqs = data.scalars().all()
material_cost = get_material_cost("B", len(eqs))
results = [] results = []
data_cost = get_cost_per_failute() material_cost = await get_cm_cost_summary(collector_db=collector_db, last_oh_date=prev_oh_scope.end_date, upcoming_oh_date=overhaul.start_date)
service_cost = get_service_cost(scope=overhaul.maintenance_type.name, total_equipment=len(eqs))
for equipment in eqs: for equipment in eqs:
cost = next((item for item in data_cost if item['location'] == equipment.location_tag), None) cost = material_cost.get(equipment.location_tag,0)
res = OverhaulActivityRead( res = OverhaulActivityRead(
id=equipment.id, id=equipment.id,
material_cost=cost.get('avg_cost', 0) if cost else 0, material_cost=float(cost),
service_cost=200000000, service_cost=float(service_cost),
location_tag=equipment.location_tag, location_tag=equipment.location_tag,
equipment_name=equipment.master_equipment.name if equipment.master_equipment else None, equipment_name=equipment.master_equipment.name if equipment.master_equipment else None,
oh_scope=overhaul.maintenance_type.name, oh_scope=overhaul.maintenance_type.name,

Loading…
Cancel
Save