From 1aa9e1e9aa02e27a4c0f3ccb91961919e99b79d1 Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Fri, 3 Oct 2025 11:12:27 +0700 Subject: [PATCH] add rqeuirement sparepart for oh --- .env | 20 +- src/calculation_target_reliability/schema.py | 1 + src/calculation_target_reliability/service.py | 3 + src/calculation_time_constrains/flows.py | 4 +- src/sparepart/router.py | 4 +- src/sparepart/service.py | 258 +++++++++++++----- 6 files changed, 206 insertions(+), 84 deletions(-) diff --git a/.env b/.env index b79c063..3ba6479 100644 --- a/.env +++ b/.env @@ -9,15 +9,15 @@ DATABASE_CREDENTIAL_USER=postgres DATABASE_CREDENTIAL_PASSWORD=postgres DATABASE_NAME=digital_twin -# COLLECTOR_HOSTNAME=192.168.1.82 -# COLLECTOR_PORT=1111 -# COLLECTOR_CREDENTIAL_USER=digital_twin -# COLLECTOR_CREDENTIAL_PASSWORD=Pr0jec7@D!g!tTwiN -# COLLECTOR_NAME=digital_twin +COLLECTOR_HOSTNAME=192.168.1.82 +COLLECTOR_PORT=1111 +COLLECTOR_CREDENTIAL_USER=digital_twin +COLLECTOR_CREDENTIAL_PASSWORD=Pr0jec7@D!g!tTwiN +COLLECTOR_NAME=digital_twin -COLLECTOR_HOSTNAME=192.168.1.86 -COLLECTOR_PORT=5432 -COLLECTOR_CREDENTIAL_USER=postgres -COLLECTOR_CREDENTIAL_PASSWORD=postgres -COLLECTOR_NAME=digital_twin +# COLLECTOR_HOSTNAME=192.168.1.86 +# COLLECTOR_PORT=5432 +# COLLECTOR_CREDENTIAL_USER=postgres +# COLLECTOR_CREDENTIAL_PASSWORD=postgres +# COLLECTOR_NAME=digital_twin diff --git a/src/calculation_target_reliability/schema.py b/src/calculation_target_reliability/schema.py index 9855ed8..7a1ff85 100644 --- a/src/calculation_target_reliability/schema.py +++ b/src/calculation_target_reliability/schema.py @@ -52,6 +52,7 @@ class MaintenanceScenario(OverhaulBase): class OptimizationResult(OverhaulBase): current_plant_eaf: float target_plant_eaf: float + possible_plant_eaf:float eaf_gap: float asset_contributions: List[dict] optimization_success: bool = False diff --git a/src/calculation_target_reliability/service.py b/src/calculation_target_reliability/service.py index 75dfad3..4a96d3b 100644 --- a/src/calculation_target_reliability/service.py +++ b/src/calculation_target_reliability/service.py @@ -201,11 +201,14 @@ async def identify_worst_eaf_contributors( else: # allow overshoot tolerance by skipping large ones, continue with smaller ones continue + + possible_eaf_plant = current_plant_eaf + project_eaf_improvement # Build output with efficiency included return OptimizationResult( current_plant_eaf=current_plant_eaf, target_plant_eaf=target_eaf, + possible_plant_eaf=possible_eaf_plant, eaf_gap=eaf_gap, asset_contributions=[ { diff --git a/src/calculation_time_constrains/flows.py b/src/calculation_time_constrains/flows.py index e84be27..5ef84be 100644 --- a/src/calculation_time_constrains/flows.py +++ b/src/calculation_time_constrains/flows.py @@ -93,12 +93,14 @@ async def create_calculation( calculation_param_in=calculation_time_constrains_in, created_by=created_by, ) + + rbd_simulation_id = "f9ebaf0a-3c41-4cd6-ba60-552c0b303f3f" # results = await create_calculation_result_service( # db_session=db_session, calculation=calculation_data, token=token # ) results = await run_simulation_with_spareparts( - db_session=db_session, calculation=calculation_data, token=token, collector_db_session=collector_db_session + db_session=db_session, calculation=calculation_data, token=token, collector_db_session=collector_db_session, simulation_id=rbd_simulation_id ) return results["id"] diff --git a/src/sparepart/router.py b/src/sparepart/router.py index 6398f93..cd12603 100644 --- a/src/sparepart/router.py +++ b/src/sparepart/router.py @@ -5,7 +5,7 @@ from src.database.service import (CommonParameters, DbSession, search_filter_sort_paginate) from src.models import StandardResponse -from .service import get_all +from .service import get_spareparts_paginated router = APIRouter() @@ -14,7 +14,7 @@ router = APIRouter() async def get_sparepart(collector_db_session:CollectorDbSession): """Get all scope activity pagination.""" # return - data = await get_all(collector_db_session) + data = await get_spareparts_paginated(collector_db_session) diff --git a/src/sparepart/service.py b/src/sparepart/service.py index a221be1..9d7ad56 100644 --- a/src/sparepart/service.py +++ b/src/sparepart/service.py @@ -23,93 +23,209 @@ from src.overhaul_scope.service import get_prev_oh log = logging.getLogger(__name__) setup_logging(logger=log) -async def get_all(db_session: DbSession): +from sqlalchemy import text +import math + +async def get_spareparts_paginated(db_session): """ - Get all spare parts with their latest PR and PO information. + Get paginated spare parts with usage, inventory, and PR/PO information. + Uses two queries: one for data, one for total count. Args: db_session: SQLAlchemy database session - assetnum: Optional asset number filter (not used in this query but kept for compatibility) - - Returns: - List of dictionaries containing spare part information + page (int): Page number (1-based) + items_per_page (int): Number of items per page """ - # Define the SQL query - query = text(""" - WITH latest_prs AS ( - SELECT DISTINCT ON (pl.item_num) - pl.item_num, - h.num as pr_number, - h.issue_date as pr_issue_date, - h.status as pr_status, - pl.qty_ordered as pr_qty_ordered, - pl.description, - pl.unit_cost, - pl.line_cost - FROM public.maximo_sparepart_pr_po h - JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num - WHERE h.type = 'PR' - AND h.issue_date IS NOT NULL - AND h.num LIKE 'K%' - ORDER BY pl.item_num, h.issue_date DESC -) - SELECT DISTINCT ON (pr.item_num) - pr.item_num, - pr.line_cost, - pr.unit_cost, - pr.description, - COALESCE(i.curbaltotal, 0) as current_balance_total, - pr.pr_number, - pr.pr_issue_date, - pr.pr_qty_ordered, - CASE - WHEN po.po_number IS NOT NULL THEN 'YES' - ELSE 'NO' - END as po_exists, - COALESCE(po.qty_received, 0) as po_qty_received, - COALESCE(po.qty_ordered, 0) as po_qty_ordered, - po.estimated_arrival_date as po_estimated_arrival_date - FROM latest_prs pr - LEFT JOIN public.maximo_inventory i ON pr.item_num = i.itemnum - LEFT JOIN LATERAL ( + # calculate limit/offset + # limit = items_per_page + # offset = (page - 1) * items_per_page + + # ----------------------------- + # Query #1: Fetch paginated rows + # ----------------------------- + data_query = text(""" + WITH oh_workorders AS ( + SELECT DISTINCT wonum, asset_location, asset_unit + FROM public.wo_staging_maximo_2 + WHERE worktype = 'OH' + AND asset_location IS NOT NULL + AND EXTRACT(YEAR FROM reportdate) >= 2019 + AND asset_unit IN ('3', '00') + ), + sparepart_usage AS ( + SELECT oh.asset_location, mwm.itemnum, mwm.itemqty, mwm.wonum + FROM oh_workorders oh + INNER JOIN public.maximo_workorder_materials mwm ON oh.wonum = mwm.wonum + ), + location_sparepart_stats AS ( + SELECT asset_location, itemnum, + COUNT(DISTINCT wonum) as total_wo_count, + SUM(itemqty) as total_qty_used, + AVG(itemqty) as avg_qty_per_wo, + MIN(itemqty) as min_qty_used, + MAX(itemqty) as max_qty_used + FROM sparepart_usage + GROUP BY asset_location, itemnum + HAVING SUM(itemqty) > 0 + ), + pr_lines AS ( + SELECT + pl.item_num, + h.num as pr_number, + h.issue_date as pr_issue_date, + h.status as pr_status, + pl.qty_ordered as pr_qty_ordered, + pl.qty_requested as pr_qty_requested + FROM public.maximo_sparepart_pr_po h + JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num + WHERE h.type = 'PR' AND EXTRACT(YEAR FROM h.issue_date) >= 2023 + ), + item_descriptions AS ( + SELECT DISTINCT + item_num, + FIRST_VALUE(description) OVER ( + PARTITION BY item_num + ORDER BY created_at DESC NULLS LAST + ) as description + FROM public.maximo_sparepart_pr_po_line + WHERE description IS NOT NULL + ), + po_lines AS ( SELECT + pl.item_num, h.num as po_number, - pl.qty_received, - pl.qty_ordered, - h.estimated_arrival_date + h.estimated_arrival_date as po_estimated_arrival_date, + h.vendeliverydate as po_vendeliverydate, + h.receipts as po_receipt, + h.status as po_status, + pl.qty_ordered as po_qty_ordered, + pl.qty_received as po_qty_received FROM public.maximo_sparepart_pr_po h JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num WHERE h.type = 'PO' - AND h.num = pr.pr_number - AND pl.item_num = pr.item_num - LIMIT 1 - ) po ON true - ORDER BY pr.item_num; + AND (h.receipts = 'NONE') + AND (h.status IS NOT NULL) + ), + pr_po_unified AS ( + SELECT + pr.item_num, + pr.pr_number, + pr.pr_issue_date, + pr.pr_qty_ordered, + pr.pr_status, + po.po_number, + COALESCE(po.po_qty_ordered,0) as po_qty_ordered, + COALESCE(po.po_qty_received,0) as po_qty_received, + po.po_estimated_arrival_date, + po.po_vendeliverydate, + po.po_receipt, + po.po_status, + CASE WHEN po.po_number IS NOT NULL THEN 'YES' ELSE 'NO' END as po_exists + FROM pr_lines pr + LEFT JOIN po_lines po + ON pr.item_num = po.item_num + AND pr.pr_number = po.po_number + ), + pr_po_agg AS ( + SELECT + item_num, + SUM(COALESCE(pr_qty_ordered,0)) as total_pr_qty, + SUM(COALESCE(po_qty_ordered,0)) as total_po_qty, + SUM(COALESCE(po_qty_received,0)) as total_po_received, + JSON_AGG( + JSON_BUILD_OBJECT( + 'pr_number', pr_number, + 'pr_issue_date', pr_issue_date, + 'pr_qty_requested', pr_qty_ordered, + 'pr_status', pr_status, + 'po_exists', po_exists, + 'po_qty_ordered', po_qty_ordered, + 'po_qty_received', po_qty_received, + 'po_estimated_arrival_date', po_estimated_arrival_date, + 'po_vendeliverydate', po_vendeliverydate, + 'po_receipt', po_receipt, + 'po_status', po_status + ) ORDER BY pr_issue_date DESC + ) as pr_po_details + FROM pr_po_unified + GROUP BY item_num + ) + SELECT + lss.itemnum, + COALESCE(id.description, 'No description available') as item_description, + lss.total_wo_count, + lss.total_qty_used, + ROUND(CAST(lss.avg_qty_per_wo AS NUMERIC), 2) as avg_qty_per_wo, + lss.min_qty_used, + lss.max_qty_used, + COALESCE(i.curbaltotal,0) as current_balance_total, + COALESCE(ap.total_pr_qty,0) as total_pr_qty, + COALESCE(ap.total_po_qty,0) as total_po_qty, + COALESCE(ap.total_po_received,0) as total_po_received, + ap.pr_po_details + FROM location_sparepart_stats lss + LEFT JOIN item_descriptions id ON lss.itemnum = id.item_num + LEFT JOIN public.maximo_inventory i ON lss.itemnum = i.itemnum + LEFT JOIN pr_po_agg ap ON lss.itemnum = ap.item_num + ORDER BY lss.asset_location, lss.itemnum """) - - # Execute the query - result = await db_session.execute(query) - - # Fetch all results and convert to list of dictionaries + + rows = await db_session.execute(data_query) + spare_parts = [] - for row in result: + for row in rows: spare_parts.append({ - "item_num": row.item_num, - "description": row.description, - "line_cost": row.line_cost, - "unit_cost": row.unit_cost, - "current_balance_total": float(row.current_balance_total) if row.current_balance_total is not None else 0.0, - "pr_number": row.pr_number, - "pr_issue_date": row.pr_issue_date, - "pr_qty_ordered": float(row.pr_qty_ordered) if row.pr_qty_ordered is not None else 0.0, - "po_exists": row.po_exists, - "po_qty_received": float(row.po_qty_received) if row.po_qty_received is not None else 0.0, - "po_qty_ordered": float(row.po_qty_ordered) if row.po_qty_ordered is not None else 0.0, - "po_estimated_arrival_date": row.po_estimated_arrival_date + "item_num": row.itemnum, + "description": row.item_description, + "current_balance_total": float(row.current_balance_total) if row.current_balance_total else 0.0, + "total_required_for_oh": float(row.avg_qty_per_wo), + "total_pr_qty": row.total_pr_qty, + "total_po_qty": row.total_po_qty, + "total_po_received": row.total_po_received, + "pr_po_details": row.pr_po_details }) - + return spare_parts + # # ----------------------------- + # # Query #2: Count total rows + # # ----------------------------- + # count_query = text(""" + # WITH oh_workorders AS ( + # SELECT DISTINCT wonum, asset_location, asset_unit + # FROM public.wo_staging_maximo_2 + # WHERE worktype = 'OH' + # AND asset_location IS NOT NULL + # AND EXTRACT(YEAR FROM reportdate) >= 2019 + # AND asset_unit IN ('3', '00') + # ), + # sparepart_usage AS ( + # SELECT oh.asset_location, mwm.itemnum, mwm.itemqty, mwm.wonum + # FROM oh_workorders oh + # INNER JOIN public.maximo_workorder_materials mwm ON oh.wonum = mwm.wonum + # ), + # location_sparepart_stats AS ( + # SELECT asset_location, itemnum + # FROM sparepart_usage + # GROUP BY asset_location, itemnum + # ) + # SELECT COUNT(*) as total_count + # FROM location_sparepart_stats; + # """) + + # total_count_result = await db_session.execute(count_query) + # total_count = total_count_result.scalar() or 0 + + # # calculate total pages + # total_pages = math.ceil(total_count / items_per_page) if items_per_page > 0 else 1 + + # return { + # "total": total_count, + # "page": page, + # "items_per_page": items_per_page, + # "total_pages": total_pages, + # "items": spare_parts + # } class ProcurementStatus(Enum):