add session to scope job

main
Cizz22 11 months ago
parent 2ae301a78b
commit 9a126d013c

@ -1,10 +1,6 @@
import uvicorn
from src.config import PORT, HOST
from src.config import HOST, PORT
if __name__ == "__main__":
uvicorn.run(
"src.main:app",
host=HOST,
port=PORT,
reload=True
)
uvicorn.run("src.main:app", host=HOST, port=PORT, reload=True)

@ -1,11 +1,23 @@
from typing import List, Optional
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from src.auth.service import JWTBearer
from src.calculation_budget_constrains.router import \
router as calculation_budget_constraint
from src.calculation_target_reliability.router import \
router as calculation_target_reliability
from src.calculation_time_constrains.router import \
router as calculation_time_constrains_router
from src.job.router import router as job_router
from src.overhaul.router import router as overhaul_router
from src.overhaul_activity.router import router as overhaul_activity_router
from src.overhaul_job.router import router as job_overhaul_router
from src.overhaul_scope.router import router as scope_router
from src.scope_equipment.router import router as scope_equipment_router
from src.scope_equipment_job.router import router as scope_equipment_job_router
# from src.overhaul_scope.router import router as scope_router
# from src.scope_equipment.router import router as scope_equipment_router
@ -15,20 +27,11 @@ from src.auth.service import JWTBearer
# # from src.overhaul_schedule.router import router as ovehaul_schedule_router
# from src.scope_equipment_part.router import router as scope_equipment_part_router
# from src.calculation_target_reliability.router import router as calculation_target_reliability
#
#
# from src.master_activity.router import router as activity_router
from src.overhaul.router import router as overhaul_router
from src.scope_equipment.router import router as scope_equipment_router
from src.overhaul_scope.router import router as scope_router
from src.overhaul_activity.router import router as overhaul_activity_router
from src.calculation_target_reliability.router import router as calculation_target_reliability
from src.scope_equipment_job.router import router as scope_equipment_job_router
from src.job.router import router as job_router
from src.calculation_time_constrains.router import router as calculation_time_constrains_router
from src.calculation_budget_constrains.router import router as calculation_budget_constraint
from src.overhaul_job.router import router as job_overhaul_router
class ErrorMessage(BaseModel):
msg: str
@ -55,18 +58,20 @@ def healthcheck():
return {"status": "ok"}
authenticated_api_router = APIRouter(dependencies=[Depends(JWTBearer())],
)
authenticated_api_router = APIRouter(
dependencies=[Depends(JWTBearer())],
)
# overhaul data
authenticated_api_router.include_router(
overhaul_router, prefix="/overhauls", tags=["overhaul"])
overhaul_router, prefix="/overhauls", tags=["overhaul"]
)
authenticated_api_router.include_router(
job_router, prefix="/jobs", tags=["job"])
authenticated_api_router.include_router(job_router, prefix="/jobs", tags=["job"])
# # Overhaul session data
authenticated_api_router.include_router(
scope_router, prefix="/overhaul-session", tags=["overhaul-session"])
scope_router, prefix="/overhaul-session", tags=["overhaul-session"]
)
authenticated_api_router.include_router(
scope_equipment_router, prefix="/scope-equipments", tags=["scope_equipment"]
@ -77,7 +82,9 @@ authenticated_api_router.include_router(
)
authenticated_api_router.include_router(
scope_equipment_job_router, prefix="/scope-equipment-jobs", tags=["scope_equipment", "job"]
scope_equipment_job_router,
prefix="/scope-equipment-jobs",
tags=["scope_equipment", "job"],
)
authenticated_api_router.include_router(
@ -109,20 +116,25 @@ calculation_router = APIRouter(prefix="/calculation", tags=["calculations"])
# Time constrains
calculation_router.include_router(
calculation_time_constrains_router, prefix="/time-constraint", tags=["calculation", "time_constraint"])
calculation_time_constrains_router,
prefix="/time-constraint",
tags=["calculation", "time_constraint"],
)
# Target reliability
calculation_router.include_router(
calculation_target_reliability, prefix="/target-reliability", tags=["calculation", "target_reliability"]
calculation_target_reliability,
prefix="/target-reliability",
tags=["calculation", "target_reliability"],
)
# # Budget Constrain
calculation_router.include_router(
calculation_budget_constraint, prefix="/budget-constraint", tags=["calculation", "budget_constraint"]
calculation_budget_constraint,
prefix="/budget-constraint",
tags=["calculation", "budget_constraint"],
)
authenticated_api_router.include_router(
calculation_router
)
authenticated_api_router.include_router(calculation_router)
api_router.include_router(authenticated_api_router)

@ -1,5 +1,3 @@
from pydantic import BaseModel

@ -1,10 +1,13 @@
# app/auth/auth_bearer.py
from typing import Annotated, Optional
from fastapi import Depends, Request, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import requests
from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
import src.config as config
from .model import UserBase
@ -13,21 +16,24 @@ class JWTBearer(HTTPBearer):
super(JWTBearer, self).__init__(auto_error=auto_error)
async def __call__(self, request: Request):
credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
credentials: HTTPAuthorizationCredentials = await super(
JWTBearer, self
).__call__(request)
if credentials:
if not credentials.scheme == "Bearer":
raise HTTPException(
status_code=403, detail="Invalid authentication scheme.")
status_code=403, detail="Invalid authentication scheme."
)
user_info = self.verify_jwt(credentials.credentials)
if not user_info:
raise HTTPException(
status_code=403, detail="Invalid token or expired token.")
status_code=403, detail="Invalid token or expired token."
)
request.state.user = user_info
return user_info
else:
raise HTTPException(
status_code=403, detail="Invalid authorization code.")
raise HTTPException(status_code=403, detail="Invalid authorization code.")
def verify_jwt(self, jwtoken: str) -> Optional[UserBase]:
try:
@ -40,7 +46,7 @@ class JWTBearer(HTTPBearer):
return None
user_data = response.json()
return UserBase(**user_data['data'])
return UserBase(**user_data["data"])
except Exception as e:
print(f"Token verification error: {str(e)}")
@ -51,6 +57,7 @@ class JWTBearer(HTTPBearer):
async def get_current_user(request: Request) -> UserBase:
return request.state.user
async def get_token(request: Request):
token = request.headers.get("Authorization")
@ -59,5 +66,6 @@ async def get_token(request: Request):
return ""
CurrentUser = Annotated[UserBase, Depends(get_current_user)]
Token = Annotated[str, Depends(get_token)]

@ -1,19 +1,26 @@
from typing import Dict, List, Optional
from fastapi import APIRouter, HTTPException, status
from fastapi.params import Query
from src.database.core import DbSession
from src.models import StandardResponse
from .service import get_all_budget_constrains
from src.models import StandardResponse
from src.database.core import DbSession
router = APIRouter()
@router.get("", response_model=StandardResponse[List[Dict]])
async def get_target_reliability(db_session: DbSession, scope_name: Optional[str] = Query(None), cost_threshold: float = Query(100)):
async def get_target_reliability(
db_session: DbSession,
scope_name: Optional[str] = Query(None),
cost_threshold: float = Query(100),
):
"""Get all scope pagination."""
results = await get_all_budget_constrains(db_session=db_session, scope_name=scope_name, cost_threshold=cost_threshold)
results = await get_all_budget_constrains(
db_session=db_session, scope_name=scope_name, cost_threshold=cost_threshold
)
return StandardResponse(
data=results,

@ -1,9 +1,9 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
from src.models import DefultBase, Pagination
@ -16,13 +16,13 @@ class OverhaulCriticalParts(OverhaulBase):
class OverhaulSchedules(OverhaulBase):
schedules: List[Dict[str, Any]
] = Field(..., description="List of schedules")
schedules: List[Dict[str, Any]] = Field(..., description="List of schedules")
class OverhaulSystemComponents(OverhaulBase):
systemComponents: Dict[str,
Any] = Field(..., description="List of system components")
systemComponents: Dict[str, Any] = Field(
..., description="List of system components"
)
class OverhaulRead(OverhaulBase):

@ -1,16 +1,17 @@
import random
from sqlalchemy import Select, Delete
from typing import Optional
from src.database.core import DbSession
from sqlalchemy import Delete, Select
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.scope_equipment.model import ScopeEquipment
from src.scope_equipment.service import get_by_scope_name
async def get_all_budget_constrains(*, db_session: DbSession, scope_name: str, cost_threshold: float = 100.0):
async def get_all_budget_constrains(
*, db_session: DbSession, scope_name: str, cost_threshold: float = 100.0
):
"""Get all overhaul overview with EAF values that sum to 100%."""
equipments = await get_by_scope_name(db_session=db_session, scope_name=scope_name)
@ -20,23 +21,23 @@ async def get_all_budget_constrains(*, db_session: DbSession, scope_name: str, c
# Create result array of dictionaries
result = [
{
'id': equipment.id,
'assetnum': equipment.assetnum,
'location_tag': equipment.master_equipment.location_tag,
'name': equipment.master_equipment.name,
'total_cost': 1000000 + random.randint(10000, 5000000)
"id": equipment.id,
"assetnum": equipment.assetnum,
"location_tag": equipment.master_equipment.location_tag,
"name": equipment.master_equipment.name,
"total_cost": 1000000 + random.randint(10000, 5000000),
}
for equipment in equipments
]
result.sort(key=lambda x: x['total_cost'], reverse=True)
result.sort(key=lambda x: x["total_cost"], reverse=True)
# Filter equipment up to threshold
cumulative_cost = 0
filtered_result = []
for equipment in result:
cumulative_cost += equipment['total_cost']
cumulative_cost += equipment["total_cost"]
if cumulative_cost >= cost_threshold:
break

@ -1,19 +1,26 @@
from typing import Dict, List, Optional
from fastapi import APIRouter, HTTPException, status
from fastapi.params import Query
from src.database.core import DbSession
from src.models import StandardResponse
from .service import get_all_target_reliability
from src.models import StandardResponse
from src.database.core import DbSession
router = APIRouter()
@router.get("", response_model=StandardResponse[List[Dict]])
async def get_target_reliability(db_session: DbSession, scope_name: Optional[str] = Query(None), eaf_threshold: float = Query(100)):
async def get_target_reliability(
db_session: DbSession,
scope_name: Optional[str] = Query(None),
eaf_threshold: float = Query(100),
):
"""Get all scope pagination."""
results = await get_all_target_reliability(db_session=db_session, scope_name=scope_name, eaf_threshold=eaf_threshold)
results = await get_all_target_reliability(
db_session=db_session, scope_name=scope_name, eaf_threshold=eaf_threshold
)
return StandardResponse(
data=results,

@ -1,9 +1,9 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
from src.models import DefultBase, Pagination
@ -16,13 +16,13 @@ class OverhaulCriticalParts(OverhaulBase):
class OverhaulSchedules(OverhaulBase):
schedules: List[Dict[str, Any]
] = Field(..., description="List of schedules")
schedules: List[Dict[str, Any]] = Field(..., description="List of schedules")
class OverhaulSystemComponents(OverhaulBase):
systemComponents: Dict[str,
Any] = Field(..., description="List of system components")
systemComponents: Dict[str, Any] = Field(
..., description="List of system components"
)
class OverhaulRead(OverhaulBase):

@ -1,20 +1,24 @@
from sqlalchemy import Select, Delete
from typing import Optional
from src.database.core import DbSession
from sqlalchemy import Delete, Select
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.scope_equipment.model import ScopeEquipment
from src.scope_equipment.service import get_by_scope_name
from src.scope_equipment_job.service import get_equipment_level_by_no
async def get_all_target_reliability(*, db_session: DbSession, scope_name: str, eaf_threshold: float = 100.0):
async def get_all_target_reliability(
*, db_session: DbSession, scope_name: str, eaf_threshold: float = 100.0
):
"""Get all overhaul overview with EAF values that sum to 100%, aggregated by system."""
equipments = await get_by_scope_name(db_session=db_session, scope_name=scope_name)
equipment_system = await get_equipment_level_by_no(db_session=db_session, level=1)
equipment_subsystem = await get_equipment_level_by_no(db_session=db_session, level=2)
equipment_subsystem = await get_equipment_level_by_no(
db_session=db_session, level=2
)
# If no equipments found, return empty list
if not equipments:
return []
@ -23,13 +27,13 @@ async def get_all_target_reliability(*, db_session: DbSession, scope_name: str,
n = len(equipments)
base_value = 100 / n # Even distribution as base
# Generate EAF values with ±30% variation from base
eaf_values = [
base_value + random.uniform(-0.3 * base_value, 0.3 * base_value)
for _ in range(n)
]
# Normalize to ensure sum is 100
total = sum(eaf_values)
eaf_values = [(v * 100 / total) for v in eaf_values]
@ -37,49 +41,53 @@ async def get_all_target_reliability(*, db_session: DbSession, scope_name: str,
# Create result array of dictionaries
result = [
{
'id': equipment.id,
'assetnum': equipment.assetnum,
'location_tag': equipment.master_equipment.location_tag,
'name': equipment.master_equipment.name,
'parent_id': equipment.master_equipment.parent_id, # Add parent_id to identify the system
'eaf': round(eaf, 4) # Add EAF value
"id": equipment.id,
"assetnum": equipment.assetnum,
"location_tag": equipment.master_equipment.location_tag,
"name": equipment.master_equipment.name,
"parent_id": equipment.master_equipment.parent_id, # Add parent_id to identify the system
"eaf": round(eaf, 4), # Add EAF value
}
for equipment, eaf in zip(equipments, eaf_values)
]
# Group equipment by system
sub_system = {subsystem.id: subsystem.parent_id for subsystem in equipment_subsystem}
systems = {system.id: {'name': system.name, 'total_eaf': 0, 'equipments': []} for system in equipment_system}
sub_system = {
subsystem.id: subsystem.parent_id for subsystem in equipment_subsystem
}
systems = {
system.id: {"name": system.name, "total_eaf": 0, "equipments": []}
for system in equipment_system
}
for equipment in result:
if equipment['parent_id'] in sub_system:
systems[sub_system[equipment['parent_id']]]['equipments'].append(equipment)
systems[sub_system[equipment['parent_id']]]['total_eaf'] += equipment['eaf']
if equipment["parent_id"] in sub_system:
systems[sub_system[equipment["parent_id"]]]["equipments"].append(equipment)
systems[sub_system[equipment["parent_id"]]]["total_eaf"] += equipment["eaf"]
# Convert the systems dictionary to a list of aggregated results
aggregated_result = [
{
'system_id': system_id,
'system_name': system_data['name'],
'total_eaf': round(system_data['total_eaf'], 4),
'equipments': system_data['equipments']
"system_id": system_id,
"system_name": system_data["name"],
"total_eaf": round(system_data["total_eaf"], 4),
"equipments": system_data["equipments"],
}
for system_id, system_data in systems.items()
]
# Sort the aggregated result by total_eaf in descending order
aggregated_result.sort(key=lambda x: x['total_eaf'], reverse=True)
aggregated_result.sort(key=lambda x: x["total_eaf"], reverse=True)
# Filter systems up to the threshold
cumulative_eaf = 0
filtered_aggregated_result = []
for system in aggregated_result:
cumulative_eaf += system['total_eaf']
cumulative_eaf += system["total_eaf"]
filtered_aggregated_result.append(system)
if cumulative_eaf >= eaf_threshold:
break
return filtered_aggregated_result
return filtered_aggregated_result

@ -2,37 +2,36 @@ from typing import Optional
from uuid import UUID
import numpy as np
from fastapi import HTTPException
from fastapi import status
from sqlalchemy import Select
from sqlalchemy import func
from sqlalchemy import select
from fastapi import HTTPException, status
from sqlalchemy import Select, func, select
from sqlalchemy.orm import joinedload
from src.database.core import DbSession
from src.auth.service import Token
from src.database.core import DbSession
from src.overhaul_scope.service import get_all
from src.scope_equipment.model import ScopeEquipment
from src.scope_equipment.service import get_by_assetnum
from src.workorder.model import MasterWorkOrder
from .schema import CalculationTimeConstrainsParametersCreate
from .schema import CalculationTimeConstrainsParametersRead
from .schema import CalculationTimeConstrainsParametersRetrive
from .schema import CalculationTimeConstrainsRead
from .service import create_calculation_result_service
from .service import create_param_and_data
from .service import get_avg_cost_by_asset
from .service import get_calculation_by_reference_and_parameter
from .service import get_calculation_data_by_id
from .service import get_calculation_result
from .service import get_corrective_cost_time_chart
from .service import get_overhaul_cost_by_time_chart
async def get_create_calculation_parameters(*, db_session: DbSession, calculation_id: Optional[str] = None):
from .schema import (CalculationTimeConstrainsParametersCreate,
CalculationTimeConstrainsParametersRead,
CalculationTimeConstrainsParametersRetrive,
CalculationTimeConstrainsRead)
from .service import (create_calculation_result_service, create_param_and_data,
get_avg_cost_by_asset,
get_calculation_by_reference_and_parameter,
get_calculation_data_by_id, get_calculation_result,
get_corrective_cost_time_chart,
get_overhaul_cost_by_time_chart)
async def get_create_calculation_parameters(
*, db_session: DbSession, calculation_id: Optional[str] = None
):
if calculation_id is not None:
calculation = await get_calculation_data_by_id(calculation_id=calculation_id, db_session=db_session)
calculation = await get_calculation_data_by_id(
calculation_id=calculation_id, db_session=db_session
)
if not calculation:
raise HTTPException(
@ -43,13 +42,13 @@ async def get_create_calculation_parameters(*, db_session: DbSession, calculatio
return CalculationTimeConstrainsParametersRead(
costPerFailure=calculation.parameter.avg_failure_cost,
overhaulCost=calculation.parameter.overhaul_cost,
reference=calculation
reference=calculation,
)
stmt = (
select(
ScopeEquipment.scope_id,
func.avg(MasterWorkOrder.total_cost_max).label('average_cost')
func.avg(MasterWorkOrder.total_cost_max).label("average_cost"),
)
.outerjoin(MasterWorkOrder, ScopeEquipment.assetnum == MasterWorkOrder.assetnum)
.group_by(ScopeEquipment.scope_id)
@ -60,8 +59,10 @@ async def get_create_calculation_parameters(*, db_session: DbSession, calculatio
costFailure = results.all()
scopes = await get_all(db_session=db_session)
avaiableScopes = {scope.id: scope.scope_name for scope in scopes}
costFailurePerScope = {avaiableScopes.get(
costPerFailure[0]): costPerFailure[1] for costPerFailure in costFailure}
costFailurePerScope = {
avaiableScopes.get(costPerFailure[0]): costPerFailure[1]
for costPerFailure in costFailure
}
return CalculationTimeConstrainsParametersRetrive(
costPerFailure=costFailurePerScope,
@ -78,18 +79,35 @@ async def get_create_calculation_parameters(*, db_session: DbSession, calculatio
)
async def create_calculation(*,token:str, db_session: DbSession, calculation_time_constrains_in: CalculationTimeConstrainsParametersCreate, created_by: str):
async def create_calculation(
*,
token: str,
db_session: DbSession,
calculation_time_constrains_in: CalculationTimeConstrainsParametersCreate,
created_by: str
):
calculation_data = await create_param_and_data(
db_session=db_session, calculation_param_in=calculation_time_constrains_in, created_by=created_by)
db_session=db_session,
calculation_param_in=calculation_time_constrains_in,
created_by=created_by,
)
results = await create_calculation_result_service(db_session=db_session, calculation=calculation_data, token=token)
results = await create_calculation_result_service(
db_session=db_session, calculation=calculation_data, token=token
)
return results
async def get_or_create_scope_equipment_calculation(*, db_session: DbSession, scope_calculation_id, calculation_time_constrains_in: Optional[CalculationTimeConstrainsParametersCreate]):
scope_calculation = await get_calculation_data_by_id(db_session=db_session, calculation_id=scope_calculation_id)
async def get_or_create_scope_equipment_calculation(
*,
db_session: DbSession,
scope_calculation_id,
calculation_time_constrains_in: Optional[CalculationTimeConstrainsParametersCreate]
):
scope_calculation = await get_calculation_data_by_id(
db_session=db_session, calculation_id=scope_calculation_id
)
if not scope_calculation:
raise HTTPException(
@ -103,5 +121,5 @@ async def get_or_create_scope_equipment_calculation(*, db_session: DbSession, sc
reference=scope_calculation.overhaul_session_id,
results=scope_calculation.results,
optimum_oh=scope_calculation.optimum_oh_day,
equipment_results=scope_calculation.equipment_results
equipment_results=scope_calculation.equipment_results,
)

@ -1,9 +1,10 @@
from enum import Enum
from typing import List, Optional, Union
from sqlalchemy import UUID, Column, Float, ForeignKey, Integer, String, JSON, Numeric, Boolean
from sqlalchemy import (JSON, UUID, Boolean, Column, Float, ForeignKey,
Integer, Numeric, String)
from sqlalchemy.orm import relationship
from src.database.core import Base, DbSession
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin, UUIDMixin
@ -20,8 +21,7 @@ class CalculationParam(Base, DefaultMixin, IdentityMixin):
overhaul_cost = Column(Float, nullable=False)
# Relationships
calculation_data = relationship(
"CalculationData", back_populates="parameter")
calculation_data = relationship("CalculationData", back_populates="parameter")
results = relationship("CalculationResult", back_populates="parameter")
# @classmethod
@ -60,42 +60,40 @@ class CalculationParam(Base, DefaultMixin, IdentityMixin):
class CalculationData(Base, DefaultMixin, IdentityMixin):
__tablename__ = "oh_tr_calculation_data"
parameter_id = Column(UUID(as_uuid=True), ForeignKey(
'oh_ms_calculation_param.id'), nullable=True)
overhaul_session_id= Column(UUID(as_uuid=True), ForeignKey('oh_ms_overhaul_scope.id'))
parameter_id = Column(
UUID(as_uuid=True), ForeignKey("oh_ms_calculation_param.id"), nullable=True
)
overhaul_session_id = Column(
UUID(as_uuid=True), ForeignKey("oh_ms_overhaul_scope.id")
)
optimum_oh_day = Column(Integer, nullable=True)
session = relationship(
"OverhaulScope", lazy="raise")
session = relationship("OverhaulScope", lazy="raise")
parameter = relationship("CalculationParam", back_populates="calculation_data")
parameter = relationship(
"CalculationParam", back_populates="calculation_data")
equipment_results = relationship(
"CalculationEquipmentResult", lazy="raise", viewonly=True
)
results = relationship(
"CalculationResult", lazy="raise", viewonly=True
)
results = relationship("CalculationResult", lazy="raise", viewonly=True)
@classmethod
async def create_with_param(
cls,
overhaul_session_id: str,
overhaul_session_id: str,
db: DbSession,
avg_failure_cost: Optional[float],
overhaul_cost: Optional[float],
created_by: str,
params_id: Optional[UUID]
params_id: Optional[UUID],
):
if not params_id:
# Create Params
params = CalculationParam(
avg_failure_cost=avg_failure_cost,
overhaul_cost=overhaul_cost,
created_by=created_by
created_by=created_by,
)
db.add(params)
@ -105,7 +103,7 @@ class CalculationData(Base, DefaultMixin, IdentityMixin):
calculation_data = cls(
overhaul_session_id=overhaul_session_id,
created_by=created_by,
parameter_id=params_id
parameter_id=params_id,
)
db.add(calculation_data)
@ -120,10 +118,12 @@ class CalculationResult(Base, DefaultMixin):
__tablename__ = "oh_tr_calculation_result"
parameter_id = Column(UUID(as_uuid=True), ForeignKey(
'oh_ms_calculation_param.id'), nullable=False)
calculation_data_id = Column(UUID(as_uuid=True), ForeignKey(
'oh_tr_calculation_data.id'), nullable=False)
parameter_id = Column(
UUID(as_uuid=True), ForeignKey("oh_ms_calculation_param.id"), nullable=False
)
calculation_data_id = Column(
UUID(as_uuid=True), ForeignKey("oh_tr_calculation_data.id"), nullable=False
)
day = Column(Integer, nullable=False)
corrective_cost = Column(Float, nullable=False)
overhaul_cost = Column(Float, nullable=False)
@ -136,25 +136,22 @@ class CalculationResult(Base, DefaultMixin):
class CalculationEquipmentResult(Base, DefaultMixin):
__tablename__ = "oh_tr_calculation_equipment_result"
corrective_costs = Column(JSON, nullable=False)
overhaul_costs = Column(JSON, nullable=False)
daily_failures = Column(JSON, nullable=False)
assetnum = Column(String(255), nullable=False)
material_cost = Column(Float, nullable=False)
service_cost = Column(Float, nullable=False)
calculation_data_id = Column(UUID(as_uuid=True), ForeignKey('oh_tr_calculation_data.id'), nullable=True)
calculation_data_id = Column(
UUID(as_uuid=True), ForeignKey("oh_tr_calculation_data.id"), nullable=True
)
optimum_day = Column(Integer, default=1)
is_included = Column(Boolean, default=True)
master_equipment = relationship(
"MasterEquipment",
lazy="joined",
primaryjoin="and_(CalculationEquipmentResult.assetnum == foreign(MasterEquipment.assetnum))",
uselist=False # Add this if it's a one-to-one relationship
uselist=False, # Add this if it's a one-to-one relationship
)

@ -1,7 +1,4 @@
from typing import List
from typing import Optional
from typing import Union
from typing import List, Optional, Union
from fastapi import APIRouter
from fastapi.params import Query
@ -10,32 +7,47 @@ from src.auth.service import CurrentUser, Token
from src.database.core import DbSession
from src.models import StandardResponse
from .flows import create_calculation
from .flows import get_create_calculation_parameters
from .flows import get_or_create_scope_equipment_calculation
from .schema import CalculationResultsRead
from .schema import CalculationSelectedEquipmentUpdate
from .schema import CalculationTimeConstrainsCreate
from .schema import CalculationTimeConstrainsParametersCreate
from .schema import CalculationTimeConstrainsParametersRead
from .schema import CalculationTimeConstrainsParametersRetrive
from .schema import CalculationTimeConstrainsRead
from .service import get_calculation_result
from .service import get_calculation_result_by_day
from .service import bulk_update_equipment
from .flows import (create_calculation, get_create_calculation_parameters,
get_or_create_scope_equipment_calculation)
from .schema import (CalculationResultsRead,
CalculationSelectedEquipmentUpdate,
CalculationTimeConstrainsCreate,
CalculationTimeConstrainsParametersCreate,
CalculationTimeConstrainsParametersRead,
CalculationTimeConstrainsParametersRetrive,
CalculationTimeConstrainsRead)
from .service import (bulk_update_equipment, get_calculation_result,
get_calculation_result_by_day)
router = APIRouter()
@router.post("", response_model=StandardResponse[Union[str, CalculationTimeConstrainsRead]])
async def create_calculation_time_constrains(token:Token ,db_session: DbSession, current_user: CurrentUser, calculation_time_constrains_in: CalculationTimeConstrainsParametersCreate, scope_calculation_id: Optional[str] = Query(None), with_results: Optional[int] = Query(0)):
@router.post(
"", response_model=StandardResponse[Union[str, CalculationTimeConstrainsRead]]
)
async def create_calculation_time_constrains(
token: Token,
db_session: DbSession,
current_user: CurrentUser,
calculation_time_constrains_in: CalculationTimeConstrainsParametersCreate,
scope_calculation_id: Optional[str] = Query(None),
with_results: Optional[int] = Query(0),
):
"""Save calculation time constrains Here"""
if scope_calculation_id:
results = await get_or_create_scope_equipment_calculation(db_session=db_session, scope_calculation_id=scope_calculation_id, calculation_time_constrains_in=calculation_time_constrains_in)
results = await get_or_create_scope_equipment_calculation(
db_session=db_session,
scope_calculation_id=scope_calculation_id,
calculation_time_constrains_in=calculation_time_constrains_in,
)
else:
results = await create_calculation(token=token ,db_session=db_session, calculation_time_constrains_in=calculation_time_constrains_in, created_by=current_user.name)
results = await create_calculation(
token=token,
db_session=db_session,
calculation_time_constrains_in=calculation_time_constrains_in,
created_by=current_user.name,
)
if not with_results:
results = str(results.id)
@ -43,11 +55,23 @@ async def create_calculation_time_constrains(token:Token ,db_session: DbSession,
return StandardResponse(data=results, message="Data created successfully")
@router.get("/parameters", response_model=StandardResponse[Union[CalculationTimeConstrainsParametersRetrive, CalculationTimeConstrainsParametersRead]])
async def get_calculation_parameters(db_session: DbSession, calculation_id: Optional[str] = Query(default=None)):
@router.get(
"/parameters",
response_model=StandardResponse[
Union[
CalculationTimeConstrainsParametersRetrive,
CalculationTimeConstrainsParametersRead,
]
],
)
async def get_calculation_parameters(
db_session: DbSession, calculation_id: Optional[str] = Query(default=None)
):
"""Get all calculation parameter."""
parameters = await get_create_calculation_parameters(db_session=db_session, calculation_id=calculation_id)
parameters = await get_create_calculation_parameters(
db_session=db_session, calculation_id=calculation_id
)
return StandardResponse(
data=parameters,
@ -55,9 +79,13 @@ async def get_calculation_parameters(db_session: DbSession, calculation_id: Opti
)
@router.get("/{calculation_id}", response_model=StandardResponse[CalculationTimeConstrainsRead])
@router.get(
"/{calculation_id}", response_model=StandardResponse[CalculationTimeConstrainsRead]
)
async def get_calculation_results(db_session: DbSession, calculation_id):
results = await get_calculation_result(db_session=db_session, calculation_id=calculation_id)
results = await get_calculation_result(
db_session=db_session, calculation_id=calculation_id
)
return StandardResponse(
data=results,
@ -65,15 +93,37 @@ async def get_calculation_results(db_session: DbSession, calculation_id):
)
@router.post("/{calculation_id}/simulation", response_model=StandardResponse[CalculationResultsRead])
async def get_simulation_result(db_session:DbSession, calculation_id, calculation_simuation_in: CalculationTimeConstrainsCreate):
simulation_result = await get_calculation_result_by_day(db_session=db_session, calculation_id=calculation_id, simulation_day=calculation_simuation_in.intervalDays)
@router.post(
"/{calculation_id}/simulation",
response_model=StandardResponse[CalculationResultsRead],
)
async def get_simulation_result(
db_session: DbSession,
calculation_id,
calculation_simuation_in: CalculationTimeConstrainsCreate,
):
simulation_result = await get_calculation_result_by_day(
db_session=db_session,
calculation_id=calculation_id,
simulation_day=calculation_simuation_in.intervalDays,
)
return StandardResponse(
data=simulation_result, message="Data retrieved successfully"
)
return StandardResponse(data=simulation_result, message="Data retrieved successfully")
@router.put("/{calculation_id}", response_model=StandardResponse[List[str]])
async def update_selected_equipment(db_session: DbSession, calculation_id, calculation_time_constrains_in: List[CalculationSelectedEquipmentUpdate]):
results = await bulk_update_equipment(db=db_session, selected_equipments=calculation_time_constrains_in, calculation_data_id=calculation_id)
async def update_selected_equipment(
db_session: DbSession,
calculation_id,
calculation_time_constrains_in: List[CalculationSelectedEquipmentUpdate],
):
results = await bulk_update_equipment(
db=db_session,
selected_equipments=calculation_time_constrains_in,
calculation_data_id=calculation_id,
)
return StandardResponse(
data=results,

@ -1,47 +1,41 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from uuid import UUID
from pydantic import Field
from src.models import DefultBase
from dataclasses import dataclass
from src.models import DefultBase
from src.scope_equipment.schema import MasterEquipmentBase
class CalculationTimeConstrainsBase(DefultBase):
pass
class ReferenceLinkBase(DefultBase):
reference_id: str = Field(..., description="Reference ID")
overhaul_reference_type: str = Field(...,
description="Overhaul reference type")
overhaul_reference_type: str = Field(..., description="Overhaul reference type")
class CalculationTimeConstrainsParametersRetrive(CalculationTimeConstrainsBase):
# type: ignore
costPerFailure: Union[dict,
float] = Field(..., description="Cost per failure")
costPerFailure: Union[dict, float] = Field(..., description="Cost per failure")
availableScopes: List[str] = Field(..., description="Available scopes")
recommendedScope: str = Field(..., description="Recommended scope")
# historicalData: Dict[str, Any] = Field(..., description="Historical data")
class CalculationTimeConstrainsParametersRead(CalculationTimeConstrainsBase):
costPerFailure: Union[dict,
float] = Field(..., description="Cost per failure")
costPerFailure: Union[dict, float] = Field(..., description="Cost per failure")
overhaulCost: Optional[float] = Field(None, description="Overhaul cost")
reference: Optional[List[ReferenceLinkBase]] = Field(
None, description="Reference")
reference: Optional[List[ReferenceLinkBase]] = Field(None, description="Reference")
class CalculationTimeConstrainsParametersCreate(CalculationTimeConstrainsBase):
overhaulCost: Optional[float] = Field(0, description="Overhaul cost")
ohSessionId: Optional[UUID] = Field(None, description="Scope OH")
costPerFailure: Optional[float] = Field(0,
description="Cost per failure")
costPerFailure: Optional[float] = Field(0, description="Cost per failure")
# class CalculationTimeConstrainsCreate(CalculationTimeConstrainsBase):
@ -50,12 +44,14 @@ class CalculationTimeConstrainsParametersCreate(CalculationTimeConstrainsBase):
# costPerFailure: float = Field(..., description="Cost per failure")
# metadata: Dict[str, Any] = Field(..., description="Metadata")
class CalculationResultsRead(CalculationTimeConstrainsBase):
day: int
corrective_cost: float
overhaul_cost: float
num_failures: int
class OptimumResult(CalculationTimeConstrainsBase):
overhaul_cost: float
corrective_cost: float
@ -74,6 +70,7 @@ class EquipmentResult(CalculationTimeConstrainsBase):
is_included: bool
master_equipment: Optional[MasterEquipmentBase] = Field(None)
class CalculationTimeConstrainsRead(CalculationTimeConstrainsBase):
id: UUID
reference: UUID

@ -1,16 +1,11 @@
from typing import List
from typing import Optional
from typing import Tuple
import datetime
from typing import List, Optional, Tuple
from uuid import UUID
import numpy as np
from fastapi import HTTPException
from fastapi import status
from sqlalchemy import and_
from sqlalchemy import case
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy import update
import requests
from fastapi import HTTPException, status
from sqlalchemy import and_, case, func, select, update
from sqlalchemy.orm import joinedload
from src.database.core import DbSession
@ -18,20 +13,17 @@ from src.overhaul_activity.service import get_all_by_session_id
from src.overhaul_scope.service import get as get_scope
from src.workorder.model import MasterWorkOrder
from .model import CalculationData
from .model import CalculationEquipmentResult
from .model import CalculationResult
from .schema import CalculationResultsRead
from .schema import CalculationTimeConstrainsParametersCreate
from .schema import CalculationTimeConstrainsRead
from .schema import OptimumResult
from .schema import CalculationSelectedEquipmentUpdate
import requests
import datetime
from .model import (CalculationData, CalculationEquipmentResult,
CalculationResult)
from .schema import (CalculationResultsRead,
CalculationSelectedEquipmentUpdate,
CalculationTimeConstrainsParametersCreate,
CalculationTimeConstrainsRead, OptimumResult)
def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipments:int ,decay_base: float = 1.01) -> np.ndarray:
def get_overhaul_cost_by_time_chart(
overhaul_cost: float, days: int, numEquipments: int, decay_base: float = 1.01
) -> np.ndarray:
if overhaul_cost < 0:
raise ValueError("Overhaul cost cannot be negative")
if days <= 0:
@ -40,7 +32,7 @@ def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipment
exponents = np.arange(0, days)
cost_per_equipment = overhaul_cost / numEquipments
# Using a slower decay base to spread the budget depletion over more days
results = cost_per_equipment / (decay_base ** exponents)
results = cost_per_equipment / (decay_base**exponents)
results = np.where(np.isfinite(results), results, 0)
return results
@ -61,7 +53,10 @@ def get_overhaul_cost_by_time_chart(overhaul_cost: float, days: int,numEquipment
# results = np.where(np.isfinite(results), results, 0)
# return results
async def get_corrective_cost_time_chart(material_cost: float, service_cost: float, location_tag: str, token) -> Tuple[np.ndarray, np.ndarray]:
async def get_corrective_cost_time_chart(
material_cost: float, service_cost: float, location_tag: str, token
) -> Tuple[np.ndarray, np.ndarray]:
"""
Fetch failure data from API and calculate corrective costs, ensuring 365 days of data.
@ -74,14 +69,14 @@ async def get_corrective_cost_time_chart(material_cost: float, service_cost: flo
Returns:
Tuple of (corrective_costs, daily_failure_rate)
"""
url = f'http://192.168.1.82:8000/reliability/main/number-of-failures/{location_tag}/2024-01-01/2024-12-31'
url = f"http://192.168.1.82:8000/reliability/main/number-of-failures/{location_tag}/2024-01-01/2024-12-31"
try:
response = requests.get(
url,
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
},
)
data = response.json()
@ -92,8 +87,8 @@ async def get_corrective_cost_time_chart(material_cost: float, service_cost: flo
# Create a dictionary of existing data
data_dict = {
datetime.datetime.strptime(item['date'], '%d %b %Y'): item['num_fail']
for item in data['data']
datetime.datetime.strptime(item["date"], "%d %b %Y"): item["num_fail"]
for item in data["data"]
}
# Fill in missing dates with nearest available value
@ -121,6 +116,7 @@ async def get_corrective_cost_time_chart(material_cost: float, service_cost: flo
print(f"Error fetching or processing data: {str(e)}")
raise
# def get_corrective_cost_time_chart(material_cost: float, service_cost: float, days: int, numEquipments: int) -> Tuple[np.ndarray, np.ndarray]:
# day_points = np.arange(0, days)
@ -147,12 +143,18 @@ async def get_corrective_cost_time_chart(material_cost: float, service_cost: flo
# return corrective_costs, daily_failure_rate
async def create_param_and_data(*, db_session: DbSession, calculation_param_in: CalculationTimeConstrainsParametersCreate, created_by: str, parameter_id: Optional[UUID] = None):
async def create_param_and_data(
*,
db_session: DbSession,
calculation_param_in: CalculationTimeConstrainsParametersCreate,
created_by: str,
parameter_id: Optional[UUID] = None,
):
"""Creates a new document."""
if calculation_param_in.ohSessionId is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="overhaul_session_id is required"
detail="overhaul_session_id is required",
)
calculationData = await CalculationData.create_with_param(
@ -161,36 +163,39 @@ async def create_param_and_data(*, db_session: DbSession, calculation_param_in:
avg_failure_cost=calculation_param_in.costPerFailure,
overhaul_cost=calculation_param_in.overhaulCost,
created_by=created_by,
params_id=parameter_id
params_id=parameter_id,
)
return calculationData
async def get_calculation_result(db_session: DbSession, calculation_id: str):
days=365
scope_calculation = await get_calculation_data_by_id(db_session=db_session, calculation_id=calculation_id)
days = 365
scope_calculation = await get_calculation_data_by_id(
db_session=db_session, calculation_id=calculation_id
)
if not scope_calculation:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="A data with this id does not exist.",
)
scope_overhaul = await get_scope(db_session=db_session, overhaul_session_id=scope_calculation.overhaul_session_id)
scope_overhaul = await get_scope(
db_session=db_session, overhaul_session_id=scope_calculation.overhaul_session_id
)
if not scope_overhaul:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="A data with this id does not exist.",
)
calculation_results = []
for i in range(days):
result = {
"overhaul_cost": 0,
"corrective_cost": 0,
"num_failures": 0,
"day": i + 1
"day": i + 1,
}
for eq in scope_calculation.equipment_results:
@ -200,10 +205,8 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str):
result["overhaul_cost"] += float(eq.overhaul_costs[i])
result["num_failures"] += int(eq.daily_failures[i])
calculation_results.append(CalculationResultsRead(**result))
# Check if calculation already exist
return CalculationTimeConstrainsRead(
id=scope_calculation.id,
@ -211,18 +214,22 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str):
scope=scope_overhaul.type,
results=calculation_results,
optimum_oh=scope_calculation.optimum_oh_day,
equipment_results=scope_calculation.equipment_results
equipment_results=scope_calculation.equipment_results,
)
async def get_calculation_data_by_id(db_session: DbSession, calculation_id) -> CalculationData:
stmt = select(CalculationData).filter(
CalculationData.id == calculation_id
).options(
joinedload(CalculationData.equipment_results), joinedload(CalculationData.parameter)
async def get_calculation_data_by_id(
db_session: DbSession, calculation_id
) -> CalculationData:
stmt = (
select(CalculationData)
.filter(CalculationData.id == calculation_id)
.options(
joinedload(CalculationData.equipment_results),
joinedload(CalculationData.parameter),
)
)
result = await db_session.execute(stmt)
return result.unique().scalar()
@ -287,20 +294,21 @@ async def get_calculation_data_by_id(db_session: DbSession, calculation_id) -> C
async def create_calculation_result_service(
db_session: DbSession,
calculation: CalculationData,
token: str
db_session: DbSession, calculation: CalculationData, token: str
) -> CalculationTimeConstrainsRead:
days = 365 # Changed to 365 days as per requirement
# Get all equipment for this calculation session
equipments = await get_all_by_session_id(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
scope = await get_scope(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id)
calculation_data = await get_calculation_data_by_id(db_session=db_session, calculation_id=calculation.id)
equipments = await get_all_by_session_id(
db_session=db_session, overhaul_session_id=calculation.overhaul_session_id
)
scope = await get_scope(
db_session=db_session, overhaul_session_id=calculation.overhaul_session_id
)
calculation_data = await get_calculation_data_by_id(
db_session=db_session, calculation_id=calculation.id
)
# Store results for each equipment
equipment_results: List[CalculationEquipmentResult] = []
@ -313,31 +321,33 @@ async def create_calculation_result_service(
material_cost=eq.material_cost,
service_cost=eq.service_cost,
token=token,
location_tag=eq.equipment.location_tag
location_tag=eq.equipment.location_tag,
)
overhaul_cost_points = get_overhaul_cost_by_time_chart(
calculation_data.parameter.overhaul_cost,
days=len(corrective_costs),
numEquipments=len(equipments)
)
numEquipments=len(equipments),
)
# Calculate individual equipment optimum points
equipment_total_cost = corrective_costs + overhaul_cost_points
equipment_optimum_index = np.argmin(equipment_total_cost)
equipment_failure_sum = sum(daily_failures[:equipment_optimum_index])
equipment_results.append(CalculationEquipmentResult(
corrective_costs=corrective_costs.tolist(),
overhaul_costs=overhaul_cost_points.tolist(),
daily_failures=daily_failures.tolist(),
assetnum=eq.assetnum,
material_cost=eq.material_cost,
service_cost=eq.service_cost,
optimum_day=int(equipment_optimum_index + 1),
calculation_data_id=calculation.id,
master_equipment=eq.equipment
))
equipment_results.append(
CalculationEquipmentResult(
corrective_costs=corrective_costs.tolist(),
overhaul_costs=overhaul_cost_points.tolist(),
daily_failures=daily_failures.tolist(),
assetnum=eq.assetnum,
material_cost=eq.material_cost,
service_cost=eq.service_cost,
optimum_day=int(equipment_optimum_index + 1),
calculation_data_id=calculation.id,
master_equipment=eq.equipment,
)
)
# Add to totals
total_corrective_costs += corrective_costs
@ -345,7 +355,6 @@ async def create_calculation_result_service(
db_session.add_all(equipment_results)
# Calculate optimum points using total costs
total_cost = total_corrective_costs + overhaul_cost_points
optimum_oh_index = np.argmin(total_cost)
@ -355,7 +364,7 @@ async def create_calculation_result_service(
overhaul_cost=float(overhaul_cost_points[optimum_oh_index]),
corrective_cost=float(total_corrective_costs[optimum_oh_index]),
num_failures=int(numbers_of_failure),
days=int(optimum_oh_index + 1)
days=int(optimum_oh_index + 1),
)
# # Create calculation results for database
@ -376,7 +385,6 @@ async def create_calculation_result_service(
await db_session.commit()
# Return results including individual equipment data
return CalculationTimeConstrainsRead(
id=calculation.id,
@ -384,26 +392,34 @@ async def create_calculation_result_service(
scope=scope.type,
results=[],
optimum_oh=optimum,
equipment_results=equipment_results
equipment_results=equipment_results,
)
async def get_calculation_by_reference_and_parameter(*, db_session: DbSession, calculation_reference_id, parameter_id):
stmt = select(CalculationData).filter(and_(
CalculationData.reference_id == calculation_reference_id,
CalculationData.parameter_id == parameter_id,
))
async def get_calculation_by_reference_and_parameter(
*, db_session: DbSession, calculation_reference_id, parameter_id
):
stmt = select(CalculationData).filter(
and_(
CalculationData.reference_id == calculation_reference_id,
CalculationData.parameter_id == parameter_id,
)
)
result = await db_session.execute(stmt)
return result.scalar()
async def get_calculation_result_by_day(*, db_session: DbSession, calculation_id, simulation_day):
stmt = select(CalculationResult).filter(and_(
CalculationResult.day == simulation_day,
CalculationResult.calculation_data_id == calculation_id
))
async def get_calculation_result_by_day(
*, db_session: DbSession, calculation_id, simulation_day
):
stmt = select(CalculationResult).filter(
and_(
CalculationResult.day == simulation_day,
CalculationResult.calculation_data_id == calculation_id,
)
)
result = await db_session.execute(stmt)
@ -411,21 +427,22 @@ async def get_calculation_result_by_day(*, db_session: DbSession, calculation_id
async def get_avg_cost_by_asset(*, db_session: DbSession, assetnum: str):
stmt = (
select(func.avg(MasterWorkOrder.total_cost_max).label('average_cost'))
.where(MasterWorkOrder.assetnum == assetnum)
stmt = select(func.avg(MasterWorkOrder.total_cost_max).label("average_cost")).where(
MasterWorkOrder.assetnum == assetnum
)
result = await db_session.execute(stmt)
return result.scalar_one_or_none()
async def bulk_update_equipment(*, db: DbSession, selected_equipments: List[CalculationSelectedEquipmentUpdate], calculation_data_id: UUID):
async def bulk_update_equipment(
*,
db: DbSession,
selected_equipments: List[CalculationSelectedEquipmentUpdate],
calculation_data_id: UUID,
):
# Create a dictionary mapping assetnum to is_included status
case_mappings = {
asset.assetnum: asset.is_included
for asset in selected_equipments
}
case_mappings = {asset.assetnum: asset.is_included for asset in selected_equipments}
# Get all assetnums that need to be updated
assetnums = list(case_mappings.keys())
@ -441,9 +458,13 @@ async def bulk_update_equipment(*, db: DbSession, selected_equipments: List[Calc
update(CalculationEquipmentResult)
.where(CalculationEquipmentResult.calculation_data_id == calculation_data_id)
.where(CalculationEquipmentResult.assetnum.in_(assetnums))
.values({
"is_included": case(*when_clauses) # Unpack the when clauses as separate arguments
})
.values(
{
"is_included": case(
*when_clauses
) # Unpack the when clauses as separate arguments
}
)
)
await db.execute(stmt)

@ -1,14 +1,13 @@
import base64
import logging
import os
import base64
from urllib import parse
from typing import List
from pydantic import BaseModel
from urllib import parse
from pydantic import BaseModel
from starlette.config import Config
from starlette.datastructures import CommaSeparatedStrings
log = logging.getLogger(__name__)
@ -60,10 +59,10 @@ _QUOTED_DATABASE_PASSWORD = parse.quote(str(_DATABASE_CREDENTIAL_PASSWORD))
DATABASE_NAME = config("DATABASE_NAME", default="digital_twin")
DATABASE_PORT = config("DATABASE_PORT", default="5432")
DATABASE_ENGINE_POOL_SIZE = config(
"DATABASE_ENGINE_POOL_SIZE", cast=int, default=20)
DATABASE_ENGINE_POOL_SIZE = config("DATABASE_ENGINE_POOL_SIZE", cast=int, default=20)
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
DATABASE_ENGINE_POOL_PING = config("DATABASE_ENGINE_POOL_PING", default=False)
@ -74,5 +73,4 @@ TIMEZONE = "Asia/Jakarta"
MAXIMO_BASE_URL = config("MAXIMO_BASE_URL", default="http://example.com")
MAXIMO_API_KEY = config("MAXIMO_API_KEY", default="keys")
AUTH_SERVICE_API = config(
"AUTH_SERVICE_API", default="http://192.168.1.82:8000/auth")
AUTH_SERVICE_API = config("AUTH_SERVICE_API", default="http://192.168.1.82:8000/auth")

@ -1,27 +1,23 @@
# src/database.py
from starlette.requests import Request
from sqlalchemy_utils import get_mapper
from sqlalchemy.sql.expression import true
from sqlalchemy.orm import object_session, sessionmaker, Session
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import create_engine, inspect
from pydantic import BaseModel
from fastapi import Depends
from typing import Annotated, Any
from contextlib import contextmanager
import re
import functools
from typing import AsyncGenerator
import re
from contextlib import contextmanager
from typing import Annotated, Any, AsyncGenerator
from fastapi import Depends
from pydantic import BaseModel
from sqlalchemy import create_engine, inspect
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import (DeclarativeBase, Session, object_session,
sessionmaker)
from sqlalchemy.sql.expression import true
from sqlalchemy_utils import get_mapper
from starlette.requests import Request
from src.config import SQLALCHEMY_DATABASE_URI
engine = create_async_engine(
SQLALCHEMY_DATABASE_URI,
echo=False,
future=True
)
engine = create_async_engine(SQLALCHEMY_DATABASE_URI, echo=False, future=True)
async_session = sessionmaker(
engine,
@ -68,9 +64,8 @@ class CustomBase:
for key in self.__repr_attrs__:
if not hasattr(self, key):
raise KeyError(
"{} has incorrect attribute '{}' in " "__repr__attrs__".format(
self.__class__, key
)
"{} has incorrect attribute '{}' in "
"__repr__attrs__".format(self.__class__, key)
)
value = getattr(self, key)
wrap_in_quote = isinstance(value, str)

@ -1,15 +1,13 @@
import logging
from typing import Annotated, List
from sqlalchemy import desc, func, or_, Select
from sqlalchemy_filters import apply_pagination
from fastapi import Depends, Query
from pydantic.types import Json, constr
from sqlalchemy import Select, desc, func, or_
from sqlalchemy.exc import ProgrammingError
from .core import DbSession
from sqlalchemy_filters import apply_pagination
from fastapi import Query, Depends
from pydantic.types import Json, constr
from .core import DbSession
log = logging.getLogger(__name__)
@ -27,7 +25,7 @@ def common_parameters(
sort_by: List[str] = Query([], alias="sortBy[]"),
descending: List[bool] = Query([], alias="descending[]"),
exclude: List[str] = Query([], alias="exclude[]"),
all: int = Query(0)
all: int = Query(0),
# role: QueryStr = Depends(get_current_role),
):
return {
@ -39,14 +37,13 @@ def common_parameters(
"sort_by": sort_by,
"descending": descending,
"current_user": current_user,
"all": bool(all)
"all": bool(all),
# "role": role,
}
CommonParameters = Annotated[
dict[str, int | str | DbSession | QueryStr |
Json | List[str] | List[bool]] | bool,
dict[str, int | str | DbSession | QueryStr | Json | List[str] | List[bool]] | bool,
Depends(common_parameters),
]
@ -75,8 +72,7 @@ def search(*, query_str: str, query: Query, model, sort=False):
query = query.filter(or_(*search))
if sort:
query = query.order_by(
desc(func.ts_rank_cd(vector, func.tsq_parse(query_str))))
query = query.order_by(desc(func.ts_rank_cd(vector, func.tsq_parse(query_str))))
return query.params(term=query_str)
@ -92,7 +88,7 @@ async def search_filter_sort_paginate(
descending: List[bool] = None,
current_user: str = None,
exclude: List[str] = None,
all: bool = False
all: bool = False,
):
"""Common functionality for searching, filtering, sorting, and pagination."""
# try:
@ -104,8 +100,7 @@ async def search_filter_sort_paginate(
if query_str:
sort = False if sort_by else True
query = search(query_str=query_str, query=query,
model=model, sort=sort)
query = search(query_str=query_str, query=query, model=model, sort=sort)
# Get total count
count_query = Select(func.count()).select_from(query.subquery())
@ -123,11 +118,7 @@ async def search_filter_sort_paginate(
"totalPages": 1,
}
query = (
query
.offset((page - 1) * items_per_page)
.limit(items_per_page)
)
query = query.offset((page - 1) * items_per_page).limit(items_per_page)
result = await db_session.execute(query)
items = result.scalars().all()

@ -21,4 +21,4 @@ class OptimumOHEnum(StrEnum):
class ResponseStatus(OptimumOHEnum):
SUCCESS = "success"
ERROR = "error"
ERROR = "error"

@ -1,17 +1,19 @@
# Define base error model
import logging
from typing import Any, Dict, List, Optional
from asyncpg.exceptions import DataError as AsyncPGDataError
from asyncpg.exceptions import PostgresError
from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from src.enums import ResponseStatus
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from sqlalchemy.exc import (DataError, DBAPIError, IntegrityError,
SQLAlchemyError)
from sqlalchemy.exc import SQLAlchemyError, IntegrityError, DataError, DBAPIError
from asyncpg.exceptions import DataError as AsyncPGDataError, PostgresError
from src.enums import ResponseStatus
class ErrorDetail(BaseModel):
@ -27,6 +29,7 @@ class ErrorResponse(BaseModel):
status: ResponseStatus = ResponseStatus.ERROR
errors: Optional[List[ErrorDetail]] = None
# Custom exception handler setup
@ -64,7 +67,7 @@ def handle_sqlalchemy_error(error: SQLAlchemyError):
"""
Handle SQLAlchemy errors and return user-friendly error messages.
"""
original_error = getattr(error, 'orig', None)
original_error = getattr(error, "orig", None)
print(original_error)
if isinstance(error, IntegrityError):
@ -113,12 +116,8 @@ def handle_exception(request: Request, exc: Exception):
"data": None,
"message": str(exc.detail),
"status": ResponseStatus.ERROR,
"errors": [
ErrorDetail(
message=str(exc.detail)
).model_dump()
]
}
"errors": [ErrorDetail(message=str(exc.detail)).model_dump()],
},
)
if isinstance(exc, SQLAlchemyError):
@ -134,12 +133,8 @@ def handle_exception(request: Request, exc: Exception):
"data": None,
"message": error_message,
"status": ResponseStatus.ERROR,
"errors": [
ErrorDetail(
message=error_message
).model_dump()
]
}
"errors": [ErrorDetail(message=error_message).model_dump()],
},
)
# Log unexpected errors
@ -155,9 +150,7 @@ def handle_exception(request: Request, exc: Exception):
"message": str(exc),
"status": ResponseStatus.ERROR,
"errors": [
ErrorDetail(
message="An unexpected error occurred."
).model_dump()
]
}
ErrorDetail(message="An unexpected error occurred.").model_dump()
],
},
)

@ -1,12 +1,10 @@
from sqlalchemy import UUID, Column, Float, ForeignKey, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy import UUID, Column, Float, Integer, String, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
from src.workorder.model import MasterWorkOrder
from sqlalchemy.ext.hybrid import hybrid_property
class MasterActivitytask(Base, DefaultMixin):
@ -14,8 +12,11 @@ class MasterActivitytask(Base, DefaultMixin):
description = Column(String, nullable=False)
oh_type = Column(String, nullable=False)
job_id = Column(UUID(as_uuid=True), ForeignKey(
"oh_ms_job.id", ondelete="cascade"), nullable=False)
job_id = Column(
UUID(as_uuid=True),
ForeignKey("oh_ms_job.id", ondelete="cascade"),
nullable=False,
)
class MasterActivity(Base, DefaultMixin):

@ -1,12 +1,12 @@
from fastapi import APIRouter, HTTPException, Query, status
from .service import get_all, create, get, update, delete
from .schema import ActivityMaster, ActivityMasterCreate, ActivityMasterPagination
from src.database.service import (CommonParameters, DbSession,
search_filter_sort_paginate)
from src.models import StandardResponse
from src.database.service import CommonParameters, search_filter_sort_paginate, DbSession
from .schema import (ActivityMaster, ActivityMasterCreate,
ActivityMasterPagination)
from .service import create, delete, get, get_all, update
router = APIRouter()
@ -31,7 +31,9 @@ async def create_activity(db_session: DbSession, activity_in: ActivityMasterCrea
return StandardResponse(data=activity, message="Data created successfully")
@router.get("/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster])
@router.get(
"/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster]
)
async def get_activity(db_session: DbSession, activity_id: str):
activity = await get(db_session=db_session, activity_id=activity_id)
if not activity:
@ -43,8 +45,12 @@ async def get_activity(db_session: DbSession, activity_id: str):
return StandardResponse(data=activity, message="Data retrieved successfully")
@router.put("/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster])
async def update_scope(db_session: DbSession, activity_in: ActivityMasterCreate, activity_id):
@router.put(
"/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster]
)
async def update_scope(
db_session: DbSession, activity_in: ActivityMasterCreate, activity_id
):
activity = await get(db_session=db_session, activity_id=activity_id)
if not activity:
@ -53,10 +59,17 @@ async def update_scope(db_session: DbSession, activity_in: ActivityMasterCreate,
detail="A data with this id does not exist.",
)
return StandardResponse(data=await update(db_session=db_session, activity=activity, activity_in=activity_in), message="Data updated successfully")
return StandardResponse(
data=await update(
db_session=db_session, activity=activity, activity_in=activity_in
),
message="Data updated successfully",
)
@router.delete("/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster])
@router.delete(
"/{scope_equipment_activity_id}", response_model=StandardResponse[ActivityMaster]
)
async def delete_scope(db_session: DbSession, activity_id: str):
activity = await get(db_session=db_session, activity_id=activity_id)

@ -1,9 +1,9 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
from src.models import DefultBase, Pagination
@ -18,10 +18,12 @@ class ActivityMasterDetail(DefultBase):
class ActivityMasterCreate(ActivityMaster):
description: str
class ActivityMasterTasks(DefultBase):
description: str
oh_type: str
class ActivityMasterRead(ActivityMaster):
id: UUID
workscope: str

@ -1,15 +1,14 @@
from sqlalchemy import Select, Delete
from sqlalchemy.orm import joinedload, selectinload
from typing import Optional
from .model import MasterActivity
from .schema import ActivityMaster, ActivityMasterCreate
from sqlalchemy import Delete, Select
from sqlalchemy.orm import joinedload, selectinload
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.auth.service import CurrentUser
from .model import MasterActivity
from .schema import ActivityMaster, ActivityMasterCreate
async def get(*, db_session: DbSession, activity_id: str) -> Optional[ActivityMaster]:
@ -27,14 +26,18 @@ async def get_all(common: CommonParameters):
async def create(*, db_session: DbSession, activty_in: ActivityMasterCreate):
activity = MasterActivity(
**activty_in.model_dump())
activity = MasterActivity(**activty_in.model_dump())
db_session.add(activity)
await db_session.commit()
return activity
async def update(*, db_session: DbSession, activity: MasterActivity, activity_in: ActivityMasterCreate):
async def update(
*,
db_session: DbSession,
activity: MasterActivity,
activity_in: ActivityMasterCreate
):
"""Updates a document."""
data = activity_in.model_dump()

@ -3,7 +3,6 @@ import logging
from src.config import LOG_LEVEL
from src.enums import OptimumOHEnum
LOG_FORMAT_DEBUG = "%(levelname)s:%(message)s:%(pathname)s:%(funcName)s:%(lineno)d"

@ -1,37 +1,33 @@
import time
import logging
import time
from contextvars import ContextVar
from os import path
from typing import Final, Optional
from uuid import uuid1
from typing import Optional, Final
from contextvars import ContextVar
from fastapi import FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import ValidationError
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from sqlalchemy import inspect
from sqlalchemy.orm import scoped_session
from sqlalchemy.ext.asyncio import async_scoped_session
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from sqlalchemy.orm import scoped_session
from starlette.middleware.base import (BaseHTTPMiddleware,
RequestResponseEndpoint)
from starlette.middleware.gzip import GZipMiddleware
from starlette.requests import Request
from starlette.responses import FileResponse, Response, StreamingResponse
from starlette.routing import compile_path
from starlette.middleware.gzip import GZipMiddleware
from fastapi.middleware.cors import CORSMiddleware
from starlette.responses import Response, StreamingResponse, FileResponse
from starlette.staticfiles import StaticFiles
import logging
from src.api import api_router
from src.database.core import async_session, engine
from src.enums import ResponseStatus
from src.exceptions import handle_exception
from src.logging import configure_logging
from src.rate_limiter import limiter
from src.api import api_router
from src.database.core import engine, async_session
from src.exceptions import handle_exception
log = logging.getLogger(__name__)
@ -42,9 +38,13 @@ configure_logging()
exception_handlers = {Exception: handle_exception}
# we create the ASGI for the app
app = FastAPI(exception_handlers=exception_handlers, openapi_url="", title="LCCA API",
description="Welcome to LCCA's API documentation!",
version="0.1.0")
app = FastAPI(
exception_handlers=exception_handlers,
openapi_url="",
title="LCCA API",
description="Welcome to LCCA's API documentation!",
version="0.1.0",
)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(GZipMiddleware, minimum_size=2000)
@ -53,7 +53,8 @@ app.add_middleware(GZipMiddleware, minimum_size=2000)
REQUEST_ID_CTX_KEY: Final[str] = "request_id"
_request_id_ctx_var: ContextVar[Optional[str]] = ContextVar(
REQUEST_ID_CTX_KEY, default=None)
REQUEST_ID_CTX_KEY, default=None
)
def get_request_id() -> Optional[str]:
@ -84,9 +85,12 @@ async def db_session_middleware(request: Request, call_next):
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["Strict-Transport-Security"] = "max-age=31536000 ; includeSubDomains"
response.headers["Strict-Transport-Security"] = (
"max-age=31536000 ; includeSubDomains"
)
return response
# class MetricsMiddleware(BaseHTTPMiddleware):
# async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
# method = request.method

@ -1,10 +1,10 @@
from datetime import datetime, timedelta
from typing import Any, Dict
from fastapi import HTTPException
import httpx
from fastapi import HTTPException
from starlette.config import Config
from src.config import MAXIMO_API_KEY, MAXIMO_BASE_URL
@ -24,7 +24,7 @@ class MaximoDataMapper:
Example: might be data['startDate'] or data['SCHEDSTART'] etc.
"""
# This is a placeholder - update with actual MAXIMO field name
start_date_str = self.data.get('scheduleStart')
start_date_str = self.data.get("scheduleStart")
if not start_date_str:
raise ValueError("Start date not found in MAXIMO data")
return datetime.fromisoformat(start_date_str)
@ -35,7 +35,7 @@ class MaximoDataMapper:
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')
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)
@ -46,7 +46,7 @@ class MaximoDataMapper:
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')
maximo_id = self.data.get("workOrderId")
if not maximo_id:
raise ValueError("MAXIMO ID not found in response")
return str(maximo_id)
@ -57,7 +57,7 @@ class MaximoDataMapper:
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()
status = self.data.get("status", "").upper()
return status
def get_total_cost(self) -> float:
@ -66,11 +66,11 @@ class MaximoDataMapper:
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)
cost = self.data.get("totalCost", 0)
return float(cost)
def get_scope_name(self) -> str:
scope_name = self.data.get('location', "A")
scope_name = self.data.get("location", "A")
return scope_name
@ -86,10 +86,8 @@ class MaximoService:
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
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(),
@ -104,19 +102,19 @@ class MaximoService:
{
"assetnum": "ASSET001",
"description": "Gas Turbine",
"status": "OPERATING"
"status": "OPERATING",
},
{
"assetnum": "ASSET002",
"description": "Steam Turbine",
"status": "OPERATING"
}
"status": "OPERATING",
},
],
"workType": "OH", # OH for Overhaul
"createdBy": "MAXADMIN",
"createdDate": (current_date - timedelta(days=10)).isoformat(),
"lastModifiedBy": "MAXADMIN",
"lastModifiedDate": current_date.isoformat()
"lastModifiedDate": current_date.isoformat(),
}
async with httpx.AsyncClient() as client:
@ -131,21 +129,20 @@ class MaximoService:
params={
# Update these parameters based on actual MAXIMO API
"orderBy": "-scheduleEnd", # Example parameter
"limit": 1
}
"limit": 1,
},
)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail=f"MAXIMO API error: {response.text}"
detail=f"MAXIMO API error: {response.text}",
)
data = response.json()
if not data:
raise HTTPException(
status_code=404,
detail="No recent overhaul found"
status_code=404, detail="No recent overhaul found"
)
# TODO: Update this based on actual MAXIMO response structure
@ -153,6 +150,5 @@ class MaximoService:
except httpx.RequestError as e:
raise HTTPException(
status_code=503,
detail=f"Failed to connect to MAXIMO: {str(e)}"
status_code=503, detail=f"Failed to connect to MAXIMO: {str(e)}"
)

@ -1,16 +1,18 @@
# src/common/models.py
import uuid
from datetime import datetime
from typing import Generic, Optional, TypeVar
import uuid
import pytz
from pydantic import BaseModel, Field, SecretStr
from sqlalchemy import Column, DateTime, String, func, event
from sqlalchemy import Column, DateTime, String, event, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from src.config import TIMEZONE
import pytz
from src.auth.service import CurrentUser
from src.config import TIMEZONE
from src.enums import ResponseStatus
# SQLAlchemy Mixins
@ -18,10 +20,12 @@ class TimeStampMixin(object):
"""Timestamping mixin"""
created_at = Column(
DateTime(timezone=True), default=datetime.now(pytz.timezone(TIMEZONE)))
DateTime(timezone=True), default=datetime.now(pytz.timezone(TIMEZONE))
)
created_at._creation_order = 9998
updated_at = Column(
DateTime(timezone=True), default=datetime.now(pytz.timezone(TIMEZONE)))
DateTime(timezone=True), default=datetime.now(pytz.timezone(TIMEZONE))
)
updated_at._creation_order = 9998
@staticmethod
@ -35,17 +39,25 @@ class TimeStampMixin(object):
class UUIDMixin:
"""UUID mixin"""
id = Column(UUID(as_uuid=True), primary_key=True,
default=uuid.uuid4, unique=True, nullable=False)
id = Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
class DefaultMixin(TimeStampMixin, UUIDMixin):
"""Default mixin"""
pass
class IdentityMixin:
"""Identity mixin"""
created_by = Column(String(100), nullable=True)
updated_by = Column(String(100), nullable=True)
@ -77,7 +89,7 @@ class PrimaryKeyModel(BaseModel):
# Define data type variable for generic response
T = TypeVar('T')
T = TypeVar("T")
class StandardResponse(BaseModel, Generic[T]):

@ -1,14 +1,18 @@
from typing import List
from fastapi import APIRouter, HTTPException, status
from src.overhaul.service import get_overhaul_critical_parts, get_overhaul_overview, get_overhaul_schedules, get_overhaul_system_components
from src.database.core import DbSession
from src.models import StandardResponse
from src.overhaul.service import (get_overhaul_critical_parts,
get_overhaul_overview,
get_overhaul_schedules,
get_overhaul_system_components)
from src.overhaul_scope.schema import ScopeRead
from .schema import OverhaulRead, OverhaulCriticalParts, OverhaulSystemComponents
from .schema import (OverhaulCriticalParts, OverhaulRead,
OverhaulSystemComponents)
from src.models import StandardResponse
from src.database.core import DbSession
router = APIRouter()
@ -51,7 +55,9 @@ async def get_critical_parts():
)
@router.get("/system-components", response_model=StandardResponse[OverhaulSystemComponents])
@router.get(
"/system-components", response_model=StandardResponse[OverhaulSystemComponents]
)
async def get_system_components():
"""Get all overhaul system components."""
systemComponents = get_overhaul_system_components()

@ -1,9 +1,9 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
from src.models import DefultBase, Pagination
from src.overhaul_scope.schema import ScopeRead
@ -17,13 +17,13 @@ class OverhaulCriticalParts(OverhaulBase):
class OverhaulSchedules(OverhaulBase):
schedules: List[Dict[str, Any]
] = Field(..., description="List of schedules")
schedules: List[Dict[str, Any]] = Field(..., description="List of schedules")
class OverhaulSystemComponents(OverhaulBase):
systemComponents: Dict[str,
Any] = Field(..., description="List of system components")
systemComponents: Dict[str, Any] = Field(
..., description="List of system components"
)
class OverhaulRead(OverhaulBase):

@ -1,12 +1,12 @@
from sqlalchemy import Select, Delete
from typing import Optional
from src.database.core import DbSession
from sqlalchemy import Delete, Select
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.overhaul_scope.model import OverhaulScope
from src.overhaul_scope.service import get_all as get_all_session, get_overview_overhaul
from src.overhaul_scope.service import get_all as get_all_session
from src.overhaul_scope.service import get_overview_overhaul
async def get_overhaul_overview(db_session: DbSession):
@ -23,16 +23,16 @@ def get_overhaul_critical_parts():
"Boiler reheater system",
"Drum Level (Right) Root Valve A",
"BCP A Discharge Valve",
"BFPT A EXH Press HI Root VLV"
"BFPT A EXH Press HI Root VLV",
]
async def get_overhaul_schedules(*, db_session: DbSession):
"""Get all overhaul schedules."""
query = Select(OverhaulScope)
results = await db_session.execute(query)
return results.scalars().all()
@ -107,7 +107,6 @@ def get_overhaul_system_components():
}
# async def get(*, db_session: DbSession, scope_id: str) -> Optional[Scope]:
# """Returns a document based on the given document id."""
# query = Select(Scope).filter(Scope.id == scope_id)

@ -1,18 +1,19 @@
from sqlalchemy import UUID, Column, Float, ForeignKey, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy import UUID, Column, Float, Integer, String, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
from src.workorder.model import MasterWorkOrder
from sqlalchemy.ext.hybrid import hybrid_property
class OverhaulActivity(Base, DefaultMixin):
__tablename__ = "oh_tr_overhaul_activity"
assetnum = Column(String, nullable=True)
overhaul_scope_id = Column(UUID(as_uuid=True), ForeignKey(
"oh_ms_overhaul_scope.id"), nullable=False)
overhaul_scope_id = Column(
UUID(as_uuid=True), ForeignKey("oh_ms_overhaul_scope.id"), nullable=False
)
material_cost = Column(Float, nullable=False, default=0)
service_cost = Column(Float, nullable=False, default=0)
status = Column(String, nullable=False, default="pending")
@ -21,11 +22,10 @@ class OverhaulActivity(Base, DefaultMixin):
"MasterEquipment",
lazy="raise",
primaryjoin="and_(OverhaulActivity.assetnum == foreign(MasterEquipment.assetnum))",
uselist=False # Add this if it's a one-to-one relationship
uselist=False, # Add this if it's a one-to-one relationship
)
overhaul_scope = relationship(
"OverhaulScope",
lazy="raise",
)

@ -1,23 +1,36 @@
from typing import List, Optional
from uuid import UUID
from fastapi import APIRouter, HTTPException, Query, status
from .service import get_all, create, get, update, delete
from .schema import OverhaulActivityCreate, OverhaulActivityPagination, OverhaulActivityRead, OverhaulActivityUpdate
from fastapi import APIRouter, HTTPException, Query, status
from src.database.service import (CommonParameters, DbSession,
search_filter_sort_paginate)
from src.models import StandardResponse
from src.database.service import CommonParameters, search_filter_sort_paginate, DbSession
from .schema import (OverhaulActivityCreate, OverhaulActivityPagination,
OverhaulActivityRead, OverhaulActivityUpdate)
from .service import create, delete, get, get_all, update
router = APIRouter()
@router.get("/{overhaul_session}", response_model=StandardResponse[OverhaulActivityPagination])
async def get_scope_equipments(common: CommonParameters, overhaul_session: str, assetnum: Optional[str] = Query(None), scope_name: Optional[str] = Query(None)):
@router.get(
"/{overhaul_session}", response_model=StandardResponse[OverhaulActivityPagination]
)
async def get_scope_equipments(
common: CommonParameters,
overhaul_session: str,
assetnum: Optional[str] = Query(None),
scope_name: Optional[str] = Query(None),
):
"""Get all scope activity pagination."""
# return
data = await get_all(common=common, assetnum=assetnum, scope_name=scope_name, overhaul_session_id=overhaul_session)
data = await get_all(
common=common,
assetnum=assetnum,
scope_name=scope_name,
overhaul_session_id=overhaul_session,
)
return StandardResponse(
data=data,
@ -25,17 +38,32 @@ async def get_scope_equipments(common: CommonParameters, overhaul_session: str,
)
@ router.post("/{overhaul_session}", response_model=StandardResponse[List[str]])
async def create_overhaul_equipment(db_session: DbSession, overhaul_activty_in: OverhaulActivityCreate, overhaul_session: str):
@router.post("/{overhaul_session}", response_model=StandardResponse[List[str]])
async def create_overhaul_equipment(
db_session: DbSession,
overhaul_activty_in: OverhaulActivityCreate,
overhaul_session: str,
):
activity = await create(db_session=db_session, overhaul_activty_in=overhaul_activty_in, overhaul_session_id=overhaul_session)
activity = await create(
db_session=db_session,
overhaul_activty_in=overhaul_activty_in,
overhaul_session_id=overhaul_session,
)
return StandardResponse(data=activity, message="Data created successfully")
@ router.get("/{overhaul_session}/{assetnum}", response_model=StandardResponse[OverhaulActivityRead])
async def get_overhaul_equipment(db_session: DbSession, assetnum: str, overhaul_session):
equipment = await get(db_session=db_session, assetnum=assetnum, overhaul_session_id=overhaul_session)
@router.get(
"/{overhaul_session}/{assetnum}",
response_model=StandardResponse[OverhaulActivityRead],
)
async def get_overhaul_equipment(
db_session: DbSession, assetnum: str, overhaul_session
):
equipment = await get(
db_session=db_session, assetnum=assetnum, overhaul_session_id=overhaul_session
)
if not equipment:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -45,8 +73,15 @@ async def get_overhaul_equipment(db_session: DbSession, assetnum: str, overhaul_
return StandardResponse(data=equipment, message="Data retrieved successfully")
@ router.put("/{overhaul_session}/{assetnum}", response_model=StandardResponse[OverhaulActivityRead])
async def update_scope(db_session: DbSession, scope_equipment_activity_in: OverhaulActivityUpdate, assetnum: str):
@router.put(
"/{overhaul_session}/{assetnum}",
response_model=StandardResponse[OverhaulActivityRead],
)
async def update_scope(
db_session: DbSession,
scope_equipment_activity_in: OverhaulActivityUpdate,
assetnum: str,
):
activity = await get(db_session=db_session, assetnum=assetnum)
if not activity:
@ -55,10 +90,20 @@ async def update_scope(db_session: DbSession, scope_equipment_activity_in: Overh
detail="A data with this id does not exist.",
)
return StandardResponse(data=await update(db_session=db_session, activity=activity, scope_equipment_activity_in=scope_equipment_activity_in), message="Data updated successfully")
return StandardResponse(
data=await update(
db_session=db_session,
activity=activity,
scope_equipment_activity_in=scope_equipment_activity_in,
),
message="Data updated successfully",
)
@ router.delete("/{overhaul_session}/{assetnum}", response_model=StandardResponse[OverhaulActivityRead])
@router.delete(
"/{overhaul_session}/{assetnum}",
response_model=StandardResponse[OverhaulActivityRead],
)
async def delete_scope(db_session: DbSession, assetnum: str):
activity = await get(db_session=db_session, assetnum=assetnum)

@ -1,9 +1,9 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field
from src.models import DefultBase, Pagination
from src.scope_equipment.schema import MasterEquipmentRead

@ -1,62 +1,81 @@
import asyncio
from uuid import UUID
from sqlalchemy import Select, Delete, func, select, update as sqlUpdate
from sqlalchemy.orm import joinedload
from typing import List, Optional
from uuid import UUID
from sqlalchemy import Delete, Select, func, select
from sqlalchemy import update as sqlUpdate
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.orm import joinedload
from src.auth.service import CurrentUser
from src.database.core import DbSession
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_scope.model import OverhaulScope
from src.overhaul_scope.service import get as get_session
from .model import OverhaulActivity
from .schema import OverhaulActivityCreate, OverhaulActivityUpdate, OverhaulActivityRead
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.auth.service import CurrentUser
from src.overhaul_scope.service import get as get_session
from .schema import (OverhaulActivityCreate, OverhaulActivityRead,
OverhaulActivityUpdate)
async def get(*, db_session: DbSession, assetnum: str, overhaul_session_id: Optional[UUID] = None) -> Optional[OverhaulActivityRead]:
async def get(
*, db_session: DbSession, assetnum: str, overhaul_session_id: Optional[UUID] = None
) -> Optional[OverhaulActivityRead]:
"""Returns a document based on the given document id."""
query = Select(OverhaulActivity).where(
OverhaulActivity.assetnum == assetnum).options(joinedload(OverhaulActivity.equipment))
query = (
Select(OverhaulActivity)
.where(OverhaulActivity.assetnum == assetnum)
.options(joinedload(OverhaulActivity.equipment))
)
if overhaul_session_id:
query = query.filter(
OverhaulActivity.overhaul_scope_id == overhaul_session_id)
query = query.filter(OverhaulActivity.overhaul_scope_id == overhaul_session_id)
result = await db_session.execute(query)
return result.scalar()
async def get_all(*, common: CommonParameters, overhaul_session_id: UUID, assetnum: Optional[str] = None, scope_name: Optional[str] = None):
query = Select(OverhaulActivity).where(
OverhaulActivity.overhaul_scope_id == overhaul_session_id).options(joinedload(OverhaulActivity.equipment))
async def get_all(
*,
common: CommonParameters,
overhaul_session_id: UUID,
assetnum: Optional[str] = None,
scope_name: Optional[str] = None
):
query = (
Select(OverhaulActivity)
.where(OverhaulActivity.overhaul_scope_id == overhaul_session_id)
.options(joinedload(OverhaulActivity.equipment))
)
if assetnum:
query = query.filter(OverhaulActivity.assetnum == assetnum).options(
joinedload(OverhaulActivity.overhaul_scope))
joinedload(OverhaulActivity.overhaul_scope)
)
if scope_name:
query = query.filter(OverhaulActivity.scope_name == scope_name).options(
joinedload(OverhaulActivity.overhaul_scope))
joinedload(OverhaulActivity.overhaul_scope)
)
results = await search_filter_sort_paginate(model=query, **common)
return results
async def get_all_by_session_id(*, db_session:DbSession, overhaul_session_id):
query = Select(OverhaulActivity).where(
OverhaulActivity.overhaul_scope_id == overhaul_session_id).options(joinedload(OverhaulActivity.equipment))
async def get_all_by_session_id(*, db_session: DbSession, overhaul_session_id):
query = (
Select(OverhaulActivity)
.where(OverhaulActivity.overhaul_scope_id == overhaul_session_id)
.options(joinedload(OverhaulActivity.equipment))
)
results = await db_session.execute(query)
return results.scalars().all()
# async def create(*, db_session: DbSession, overhaul_activty_in: OverhaulActivityCreate, overhaul_session_id: UUID):
# # Check if the combination of assetnum and activity_id already exists
# existing_equipment_query = (
@ -90,14 +109,22 @@ async def get_all_by_session_id(*, db_session:DbSession, overhaul_session_id):
# return activity_with_relationship
async def create(*, db_session: DbSession, overhaul_activty_in: OverhaulActivityCreate, overhaul_session_id: UUID):
async def create(
*,
db_session: DbSession,
overhaul_activty_in: OverhaulActivityCreate,
overhaul_session_id: UUID
):
"""Creates a new document."""
assetnums = overhaul_activty_in.assetnums
if not assetnums:
return []
# Get session and count in parallel
session = await get_session(db_session=db_session, overhaul_session_id=overhaul_session_id)
session = await get_session(
db_session=db_session, overhaul_session_id=overhaul_session_id
)
equipment_count = await db_session.scalar(
select(func.count())
.select_from(OverhaulActivity)
@ -107,25 +134,25 @@ async def create(*, db_session: DbSession, overhaul_activty_in: OverhaulActivity
# Calculate costs for all records
total_equipment = equipment_count + len(assetnums)
material_cost = get_material_cost(
scope=session.type, total_equipment=total_equipment)
service_cost = get_service_cost(
scope=session.type, total_equipment=total_equipment)
scope=session.type, total_equipment=total_equipment
)
service_cost = get_service_cost(scope=session.type, total_equipment=total_equipment)
# Create the insert statement
stmt = insert(OverhaulActivity).values([
{
'assetnum': assetnum,
'overhaul_scope_id': overhaul_session_id,
'material_cost': material_cost,
'service_cost': service_cost
}
for assetnum in assetnums
])
stmt = insert(OverhaulActivity).values(
[
{
"assetnum": assetnum,
"overhaul_scope_id": overhaul_session_id,
"material_cost": material_cost,
"service_cost": service_cost,
}
for assetnum in assetnums
]
)
# Add the ON CONFLICT DO NOTHING clause
stmt = stmt.on_conflict_do_nothing(
index_elements=["assetnum", "overhaul_scope_id"]
)
stmt = stmt.on_conflict_do_nothing(index_elements=["assetnum", "overhaul_scope_id"])
# Execute the statement
await db_session.execute(stmt)
@ -139,7 +166,12 @@ async def create(*, db_session: DbSession, overhaul_activty_in: OverhaulActivity
return assetnums
async def update(*, db_session: DbSession, activity: OverhaulActivity, overhaul_activity_in: OverhaulActivityUpdate):
async def update(
*,
db_session: DbSession,
activity: OverhaulActivity,
overhaul_activity_in: OverhaulActivityUpdate
):
"""Updates a document."""
data = overhaul_activity_in.model_dump()

@ -1,33 +1,35 @@
from decimal import Decimal, getcontext
def get_material_cost(scope, total_equipment):
# Set precision to 28 digits (maximum precision for Decimal)
getcontext().prec = 28
if not total_equipment: # Guard against division by zero
return float(0)
if scope == 'B':
result = Decimal('365539731101') / Decimal(str(total_equipment))
if scope == "B":
result = Decimal("365539731101") / Decimal(str(total_equipment))
return float(result)
else:
result = Decimal('8565468127') / Decimal(str(total_equipment))
result = Decimal("8565468127") / Decimal(str(total_equipment))
return float(result)
return float(0)
def get_service_cost(scope, total_equipment):
# Set precision to 28 digits (maximum precision for Decimal)
getcontext().prec = 28
if not total_equipment: # Guard against division by zero
return float(0)
if scope == 'B':
result = Decimal('36405830225') / Decimal(str(total_equipment))
if scope == "B":
result = Decimal("36405830225") / Decimal(str(total_equipment))
return float(result)
else:
result = Decimal('36000000000') / Decimal(str(total_equipment))
result = Decimal("36000000000") / Decimal(str(total_equipment))
return float(result)
return float(0)
return float(0)

@ -1,28 +1,29 @@
from sqlalchemy import (UUID, Column, DateTime, Float, ForeignKey, Integer,
String)
from sqlalchemy.orm import relationship
from sqlalchemy import Column, DateTime, Float, Integer, String, UUID, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
class OverhaulJob(Base, DefaultMixin):
__tablename__ = "oh_tr_overhaul_job"
overhaul_activity_id = Column(UUID(as_uuid=True), ForeignKey(
"oh_tr_overhaul_activity.id"), nullable=False)
scope_equipment_job_id = Column(UUID(as_uuid=True), ForeignKey(
"oh_ms_scope_equipment_job.id", ondelete="cascade"), nullable=False)
overhaul_activity_id = Column(
UUID(as_uuid=True), ForeignKey("oh_tr_overhaul_activity.id"), nullable=False
)
scope_equipment_job_id = Column(
UUID(as_uuid=True),
ForeignKey("oh_ms_scope_equipment_job.id", ondelete="cascade"),
nullable=False,
)
notes = Column(String, nullable=True)
status = Column(String, nullable=True, default="pending")
scope_equipment_job = relationship(
"ScopeEquipmentJob", lazy="raise"
"ScopeEquipmentJob", lazy="raise", back_populates="overhaul_jobs"
)
overhaul_activity = relationship(
"OverhaulActivity", lazy="raise"
)
overhaul_activity = relationship("OverhaulActivity", lazy="raise")

@ -1,19 +1,22 @@
from typing import List, Optional
from typing import Optional, List
from fastapi import APIRouter, HTTPException, status
from .schema import OverhaulJobBase, OverhaulJobRead, OverhaulJobPagination, OverhaulJobCreate
from .service import get_all, create
from src.database.service import CommonParameters
from src.database.core import DbSession
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters
from src.models import StandardResponse
from .schema import (OverhaulJobBase, OverhaulJobCreate, OverhaulJobPagination,
OverhaulJobRead)
from .service import create, get_all
router = APIRouter()
@router.get("/{overhaul_equipment_id}", response_model=StandardResponse[OverhaulJobPagination])
@router.get(
"/{overhaul_equipment_id}", response_model=StandardResponse[OverhaulJobPagination]
)
async def get_jobs(common: CommonParameters, overhaul_equipment_id: str):
"""Get all scope pagination."""
# return
@ -24,11 +27,18 @@ async def get_jobs(common: CommonParameters, overhaul_equipment_id: str):
message="Data retrieved successfully",
)
@router.post("/{overhaul_equipment_id}", response_model=StandardResponse[None])
async def create_overhaul_equipment_jobs(db_session: DbSession, overhaul_equipment_id, overhaul_job_in: OverhaulJobCreate):
async def create_overhaul_equipment_jobs(
db_session: DbSession, overhaul_equipment_id, overhaul_job_in: OverhaulJobCreate
):
"""Get all scope activity pagination."""
# return
await create(db_session=db_session, overhaul_equipment_id=overhaul_equipment_id, overhaul_job_in=overhaul_job_in)
await create(
db_session=db_session,
overhaul_equipment_id=overhaul_equipment_id,
overhaul_job_in=overhaul_job_in,
)
return StandardResponse(
data=None,

@ -1,12 +1,14 @@
from datetime import datetime
from typing import List, Optional
from uuid import UUID
from pydantic import Field
from src.models import DefultBase, Pagination
from src.overhaul_scope.schema import ScopeRead
from src.scope_equipment_job.schema import ScopeEquipmentJobRead
class OverhaulJobBase(DefultBase):
pass
@ -18,9 +20,11 @@ class OverhaulJobCreate(OverhaulJobBase):
class OverhaulJobUpdate(OverhaulJobBase):
pass
class OverhaulActivity(DefultBase):
id: UUID
overhaul_scope_id: UUID
overhaul_scope: ScopeRead
class OverhaulJobRead(OverhaulJobBase):

@ -1,43 +1,48 @@
from typing import Optional
from sqlalchemy import Select, Delete, func
from sqlalchemy import Delete, Select, func
from sqlalchemy.orm import selectinload
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import search_filter_sort_paginate
from .model import OverhaulJob
from typing import Optional
from .schema import OverhaulJobCreate
from src.database.core import DbSession
from src.auth.service import CurrentUser
async def get_all(*, common, overhaul_equipment_id: str):
"""Returns all documents."""
query = Select(OverhaulJob).where(OverhaulJob.overhaul_activity_id == overhaul_equipment_id).options(
selectinload(OverhaulJob.scope_equipment_job), selectinload(OverhaulJob.overhaul_activity)
query = (
Select(OverhaulJob)
.where(OverhaulJob.overhaul_activity_id == overhaul_equipment_id)
.options(
selectinload(OverhaulJob.scope_equipment_job),
selectinload(OverhaulJob.overhaul_activity),
)
)
results = await search_filter_sort_paginate(model=query, **common)
return results
async def create(*, db_session: DbSession, overhaul_equipment_id, overhaul_job_in: OverhaulJobCreate):
async def create(
*, db_session: DbSession, overhaul_equipment_id, overhaul_job_in: OverhaulJobCreate
):
overhaul_jobs = []
if not overhaul_equipment_id:
raise ValueError("assetnum parameter is required")
equipment_stmt = Select(OverhaulJob).where(
OverhaulJob.overhaul_activity_id == overhaul_equipment_id)
OverhaulJob.overhaul_activity_id == overhaul_equipment_id
)
equipment = await db_session.scalar(equipment_stmt)
for job_id in overhaul_job_in.job_ids:
overhaul_equipment_job = OverhaulJob(
overhaul_activity_id=overhaul_equipment_id, scope_equipment_job_id=job_id
overhaul_activity_id=overhaul_equipment_id, scope_equipment_job_id=job_id
)
overhaul_jobs.append(overhaul_equipment_job)

@ -1,8 +1,8 @@
from sqlalchemy import Column, DateTime, Float, Integer, String
from sqlalchemy.orm import relationship
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
class OverhaulScope(Base, DefaultMixin):
@ -15,7 +15,4 @@ class OverhaulScope(Base, DefaultMixin):
crew_number = Column(Integer, nullable=True, default=1)
status = Column(String, nullable=False, default="upcoming")
activity_equipments = relationship(
"OverhaulActivity",
lazy="selectin"
)
activity_equipments = relationship("OverhaulActivity", lazy="selectin")

@ -1,16 +1,16 @@
from typing import Optional
from fastapi import APIRouter, HTTPException, status
from .model import OverhaulScope
from .schema import ScopeCreate, ScopeRead, ScopeUpdate, ScopePagination
from .service import get, get_all, create, update, delete
from fastapi import APIRouter, HTTPException, status
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.database.core import DbSession
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.models import StandardResponse
from .model import OverhaulScope
from .schema import ScopeCreate, ScopePagination, ScopeRead, ScopeUpdate
from .service import create, delete, get, get_all, update
router = APIRouter()
@ -46,7 +46,12 @@ async def create_scope(db_session: DbSession, scope_in: ScopeCreate):
@router.put("/{scope_id}", response_model=StandardResponse[ScopeRead])
async def update_scope(db_session: DbSession, scope_id: str, scope_in: ScopeUpdate, current_user: CurrentUser):
async def update_scope(
db_session: DbSession,
scope_id: str,
scope_in: ScopeUpdate,
current_user: CurrentUser,
):
scope = await get(db_session=db_session, scope_id=scope_id)
if not scope:
@ -55,7 +60,10 @@ async def update_scope(db_session: DbSession, scope_id: str, scope_in: ScopeUpda
detail="A data with this id does not exist.",
)
return StandardResponse(data=await update(db_session=db_session, scope=scope, scope_in=scope_in), message="Data updated successfully")
return StandardResponse(
data=await update(db_session=db_session, scope=scope, scope_in=scope_in),
message="Data updated successfully",
)
@router.delete("/{scope_id}", response_model=StandardResponse[ScopeRead])

@ -1,9 +1,9 @@
from datetime import datetime
from typing import List, Optional
from uuid import UUID
from pydantic import Field
from src.models import DefultBase, Pagination

@ -1,24 +1,24 @@
from typing import Optional
from sqlalchemy import Delete, Select, func
from sqlalchemy import Select, Delete, func
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import search_filter_sort_paginate
from src.overhaul_activity.model import OverhaulActivity
from src.scope_equipment.service import get_by_scope_name
from src.utils import time_now
from .model import OverhaulScope
from .schema import ScopeCreate, ScopeUpdate
from .utils import get_material_cost, get_service_cost
from typing import Optional
from src.database.core import DbSession
from src.auth.service import CurrentUser
async def get(*, db_session: DbSession, overhaul_session_id: str) -> Optional[OverhaulScope]:
async def get(
*, db_session: DbSession, overhaul_session_id: str
) -> Optional[OverhaulScope]:
"""Returns a document based on the given document id."""
query = Select(OverhaulScope).filter(
OverhaulScope.id == overhaul_session_id)
query = Select(OverhaulScope).filter(OverhaulScope.id == overhaul_session_id)
result = await db_session.execute(query)
return result.scalars().one_or_none()
@ -44,13 +44,14 @@ async def create(*, db_session: DbSession, scope_in: ScopeCreate):
scope_name = scope_in.type
# Fix the function call - parameters were in wrong order
equipments = await get_by_scope_name(
db_session=db_session,
scope_name=scope_name
)
equipments = await get_by_scope_name(db_session=db_session, scope_name=scope_name)
material_cost = get_material_cost(scope=overhaul_session.type, total_equipment=len(equipments))
service_cost = get_service_cost(scope=overhaul_session.type, total_equipment=len(equipments))
material_cost = get_material_cost(
scope=overhaul_session.type, total_equipment=len(equipments)
)
service_cost = get_service_cost(
scope=overhaul_session.type, total_equipment=len(equipments)
)
scope_equipments = [
OverhaulActivity(
@ -95,24 +96,20 @@ async def get_overview_overhaul(*, db_session: DbSession):
current_date = time_now().date()
# For ongoing overhaul with count
ongoing_query = Select(
OverhaulScope,
func.count(OverhaulActivity.id).label('equipment_count')
).outerjoin(
OverhaulScope.activity_equipments
).where(
OverhaulScope.start_date <= current_date,
OverhaulScope.end_date >= current_date,
).group_by(
OverhaulScope.id
ongoing_query = (
Select(OverhaulScope, func.count(OverhaulActivity.id).label("equipment_count"))
.outerjoin(OverhaulScope.activity_equipments)
.where(
OverhaulScope.start_date <= current_date,
OverhaulScope.end_date >= current_date,
)
.group_by(OverhaulScope.id)
)
ongoing_result = await db_session.execute(ongoing_query)
# Use first() instead of scalar_one_or_none()
ongoing_result = ongoing_result.first()
if ongoing_result:
ongoing_overhaul, equipment_count = ongoing_result # Unpack the result tuple
@ -126,22 +123,19 @@ async def get_overview_overhaul(*, db_session: DbSession):
"duration_oh": ongoing_overhaul.duration_oh,
"crew_number": ongoing_overhaul.crew_number,
"remaining_days": (ongoing_overhaul.end_date - current_date).days,
"equipment_count": equipment_count
}
"equipment_count": equipment_count,
},
}
# For upcoming overhaul with count
upcoming_query = Select(
OverhaulScope,
func.count(OverhaulActivity.id).label('equipment_count')
).outerjoin(
OverhaulScope.activity_equipments
).where(
OverhaulScope.start_date > current_date,
).group_by(
OverhaulScope.id
).order_by(
OverhaulScope.start_date
upcoming_query = (
Select(OverhaulScope, func.count(OverhaulActivity.id).label("equipment_count"))
.outerjoin(OverhaulScope.activity_equipments)
.where(
OverhaulScope.start_date > current_date,
)
.group_by(OverhaulScope.id)
.order_by(OverhaulScope.start_date)
)
upcoming_result = await db_session.execute(upcoming_query)
@ -161,14 +155,8 @@ async def get_overview_overhaul(*, db_session: DbSession):
"duration_oh": upcoming_overhaul.duration_oh,
"crew_number": upcoming_overhaul.crew_number,
"remaining_days": days_until,
"equipment_count": equipment_count
}
"equipment_count": equipment_count,
},
}
return {
"status": "no_overhaul",
"overhaul": None
}
return {"status": "no_overhaul", "overhaul": None}

@ -1,33 +1,35 @@
from decimal import Decimal, getcontext
def get_material_cost(scope, total_equipment):
# Set precision to 28 digits (maximum precision for Decimal)
getcontext().prec = 28
if not total_equipment: # Guard against division by zero
return float(0)
if scope == 'B':
result = Decimal('365539731101') / Decimal(str(total_equipment))
if scope == "B":
result = Decimal("365539731101") / Decimal(str(total_equipment))
return float(result)
else:
result = Decimal('8565468127') / Decimal(str(total_equipment))
result = Decimal("8565468127") / Decimal(str(total_equipment))
return float(result)
return float(0)
def get_service_cost(scope, total_equipment):
# Set precision to 28 digits (maximum precision for Decimal)
getcontext().prec = 28
if not total_equipment: # Guard against division by zero
return float(0)
if scope == 'B':
result = Decimal('36405830225') / Decimal(str(total_equipment))
if scope == "B":
result = Decimal("36405830225") / Decimal(str(total_equipment))
return float(result)
else:
result = Decimal('36000000000') / Decimal(str(total_equipment))
result = Decimal("36000000000") / Decimal(str(total_equipment))
return float(result)
return float(0)
return float(0)

@ -1,5 +1,4 @@
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)

@ -1,5 +1,3 @@
from src.enums import OptimumOHEnum

@ -1,10 +1,10 @@
from sqlalchemy import UUID, Column, Date, Float, ForeignKey, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy import UUID, Column, Date, Float, Integer, String, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
from src.workorder.model import MasterWorkOrder
from sqlalchemy.ext.hybrid import hybrid_property
class ScopeEquipment(Base, DefaultMixin):
@ -20,7 +20,7 @@ class ScopeEquipment(Base, DefaultMixin):
"MasterEquipment",
lazy="raise",
primaryjoin="and_(ScopeEquipment.assetnum == foreign(MasterEquipment.assetnum))",
uselist=False # Add this if it's a one-to-one relationship
uselist=False, # Add this if it's a one-to-one relationship
)
@ -32,11 +32,11 @@ class MasterEquipment(Base, DefaultMixin):
system_tag = Column(String, nullable=True)
location_tag = Column(String, nullable=True)
name = Column(String, nullable=True)
equipment_tree_id = Column(UUID(as_uuid=True), ForeignKey(
"ms_equipment_tree.id"), nullable=True)
equipment_tree_id = Column(
UUID(as_uuid=True), ForeignKey("ms_equipment_tree.id"), nullable=True
)
equipment_tree = relationship(
"MasterEquipmentTree", backref="master_equipments")
equipment_tree = relationship("MasterEquipmentTree", backref="master_equipments")
class MasterEquipmentTree(Base, DefaultMixin):

@ -1,17 +1,20 @@
from typing import List, Optional
from fastapi import APIRouter, HTTPException, status
from fastapi.params import Query
from .model import ScopeEquipment
from .schema import ScopeEquipmentCreate, ScopeEquipmentPagination, ScopeEquipmentRead, ScopeEquipmentUpdate, MasterEquipmentPagination
from .service import get_all, create, update, delete, get_all_master_equipment, get_by_assetnum
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.database.core import DbSession
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.models import StandardResponse
from .model import ScopeEquipment
from .schema import (MasterEquipmentPagination, ScopeEquipmentCreate,
ScopeEquipmentPagination, ScopeEquipmentRead,
ScopeEquipmentUpdate)
from .service import (create, delete, get_all, get_all_master_equipment,
get_by_assetnum, update)
router = APIRouter()
@ -27,7 +30,10 @@ async def get_scope_equipments(common: CommonParameters, scope_name: str = Query
)
@router.get("/available/{scope_name}", response_model=StandardResponse[MasterEquipmentPagination])
@router.get(
"/available/{scope_name}",
response_model=StandardResponse[MasterEquipmentPagination],
)
async def get_master_equipment(common: CommonParameters, scope_name: str):
results = await get_all_master_equipment(common=common, scope_name=scope_name)
@ -35,14 +41,18 @@ async def get_master_equipment(common: CommonParameters, scope_name: str):
@router.post("", response_model=StandardResponse[List[str]])
async def create_scope_equipment(db_session: DbSession, scope_equipment_in: ScopeEquipmentCreate):
async def create_scope_equipment(
db_session: DbSession, scope_equipment_in: ScopeEquipmentCreate
):
scope = await create(db_session=db_session, scope_equipment_in=scope_equipment_in)
return StandardResponse(data=scope, message="Data created successfully")
@router.put("/{assetnum}", response_model=StandardResponse[ScopeEquipmentRead])
async def update_scope_equipment(db_session: DbSession, assetnum: str, scope__equipment_in: ScopeEquipmentUpdate):
async def update_scope_equipment(
db_session: DbSession, assetnum: str, scope__equipment_in: ScopeEquipmentUpdate
):
scope_equipment = await get_by_assetnum(db_session=db_session, assetnum=assetnum)
if not scope_equipment:
@ -51,7 +61,14 @@ async def update_scope_equipment(db_session: DbSession, assetnum: str, scope__eq
detail="A data with this id does not exist.",
)
return StandardResponse(data=await update(db_session=db_session, scope_equipment=scope_equipment, scope__equipment_in=scope__equipment_in), message="Data updated successfully")
return StandardResponse(
data=await update(
db_session=db_session,
scope_equipment=scope_equipment,
scope__equipment_in=scope__equipment_in,
),
message="Data updated successfully",
)
@router.delete("/{assetnum}", response_model=StandardResponse[None])

@ -1,11 +1,12 @@
from datetime import datetime
from typing import List, Optional
from uuid import UUID
from pydantic import Field, computed_field, field_validator, validator
from src.models import DefultBase, Pagination
from src.overhaul_scope.schema import ScopeRead
from .enum import ScopeEquipmentType

@ -1,25 +1,28 @@
from datetime import datetime, timedelta
from typing import Optional, Union
from fastapi import HTTPException, status
from sqlalchemy import Select, Delete, and_, desc, func, not_, or_
from sqlalchemy import Delete, Select, and_, desc, func, not_, or_
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.orm import selectinload
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.overhaul_scope.model import OverhaulScope
from src.scope_equipment.enum import ScopeEquipmentType
from src.workorder.model import MasterWorkOrder
from .model import MasterEquipmentTree, ScopeEquipment, MasterEquipment
from .schema import ScopeEquipmentCreate, ScopeEquipmentUpdate
from typing import Optional, Union
from sqlalchemy.orm import selectinload
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.database.core import DbSession
from src.auth.service import CurrentUser
from .model import MasterEquipment, MasterEquipmentTree, ScopeEquipment
from .schema import ScopeEquipmentCreate, ScopeEquipmentUpdate
async def get_by_assetnum(*, db_session: DbSession, assetnum: str):
query = Select(ScopeEquipment).filter(ScopeEquipment.assetnum == assetnum).options(
selectinload(ScopeEquipment.master_equipment))
query = (
Select(ScopeEquipment)
.filter(ScopeEquipment.assetnum == assetnum)
.options(selectinload(ScopeEquipment.master_equipment))
)
result = await db_session.execute(query)
return result.unique().scalars().one_or_none()
@ -28,7 +31,8 @@ async def get_by_assetnum(*, db_session: DbSession, assetnum: str):
async def get_all(*, common, scope_name: str = None):
"""Returns all documents."""
query = Select(ScopeEquipment).options(
selectinload(ScopeEquipment.master_equipment))
selectinload(ScopeEquipment.master_equipment)
)
query = query.order_by(desc(ScopeEquipment.created_at))
@ -48,12 +52,17 @@ async def create(*, db_session: DbSession, scope_equipment_in: ScopeEquipmentCre
if scope_equipment_in.type == ScopeEquipmentType.TEMP:
# Search for the next or ongoing overhaul session for the given scope
stmt = Select(OverhaulScope.end_date).where(
OverhaulScope.type == scope_equipment_in.scope_name,
(OverhaulScope.start_date <= datetime.now()) & (
OverhaulScope.end_date >= datetime.now()) # Ongoing
| (OverhaulScope.start_date > datetime.now()) # Upcoming
).order_by(OverhaulScope.start_date.asc()).limit(1)
stmt = (
Select(OverhaulScope.end_date)
.where(
OverhaulScope.type == scope_equipment_in.scope_name,
(OverhaulScope.start_date <= datetime.now())
& (OverhaulScope.end_date >= datetime.now()) # Ongoing
| (OverhaulScope.start_date > datetime.now()), # Upcoming
)
.order_by(OverhaulScope.start_date.asc())
.limit(1)
)
result = await db_session.execute(stmt)
removal_date = result.scalar_one_or_none()
@ -61,14 +70,16 @@ async def create(*, db_session: DbSession, scope_equipment_in: ScopeEquipmentCre
# If no overhaul found, set a default removal date or handle the error
if removal_date is None:
# Handle if no overhaul session is found, set default or raise an error
removal_date = datetime.now() + timedelta(days=30) # Example: 30 days from now
removal_date = datetime.now() + timedelta(
days=30
) # Example: 30 days from now
for assetnum in assetnums:
stmt = insert(ScopeEquipment).values(
assetnum=assetnum,
scope_overhaul=scope_equipment_in.scope_name,
type=scope_equipment_in.type,
removal_date=removal_date
removal_date=removal_date,
)
stmt = stmt.on_conflict_do_nothing(
@ -82,7 +93,12 @@ async def create(*, db_session: DbSession, scope_equipment_in: ScopeEquipmentCre
return results
async def update(*, db_session: DbSession, scope_equipment: ScopeEquipment, scope_equipment_in: ScopeEquipmentUpdate):
async def update(
*,
db_session: DbSession,
scope_equipment: ScopeEquipment,
scope_equipment_in: ScopeEquipmentUpdate
):
"""Updates a document."""
data = scope_equipment_in.model_dump()
@ -99,11 +115,10 @@ async def update(*, db_session: DbSession, scope_equipment: ScopeEquipment, scop
async def delete(*, db_session: DbSession, assetnum: str):
"""Deletes a document."""
query = Delete(ScopeEquipment).where(
ScopeEquipment.assetnum == assetnum)
query = Delete(ScopeEquipment).where(ScopeEquipment.assetnum == assetnum)
await db_session.execute(query)
await db_session.commit()
return assetnum
# query = Select(ScopeEquipment).filter(
@ -128,10 +143,13 @@ async def delete(*, db_session: DbSession, assetnum: str):
# await db_session.commit()
async def get_by_scope_name(*, db_session: DbSession, scope_name: Optional[str]) -> Optional[ScopeEquipment]:
async def get_by_scope_name(
*, db_session: DbSession, scope_name: Optional[str]
) -> Optional[ScopeEquipment]:
"""Returns a document based on the given document id."""
query = Select(ScopeEquipment).options(
selectinload(ScopeEquipment.master_equipment))
selectinload(ScopeEquipment.master_equipment)
)
if scope_name:
query = query.filter(ScopeEquipment.scope_overhaul == scope_name)
@ -156,11 +174,14 @@ async def get_by_scope_name(*, db_session: DbSession, scope_name: Optional[str])
async def get_all_master_equipment(*, common: CommonParameters, scope_name):
equipments_scope = [equip.assetnum for equip in await get_by_scope_name(
db_session=common.get("db_session"), scope_name=scope_name)]
equipments_scope = [
equip.assetnum
for equip in await get_by_scope_name(
db_session=common.get("db_session"), scope_name=scope_name
)
]
query = Select(MasterEquipment).filter(
MasterEquipment.assetnum.is_not(None))
query = Select(MasterEquipment).filter(MasterEquipment.assetnum.is_not(None))
# Only add not_in filter if there are items in equipments_scope
if equipments_scope:
@ -171,9 +192,11 @@ async def get_all_master_equipment(*, common: CommonParameters, scope_name):
async def get_equipment_level_by_no(*, db_session: DbSession, level: int):
query = Select(MasterEquipment).join(MasterEquipment.equipment_tree).where(
MasterEquipmentTree.level_no == level)
query = (
Select(MasterEquipment)
.join(MasterEquipment.equipment_tree)
.where(MasterEquipmentTree.level_no == level)
)
result = await db_session.execute(query)
return result.scalars().all()

@ -1,22 +1,27 @@
from sqlalchemy import UUID, Column, Float, ForeignKey, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy import UUID, Column, Float, Integer, String, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
from src.workorder.model import MasterWorkOrder
from sqlalchemy.ext.hybrid import hybrid_property
class ScopeEquipmentJob(Base, DefaultMixin):
__tablename__ = "oh_ms_scope_equipment_job"
assetnum = Column(String, nullable=False)
job_id = Column(UUID(as_uuid=True), ForeignKey(
"oh_ms_job.id", ondelete="cascade"))
job_id = Column(UUID(as_uuid=True), ForeignKey("oh_ms_job.id", ondelete="cascade"))
master_equipments = relationship(
"MasterEquipment", lazy="raise", primaryjoin="and_(ScopeEquipmentJob.assetnum == foreign(MasterEquipment.assetnum))", uselist=False)
"MasterEquipment",
lazy="raise",
primaryjoin="and_(ScopeEquipmentJob.assetnum == foreign(MasterEquipment.assetnum))",
uselist=False,
)
job = relationship("MasterActivity", lazy="selectin")
job = relationship(
"MasterActivity", lazy="selectin"
overhaul_jobs = relationship(
"OverhaulJob", back_populates="scope_equipment_job", lazy="selectin"
)

@ -1,18 +1,21 @@
from typing import Dict, List
from fastapi import APIRouter, HTTPException, Query, status
from .service import get_all, delete, create
from .schema import ScopeEquipmentJobCreate, ScopeEquipmentJobPagination
from fastapi import APIRouter, HTTPException, Query, status
from src.database.service import (CommonParameters, DbSession,
search_filter_sort_paginate)
from src.models import StandardResponse
from src.database.service import CommonParameters, search_filter_sort_paginate, DbSession
from .schema import ScopeEquipmentJobCreate, ScopeEquipmentJobPagination
from .service import create, delete, get_all
router = APIRouter()
@router.get("/{assetnum}", response_model=StandardResponse[ScopeEquipmentJobPagination])
async def get_scope_equipment_jobs(db_session: DbSession, assetnum, common: CommonParameters):
async def get_scope_equipment_jobs(
db_session: DbSession, assetnum, common: CommonParameters
):
"""Get all scope activity pagination."""
# return
data = await get_all(db_session=db_session, assetnum=assetnum, common=common)
@ -24,7 +27,9 @@ async def get_scope_equipment_jobs(db_session: DbSession, assetnum, common: Comm
@router.post("/{assetnum}", response_model=StandardResponse[None])
async def create_scope_equipment_jobs(db_session: DbSession, assetnum, scope_job_in: ScopeEquipmentJobCreate):
async def create_scope_equipment_jobs(
db_session: DbSession, assetnum, scope_job_in: ScopeEquipmentJobCreate
):
"""Get all scope activity pagination."""
# return
await create(db_session=db_session, assetnum=assetnum, scope_job_in=scope_job_in)

@ -1,11 +1,12 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
from src.job.schema import ActivityMasterRead
from src.models import DefultBase, Pagination
from src.overhaul_scope.schema import ScopeRead
class ScopeEquipmentJobBase(DefultBase):
@ -21,9 +22,20 @@ class ScopeEquipmentJobUpdate(ScopeEquipmentJobBase):
cost: Optional[str] = Field(0)
class OverhaulActivity(DefultBase):
id: UUID
overhaul_scope: ScopeRead
class OverhaulJob(DefultBase):
id: UUID
overhaul_activity: OverhaulActivity
class ScopeEquipmentJobRead(ScopeEquipmentJobBase):
id: UUID
job: ActivityMasterRead
overhaul_jobs: List[OverhaulJob] = []
class ScopeEquipmentJobPagination(Pagination):

@ -1,20 +1,20 @@
import random
from sqlalchemy import Select, Delete, and_
from sqlalchemy.orm import selectinload
from typing import Optional
from sqlalchemy import Delete, Select, and_
from sqlalchemy.orm import selectinload
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.scope_equipment.model import MasterEquipment, MasterEquipmentTree
from src.scope_equipment.service import get_equipment_level_by_no
from .model import ScopeEquipmentJob
from .schema import ScopeEquipmentJobCreate
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.auth.service import CurrentUser
from src.overhaul_job.model import OverhaulJob
from src.overhaul_activity.model import OverhaulActivity
# async def get(*, db_session: DbSession, scope_equipment_activity_id: str) -> Optional[ScopeEquipmentActivity]:
# """Returns a document based on the given document id."""
@ -28,8 +28,7 @@ async def get_all(db_session: DbSession, assetnum: Optional[str], common):
raise ValueError("assetnum parameter is required")
# First get the parent equipment
equipment_stmt = Select(MasterEquipment).where(
MasterEquipment.assetnum == assetnum)
equipment_stmt = Select(MasterEquipment).where(MasterEquipment.assetnum == assetnum)
equipment: MasterEquipment = await db_session.scalar(equipment_stmt)
if not equipment:
@ -38,9 +37,13 @@ async def get_all(db_session: DbSession, assetnum: Optional[str], common):
# Build query for parts
stmt = (
Select(ScopeEquipmentJob)
.where(
ScopeEquipmentJob.assetnum == assetnum
).options(selectinload(ScopeEquipmentJob.job))
.where(ScopeEquipmentJob.assetnum == assetnum)
.options(
selectinload(ScopeEquipmentJob.job),
selectinload(ScopeEquipmentJob.overhaul_jobs)
.selectinload(OverhaulJob.overhaul_activity)
.selectinload(OverhaulActivity.overhaul_scope),
)
)
results = await search_filter_sort_paginate(model=stmt, **common)
@ -48,23 +51,23 @@ async def get_all(db_session: DbSession, assetnum: Optional[str], common):
return results
async def create(*, db_session: DbSession, assetnum, scope_job_in: ScopeEquipmentJobCreate):
async def create(
*, db_session: DbSession, assetnum, scope_job_in: ScopeEquipmentJobCreate
):
scope_jobs = []
if not assetnum:
raise ValueError("assetnum parameter is required")
# First get the parent equipment
equipment_stmt = Select(MasterEquipment).where(
MasterEquipment.assetnum == assetnum)
equipment_stmt = Select(MasterEquipment).where(MasterEquipment.assetnum == assetnum)
equipment: MasterEquipment = await db_session.scalar(equipment_stmt)
if not equipment:
raise ValueError(f"No equipment found with assetnum: {assetnum}")
for job_id in scope_job_in.job_ids:
scope_equipment_job = ScopeEquipmentJob(
assetnum=assetnum, job_id=job_id)
scope_equipment_job = ScopeEquipmentJob(assetnum=assetnum, job_id=job_id)
scope_jobs.append(scope_equipment_job)
db_session.add_all(scope_jobs)

@ -1,10 +1,10 @@
from sqlalchemy import UUID, Column, Float, ForeignKey, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy import UUID, Column, Float, Integer, String, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
from src.workorder.model import MasterWorkOrder
from sqlalchemy.ext.hybrid import hybrid_property
class ScopeEquipmentPart(Base, DefaultMixin):
@ -14,6 +14,8 @@ class ScopeEquipmentPart(Base, DefaultMixin):
stock = Column(Integer, nullable=False, default=0)
master_equipments = relationship(
"MasterEquipment", lazy="raise", primaryjoin="and_(ScopeEquipmentPart.assetnum == foreign(MasterEquipment.assetnum))", uselist=False)
"MasterEquipment",
lazy="raise",
primaryjoin="and_(ScopeEquipmentPart.assetnum == foreign(MasterEquipment.assetnum))",
uselist=False,
)

@ -1,13 +1,15 @@
from typing import Dict, List
from fastapi import APIRouter, HTTPException, Query, status
from src.database.service import (CommonParameters, DbSession,
search_filter_sort_paginate)
from src.models import StandardResponse
from .schema import (ScopeEquipmentActivityCreate,
ScopeEquipmentActivityPagination,
ScopeEquipmentActivityRead, ScopeEquipmentActivityUpdate)
from .service import get_all
from .schema import ScopeEquipmentActivityCreate, ScopeEquipmentActivityPagination, ScopeEquipmentActivityRead, ScopeEquipmentActivityUpdate
from src.models import StandardResponse
from src.database.service import CommonParameters, search_filter_sort_paginate, DbSession
router = APIRouter()

@ -1,9 +1,9 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
from src.models import DefultBase, Pagination

@ -1,26 +1,24 @@
import random
from sqlalchemy import Select, Delete, and_
from sqlalchemy.orm import selectinload
from typing import Optional
from sqlalchemy import Delete, Select, and_
from sqlalchemy.orm import selectinload
from src.auth.service import CurrentUser
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.scope_equipment.model import MasterEquipment, MasterEquipmentTree
from src.scope_equipment.service import get_equipment_level_by_no
from .model import ScopeEquipmentPart
from .schema import ScopeEquipmentActivityCreate, ScopeEquipmentActivityUpdate
from src.database.core import DbSession
from src.database.service import CommonParameters, search_filter_sort_paginate
from src.auth.service import CurrentUser
# async def get(*, db_session: DbSession, scope_equipment_activity_id: str) -> Optional[ScopeEquipmentActivity]:
# """Returns a document based on the given document id."""
# result = await db_session.get(ScopeEquipmentActivity, scope_equipment_activity_id)
# return result
def create_dummy_parts(assetnum: str, count: int = 5):
"""
Create a list of dummy ScopeEquipmentPart objects with random stock values.
@ -37,17 +35,14 @@ def create_dummy_parts(assetnum: str, count: int = 5):
# Generate a unique part asset number
part_assetnum = f"{assetnum}_PART_{i}"
stock = random.randint(1, 100) # Random stock value between 1 and 100
parts.append({
"assetnum": part_assetnum,
"stock": stock
})
parts.append({"assetnum": part_assetnum, "stock": stock})
return parts
async def get_all(db_session: DbSession, assetnum: Optional[str]):
# Example usage
dummy_parts = create_dummy_parts(assetnum, count=10)
# if not assetnum:
# raise ValueError("assetnum parameter is required")

@ -1,10 +1,9 @@
from datetime import datetime, timedelta, timezone
import re
from datetime import datetime, timedelta, timezone
from typing import Optional
from dateutil.relativedelta import relativedelta
import pytz
from dateutil.relativedelta import relativedelta
from src.config import TIMEZONE
@ -61,7 +60,11 @@ def parse_date_string(date_str: str) -> Optional[datetime]:
# Parse the date and set it to start of day in UTC
dt = datetime.strptime(date_str, fmt)
dt = dt.replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=timezone.tzname("Asia/Jakarta")
hour=0,
minute=0,
second=0,
microsecond=0,
tzinfo=timezone.tzname("Asia/Jakarta"),
)
return dt
except ValueError:

@ -1,12 +1,8 @@
from sqlalchemy import UUID, Column, Float, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy import UUID, Column, Float, Integer, String, ForeignKey
from src.database.core import Base
from src.models import DefaultMixin, IdentityMixin, TimeStampMixin
from sqlalchemy.orm import relationship
from src.models import DefaultMixin
class MasterWorkOrder(Base, DefaultMixin):
@ -17,5 +13,8 @@ class MasterWorkOrder(Base, DefaultMixin):
workgroup = Column(String, nullable=True)
total_cost_max = Column(Float, nullable=True)
scope_equipments = relationship("ScopeEquipment", lazy="raise", primaryjoin="and_(MasterWorkOrder.assetnum == foreign(ScopeEquipment.assetnum))")
scope_equipments = relationship(
"ScopeEquipment",
lazy="raise",
primaryjoin="and_(MasterWorkOrder.assetnum == foreign(ScopeEquipment.assetnum))",
)

@ -1,13 +1,12 @@
import asyncio
from typing import AsyncGenerator, Generator
import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
import pytest
from sqlalchemy_utils import drop_database, database_exists
from sqlalchemy_utils import database_exists, drop_database
from starlette.config import environ
from starlette.testclient import TestClient
@ -66,4 +65,4 @@ async def setup_db() -> AsyncGenerator[None, None]:
@pytest.fixture
async def client() -> AsyncGenerator[AsyncClient, None]:
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
yield client

@ -1,22 +1,18 @@
import uuid
from datetime import datetime
from factory import (
LazyAttribute,
LazyFunction,
Sequence,
SubFactory,
post_generation,
SelfAttribute,
)
from factory import (LazyAttribute, LazyFunction, SelfAttribute, Sequence,
SubFactory, post_generation)
from factory.alchemy import SQLAlchemyModelFactory
from factory.fuzzy import FuzzyChoice, FuzzyDateTime, FuzzyInteger, FuzzyText
from faker import Faker
from faker.providers import misc
from .database import Session
# from pytz import UTC
from .database import Session
fake = Faker()
fake.add_provider(misc)

Loading…
Cancel
Save