diff --git a/submissions/praveen-singh/level5/answers.md b/submissions/praveen-singh/level5/answers.md new file mode 100644 index 000000000..57ea80821 --- /dev/null +++ b/submissions/praveen-singh/level5/answers.md @@ -0,0 +1,309 @@ +# Level 5 — Graph Thinking + +## Q1. Factory Graph Design + +The factory graph schema models production flow, worker coverage, station workload, and weekly capacity pressure. + +The schema diagram is provided in: + +- `schema.md` + +Key design choices: +- workload metrics stored directly on relationships +- worker backup coverage modeled separately from primary assignment +- weekly overload conditions represented independently from production records + +This structure supports: +- bottleneck analysis +- staffing replacement queries +- variance tracking +- hybrid vector + graph search + +--- + +# Q2. SQL vs Cypher + +## SQL Query + +```sql +SELECT + w.name, + w.can_cover_stations, + p.project_name +FROM workers w +JOIN production pr + ON pr.station_code = '016' +JOIN projects p + ON p.project_id = pr.project_id +WHERE w.can_cover_stations LIKE '%016%' +AND w.name != 'Per Gustafsson'; +``` + +--- + +## Cypher Query + +```cypher +MATCH (w:Worker)-[:BACKUP_FOR]->(s:Station {station_code: "016"}) +MATCH (p:Project)-[:FLOWS_THROUGH]->(s) +WHERE w.worker_name <> "Per Gustafsson" + +RETURN + w.worker_name AS backup_worker, + s.station_name AS station, + collect(DISTINCT p.project_name) AS affected_projects +``` + +--- + +## Why the Graph Version Is Better + +The Cypher query directly models operational relationships: +workers → stations → projects. + +In SQL, the logic is spread across multiple joins and indirect foreign key connections. +The graph query makes worker replacement impact much easier to understand visually and operationally. + +The graph model also scales better when adding: +- skill matching +- multi-station routing +- dependency chains +- overtime propagation + +--- + +# Q3. Bottleneck Analysis + +From `factory_capacity.csv`: + +- Week W04 shows planned demand exceeding available capacity +- Week W06 has one of the highest workload deficits in the dataset + +Using production records from `factory_production.csv`, the largest overload contributors include: + +| Project | Station | Planned Hours | Actual Hours | +|---|---|---|---| +| Project 104 | Station 016 | 72 | 88 | +| Project 107 | Station 020 | 64 | 79 | +| Project 102 | Station 012 | 55 | 67 | + +These projects exceeded planned workload by more than 10%, increasing station pressure and creating downstream scheduling delays. + +--- + +## Cypher Query + +```cypher +MATCH (p:Project)-[w:WORKLOAD]->(s:Station) + +WHERE w.actual_hours > (w.planned_hours * 1.10) + +RETURN + s.station_name AS station, + collect(p.project_name) AS overloaded_projects, + avg(w.actual_hours - w.planned_hours) AS avg_variance +ORDER BY avg_variance DESC +``` + +--- + +## Bottleneck Modeling Strategy + +I would model bottlenecks using a dedicated relationship property instead of separate bottleneck nodes. + +Example: + +```cypher +(:Project)-[:WORKLOAD { + planned_hours: 72, + actual_hours: 88, + overloaded: true +}]->(:Station) +``` + +This keeps operational queries simpler and avoids unnecessary node expansion for dashboard analytics. + +--- + +# Q4. Hybrid Vector + Graph Search + +## What I Would Embed + +I would embed: +- project descriptions +- product specifications +- delivery requirements +- station usage patterns +- production timelines + +This allows semantic similarity matching between incoming and historical projects. + +--- + +## Hybrid Query + +### Vector Search Stage +Find projects with semantically similar descriptions: + +```text +"450 meters of IQB beams for a hospital extension" +``` + +--- + +### Graph Filtering Stage + +```cypher +MATCH (p:Project)-[:FLOWS_THROUGH]->(s:Station) + +WHERE p.similarity_score > 0.85 +AND p.production_variance < 0.05 + +RETURN + p.project_name, + collect(DISTINCT s.station_name) AS stations_used +``` + +--- + +## Why Hybrid Search Is Better + +Filtering only by product type ignores: +- timeline pressure +- production complexity +- routing similarity +- workload behavior + +Vector search captures semantic similarity, while graph filtering validates operational compatibility. + +This combination is useful for: +- production planning +- estimating delivery risk +- predicting overload conditions +- finding reusable factory workflows + +--- + +# Q5. Level 6 Blueprint + +## Planned Node Labels + +| Node | CSV Source | +|---|---| +| Project | factory_production.csv | +| Product | factory_production.csv | +| Station | factory_production.csv | +| Worker | factory_workers.csv | +| Week | factory_production.csv | +| WeeklyLoad | factory_capacity.csv | +| Skill | factory_workers.csv | +| JobType | factory_workers.csv | + +--- + +## Planned Relationships + +| Relationship | Source | +|---|---| +| BUILDS | production records | +| FLOWS_THROUGH | station workflow | +| RUNS_DURING | weekly production | +| WORKLOAD | planned vs actual hours | +| ASSIGNED_TO | worker primary station | +| BACKUP_FOR | worker coverage stations | +| HAS_SKILL | certifications | +| HAS_JOB | worker role | +| WEEKLY_STATUS | capacity tracking | + +--- + +## Planned Dashboard Pages + +### 1. Project Overview +Shows: +- all active projects +- total planned vs actual hours +- production completion metrics + +### Cypher Query + +```cypher +MATCH (p:Project)-[w:WORKLOAD]->() + +RETURN + p.project_name, + sum(w.planned_hours), + sum(w.actual_hours) +``` + +--- + +### 2. Station Load Dashboard + +Shows: +- overloaded stations +- workload variance +- station utilization + +### Cypher Query + +```cypher +MATCH (:Project)-[w:WORKLOAD]->(s:Station) + +RETURN + s.station_name, + sum(w.actual_hours), + sum(w.planned_hours) +``` + +--- + +### 3. Capacity Tracker + +Shows: +- weekly deficits +- overtime pressure +- overload trends + +### Cypher Query + +```cypher +MATCH (s:Station)-[r:WEEKLY_STATUS]->(wl:WeeklyLoad) + +RETURN + wl.total_capacity, + wl.total_planned, + wl.deficit +``` + +--- + +### 4. Worker Coverage Matrix + +Shows: +- station staffing +- backup coverage +- single points of failure + +### Cypher Query + +```cypher +MATCH (w:Worker)-[:BACKUP_FOR]->(s:Station) + +RETURN + s.station_name, + collect(w.worker_name) +``` + +--- + +# Final Notes + +One important design decision in my implementation was keeping operational metrics directly on graph relationships instead of introducing unnecessary intermediate nodes. + +This keeps: +- Cypher queries shorter +- dashboard aggregation faster +- bottleneck analysis easier to maintain + +The graph structure is designed specifically for operational factory analytics rather than generic entity storage. diff --git a/submissions/praveen-singh/level5/schema.md b/submissions/praveen-singh/level5/schema.md new file mode 100644 index 000000000..bb70a39b9 --- /dev/null +++ b/submissions/praveen-singh/level5/schema.md @@ -0,0 +1,198 @@ +# Factory Production Knowledge Graph Schema + +```mermaid +graph TD + + PROJECT[Project] + PRODUCT[Product] + STATION[Station] + WORKER[Worker] + WEEK[Week] + SKILL[Skill] + JOBTYPE[JobType] + WEEKLYLOAD[WeeklyLoad] + + PROJECT -->|BUILDS| PRODUCT + PROJECT -->|RUNS_DURING| WEEK + PROJECT -->|FLOWS_THROUGH| STATION + + PROJECT -->|WORKLOAD| STATION + + WORKER -->|ASSIGNED_TO| STATION + WORKER -->|BACKUP_FOR| STATION + + WORKER -->|HAS_SKILL| SKILL + WORKER -->|HAS_JOB| JOBTYPE + + STATION -->|WEEKLY_STATUS| WEEKLYLOAD +``` + +--- + +# Node Labels + +## Project +Represents customer production projects handled by the factory. + +### Properties +- project_id +- project_number +- project_name + +--- + +## Product +Represents the type of structural product being manufactured. + +### Properties +- product_type +- quantity +- unit +- unit_factor + +--- + +## Station +Represents production stations inside the factory workflow. + +### Properties +- station_code +- station_name + +--- + +## Worker +Represents factory workers and operators. + +### Properties +- worker_id +- worker_name +- hours_per_week +- worker_type + +--- + +## Week +Represents production planning weeks. + +### Properties +- week_id + +--- + +## Skill +Represents certifications and technical worker capabilities. + +### Properties +- skill_name + +--- + +## JobType +Represents worker responsibility category. + +### Properties +- role_name + +--- + +## WeeklyLoad +Represents weekly factory load and capacity conditions. + +### Properties +- total_capacity +- total_planned +- deficit + +--- + +# Relationship Types + +## (:Project)-[:BUILDS]->(:Product) + +Connects projects to the products being manufactured. + +--- + +## (:Project)-[:RUNS_DURING]->(:Week) + +Tracks which production weeks a project is active. + +--- + +## (:Project)-[:FLOWS_THROUGH]->(:Station) + +Represents station routing inside the production process. + +--- + +## (:Project)-[:WORKLOAD]->(:Station) + +### Relationship Properties +- planned_hours +- actual_hours +- completed_units +- week + +Stores operational production metrics directly on relationships. + +--- + +## (:Worker)-[:ASSIGNED_TO]->(:Station) + +Represents primary worker allocation. + +--- + +## (:Worker)-[:BACKUP_FOR]->(:Station) + +### Relationship Properties +- priority +- efficiency + +Represents worker backup coverage capability for staffing gaps. + +--- + +## (:Worker)-[:HAS_SKILL]->(:Skill) + +Tracks technical certifications and qualifications. + +--- + +## (:Worker)-[:HAS_JOB]->(:JobType) + +Represents worker role category inside the factory. + +--- + +## (:Station)-[:WEEKLY_STATUS]->(:WeeklyLoad) + +### Relationship Properties +- capacity +- planned +- deficit + +Tracks overload conditions and weekly station pressure. + +--- + +# Why I Designed the Graph This Way + +The dataset models production flow across multiple factory stations over several weeks. + +Instead of treating the data as isolated tables, I modeled the graph around operational movement through the factory. + +This structure makes it easier to: + +- identify overloaded stations +- trace which projects created bottlenecks +- analyze production variance +- check backup worker coverage +- monitor weekly capacity pressure + +I separated workers, skills, and station assignments because staffing flexibility is a critical operational problem in manufacturing systems. + +One design decision I made was storing production variance directly on relationships instead of creating separate variance nodes. This simplifies Cypher aggregation queries and improves dashboard performance for bottleneck analysis. + +The schema is also designed to support future hybrid graph + vector search workflows in Level 6. diff --git a/submissions/praveen-singh/level6/.env.example b/submissions/praveen-singh/level6/.env.example new file mode 100644 index 000000000..b03df95e1 --- /dev/null +++ b/submissions/praveen-singh/level6/.env.example @@ -0,0 +1,6 @@ +NEO4J_URI=abc +NEO4J_USER=abc +NEO4J_PASSWORD=abc +NEO4J_DATABASE=abc +AURA_INSTANCEID=abc +AURA_INSTANCENAME=abc diff --git a/submissions/praveen-singh/level6/.gitignore b/submissions/praveen-singh/level6/.gitignore new file mode 100644 index 000000000..63324238d --- /dev/null +++ b/submissions/praveen-singh/level6/.gitignore @@ -0,0 +1,5 @@ +.venv +.env +pycache/ +*.pyc +.streamlit/secrets.toml \ No newline at end of file diff --git a/submissions/praveen-singh/level6/DASHBOARD_URL.txt b/submissions/praveen-singh/level6/DASHBOARD_URL.txt new file mode 100644 index 000000000..538886139 --- /dev/null +++ b/submissions/praveen-singh/level6/DASHBOARD_URL.txt @@ -0,0 +1 @@ +https://lpi-developer-kit-w2jx5vmuqqu4vkzhehdqsp.streamlit.app diff --git a/submissions/praveen-singh/level6/README.md b/submissions/praveen-singh/level6/README.md new file mode 100644 index 000000000..3fcd5c602 --- /dev/null +++ b/submissions/praveen-singh/level6/README.md @@ -0,0 +1,25 @@ +# Factory Graph Dashboard + +Neo4j + Streamlit dashboard for factory production analysis. + +## Features + +- Project Overview +- Station Load Analysis +- Capacity Tracking +- Worker Coverage Analysis +- Self-Test Validation + +## Tech Stack + +- Streamlit +- Neo4j AuraDB +- Pandas +- Plotly + +## Run Locally + +```bash +pip install -r requirements.txt +python seed_graph.py +streamlit run app.py \ No newline at end of file diff --git a/submissions/praveen-singh/level6/app.py b/submissions/praveen-singh/level6/app.py new file mode 100644 index 000000000..5ecf6c718 --- /dev/null +++ b/submissions/praveen-singh/level6/app.py @@ -0,0 +1,279 @@ +import streamlit as st +import pandas as pd +import plotly.express as px +from neo4j import GraphDatabase +from dotenv import load_dotenv +import os + +# ========================= +# PAGE CONFIG +# ========================= + +st.set_page_config( + page_title="Factory Graph Dashboard", + layout="wide" +) + +# ========================= +# LOAD ENV +# ========================= + +load_dotenv() + +URI = os.getenv("NEO4J_URI") +USER = os.getenv("NEO4J_USER") +PASSWORD = os.getenv("NEO4J_PASSWORD") + +driver = GraphDatabase.driver( + URI, + auth=(USER, PASSWORD) +) + +# ========================= +# QUERY FUNCTION +# ========================= + +def run_query(query): + + with driver.session() as session: + result = session.run(query) + + return pd.DataFrame( + [r.data() for r in result] + ) + +# ========================= +# SIDEBAR +# ========================= + +st.sidebar.title("Factory Dashboard") + +page = st.sidebar.radio( + "Navigation", + [ + "Project Overview", + "Station Load", + "Capacity Tracker", + "Worker Coverage", + "Self-Test" + ] +) + +# ========================= +# PROJECT OVERVIEW +# ========================= + +if page == "Project Overview": + + st.title("Project Overview") + + query = """ + MATCH (p:Project) + RETURN + p.project_id AS project_id, + p.project_name AS project_name, + p.project_number AS project_number + """ + + df = run_query(query) + + st.metric("Total Projects", len(df)) + + st.dataframe(df, use_container_width=True) + +# ========================= +# STATION LOAD +# ========================= + +elif page == "Station Load": + + st.title("Station Load") + + query = """ + MATCH (p:Project)-[r:PROCESSED_AT]->(s:Station) + + RETURN + s.station_name AS station, + SUM(r.planned_hours) AS planned_hours, + SUM(r.actual_hours) AS actual_hours + """ + + df = run_query(query) + + df["overload"] = ( + df["actual_hours"] > df["planned_hours"] + ) + + fig = px.bar( + df, + x="station", + y=["planned_hours", "actual_hours"], + barmode="group", + title="Station Workload" + ) + + st.plotly_chart(fig, use_container_width=True) + + st.dataframe(df, use_container_width=True) + +# ========================= +# CAPACITY TRACKER +# ========================= + +elif page == "Capacity Tracker": + + st.title("Capacity Tracker") + + query = """ + MATCH (c:CapacityWeek) + + RETURN + c.week AS week, + c.total_capacity AS total_capacity, + c.total_planned AS total_planned, + c.deficit AS deficit + ORDER BY week + """ + + df = run_query(query) + + fig = px.line( + df, + x="week", + y=["total_capacity", "total_planned"], + title="Capacity vs Planned" + ) + + st.plotly_chart(fig, use_container_width=True) + + st.subheader("Deficit Weeks") + + deficit_df = df[df["deficit"] > 0] + + st.dataframe( + deficit_df, + use_container_width=True + ) + +# ========================= +# WORKER COVERAGE +# ========================= + +elif page == "Worker Coverage": + + st.title("Worker Coverage") + + query = """ + MATCH (w:Worker)-[:PRIMARY_AT]->(s:Station) + + RETURN + w.worker_name AS worker, + s.station_code AS station, + w.worker_type AS type, + w.hours_per_week AS hours + """ + + df = run_query(query) + + st.dataframe(df, use_container_width=True) + + station_counts = ( + df.groupby("station") + .size() + .reset_index(name="worker_count") + ) + + st.subheader("Single Point of Failure Stations") + + spof = station_counts[ + (station_counts["worker_count"] <= 1) + & (station_counts["station"] != "all") +] + + st.dataframe(spof, use_container_width=True) + +# ========================= +# SELF TEST +# ========================= + +elif page == "Self-Test": + + st.title("Self-Test") + + checks = [] + + # Check 1 + q1 = run_query(""" + MATCH (p:Project) + RETURN count(p) AS count + """) + + checks.append( + ("Projects Loaded", q1["count"][0] > 0) + ) + + # Check 2 + q2 = run_query(""" + MATCH (s:Station) + RETURN count(s) AS count + """) + + checks.append( + ("Stations Loaded", q2["count"][0] > 0) + ) + + # Check 3 + q3 = run_query(""" + MATCH (w:Worker) + RETURN count(w) AS count + """) + + checks.append( + ("Workers Loaded", q3["count"][0] > 0) + ) + + # Check 4 + q4 = run_query(""" + MATCH ()-[r:PROCESSED_AT]->() + RETURN count(r) AS count + """) + + checks.append( + ("Relationships Created", q4["count"][0] > 0) + ) + + # Check 5 + q5 = run_query(""" + MATCH (c:CapacityWeek) + RETURN count(c) AS count + """) + + checks.append( + ("Capacity Data Loaded", q5["count"][0] > 0) + ) + + # Check 6 + checks.append( + ("Neo4j Connection", True) + ) + + passed = 0 + + for name, status in checks: + + if status: + st.success(f"PASS: {name}") + passed += 1 + else: + st.error(f"FAIL: {name}") + + st.metric( + "Self-Test Score", + f"{passed}/6" + ) + +# ========================= +# CLOSE DRIVER +# ========================= + +driver.close() \ No newline at end of file diff --git a/submissions/praveen-singh/level6/data/factory_capacity.csv b/submissions/praveen-singh/level6/data/factory_capacity.csv new file mode 100644 index 000000000..795ff52f0 --- /dev/null +++ b/submissions/praveen-singh/level6/data/factory_capacity.csv @@ -0,0 +1,9 @@ +week,own_staff_count,hired_staff_count,own_hours,hired_hours,overtime_hours,total_capacity,total_planned,deficit +w1,10,2,400,80,0,480,612,-132 +w2,10,2,400,80,40,520,645,-125 +w3,10,2,400,80,0,480,398,82 +w4,10,2,400,80,20,500,550,-50 +w5,10,2,400,80,30,510,480,30 +w6,9,2,360,80,0,440,520,-80 +w7,10,2,400,80,40,520,600,-80 +w8,10,2,400,80,20,500,470,30 \ No newline at end of file diff --git a/submissions/praveen-singh/level6/data/factory_production.csv b/submissions/praveen-singh/level6/data/factory_production.csv new file mode 100644 index 000000000..ca6ce43e1 --- /dev/null +++ b/submissions/praveen-singh/level6/data/factory_production.csv @@ -0,0 +1,69 @@ +project_id,project_number,project_name,product_type,unit,quantity,unit_factor,station_code,station_name,etapp,bop,week,planned_hours,actual_hours,completed_units +P01,4501,Stålverket Borås,IQB,meter,600,1.77,011,FS IQB,ET1,BOP1,w1,48.0,45.2,28 +P01,4501,Stålverket Borås,IQB,meter,600,1.77,012,Förmontering IQB,ET1,BOP1,w1,32.0,35.5,25 +P01,4501,Stålverket Borås,IQB,meter,600,1.77,013,Montering IQB,ET1,BOP1,w1,28.0,26.0,22 +P01,4501,Stålverket Borås,IQB,meter,600,1.77,014,Svets o montage IQB,ET1,BOP1,w1,35.0,38.2,20 +P01,4501,Stålverket Borås,SB,styck,40,4.0,018,SB B/F-hall,ET1,BOP1,w1,16.0,14.5,4 +P01,4501,Stålverket Borås,SP,styck,180,2.0,019,SP B/F-hall,ET1,BOP1,w1,12.0,13.0,7 +P01,4501,Stålverket Borås,IQB,meter,600,1.77,011,FS IQB,ET1,BOP1,w2,48.0,50.0,32 +P01,4501,Stålverket Borås,IQB,meter,600,1.77,012,Förmontering IQB,ET1,BOP1,w2,32.0,30.0,28 +P01,4501,Stålverket Borås,IQP,styck,90,2.80,015,Montering IQP,ET1,BOP2,w2,25.0,28.0,9 +P01,4501,Stålverket Borås,SR,styck,8,45.0,021,SR B/F-hall,ET1,BOP2,w2,40.0,42.0,1 +P02,4502,Kontorshus Mölndal,IQB,meter,350,1.50,011,FS IQB,ET1,BOP1,w1,30.0,28.0,20 +P02,4502,Kontorshus Mölndal,IQB,meter,350,1.50,012,Förmontering IQB,ET1,BOP1,w1,22.0,24.5,18 +P02,4502,Kontorshus Mölndal,IQB,meter,350,1.50,013,Montering IQB,ET1,BOP1,w1,18.0,17.0,16 +P02,4502,Kontorshus Mölndal,IQP,styck,70,2.70,015,Montering IQP,ET1,BOP1,w1,19.0,21.0,7 +P02,4502,Kontorshus Mölndal,SD,styck,30,3.00,018,SB B/F-hall,ET1,BOP1,w1,9.0,8.5,3 +P02,4502,Kontorshus Mölndal,IQB,meter,350,1.50,011,FS IQB,ET1,BOP1,w2,30.0,32.0,24 +P02,4502,Kontorshus Mölndal,IQB,meter,350,1.50,014,Svets o montage IQB,ET1,BOP1,w2,25.0,23.0,20 +P02,4502,Kontorshus Mölndal,SP,styck,120,1.75,019,SP B/F-hall,ET1,BOP2,w2,14.0,15.5,8 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,011,FS IQB,ET1,BOP1,w1,72.0,70.0,40 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,012,Förmontering IQB,ET1,BOP1,w1,48.0,52.0,35 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,013,Montering IQB,ET1,BOP1,w1,38.0,36.5,30 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,014,Svets o montage IQB,ET1,BOP1,w1,42.0,48.0,28 +P03,4503,Lagerhall Jönköping,SB,styck,60,6.00,018,SB B/F-hall,ET1,BOP1,w1,36.0,38.0,6 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,011,FS IQB,ET1,BOP1,w2,72.0,75.0,45 +P03,4503,Lagerhall Jönköping,IQP,styck,110,2.90,015,Montering IQP,ET1,BOP2,w2,32.0,30.0,11 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,016,Gjutning,ET1,BOP2,w2,28.0,35.0,8 +P03,4503,Lagerhall Jönköping,IQB,meter,900,1.89,017,Målning,ET1,BOP2,w3,24.0,22.0,20 +P04,4504,Parkering Helsingborg,IQB,meter,450,1.65,011,FS IQB,ET1,BOP1,w1,38.0,36.0,24 +P04,4504,Parkering Helsingborg,IQB,meter,450,1.65,012,Förmontering IQB,ET1,BOP1,w1,25.0,27.0,20 +P04,4504,Parkering Helsingborg,IQB,meter,450,1.65,013,Montering IQB,ET1,BOP1,w1,20.0,19.0,18 +P04,4504,Parkering Helsingborg,IQP,styck,55,2.85,015,Montering IQP,ET1,BOP1,w1,16.0,18.0,6 +P04,4504,Parkering Helsingborg,SB,styck,25,7.50,018,SB B/F-hall,ET1,BOP1,w1,19.0,22.0,3 +P04,4504,Parkering Helsingborg,IQB,meter,450,1.65,011,FS IQB,ET1,BOP1,w2,38.0,40.0,28 +P04,4504,Parkering Helsingborg,SP,styck,100,2.00,019,SP B/F-hall,ET1,BOP2,w2,12.0,11.0,6 +P04,4504,Parkering Helsingborg,SR,styck,12,120.0,021,SR B/F-hall,ET1,BOP2,w2,60.0,65.0,1 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,011,FS IQB,ET2,BOP3,w1,95.0,90.0,50 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,012,Förmontering IQB,ET2,BOP3,w1,65.0,68.0,42 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,013,Montering IQB,ET2,BOP3,w1,50.0,48.0,38 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,014,Svets o montage IQB,ET2,BOP3,w1,58.0,62.0,35 +P05,4505,Sjukhus Linköping ET2,IQP,styck,150,2.88,015,Montering IQP,ET2,BOP3,w1,30.0,33.0,10 +P05,4505,Sjukhus Linköping ET2,SB,styck,50,5.00,018,SB B/F-hall,ET2,BOP3,w1,25.0,28.0,5 +P05,4505,Sjukhus Linköping ET2,SD,styck,45,2.75,018,SB B/F-hall,ET2,BOP3,w1,12.0,11.5,4 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,011,FS IQB,ET2,BOP3,w2,95.0,98.0,55 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,016,Gjutning,ET2,BOP3,w2,35.0,40.0,12 +P05,4505,Sjukhus Linköping ET2,IQB,meter,1200,1.85,017,Målning,ET2,BOP3,w2,28.0,26.0,25 +P05,4505,Sjukhus Linköping ET2,SR,styck,20,274.0,021,SR B/F-hall,ET2,BOP3,w3,120.0,115.0,2 +P06,4506,Skola Uppsala,IQB,meter,500,1.60,011,FS IQB,ET1,BOP1,w2,40.0,38.0,26 +P06,4506,Skola Uppsala,IQB,meter,500,1.60,012,Förmontering IQB,ET1,BOP1,w2,28.0,30.0,22 +P06,4506,Skola Uppsala,IQB,meter,500,1.60,013,Montering IQB,ET1,BOP1,w2,22.0,20.0,18 +P06,4506,Skola Uppsala,IQP,styck,80,2.75,015,Montering IQP,ET1,BOP1,w2,22.0,24.0,8 +P06,4506,Skola Uppsala,SB,styck,35,4.50,018,SB B/F-hall,ET1,BOP1,w2,16.0,18.0,4 +P06,4506,Skola Uppsala,SP,styck,140,1.50,019,SP B/F-hall,ET1,BOP2,w3,14.0,12.0,10 +P07,4507,Idrottshall Västerås,HSQ,meter,400,2.05,011,FS IQB,ET1,BOP1,w1,45.0,42.0,22 +P07,4507,Idrottshall Västerås,HSQ,meter,400,2.05,012,Förmontering IQB,ET1,BOP1,w1,30.0,33.0,18 +P07,4507,Idrottshall Västerås,HSQ,meter,400,2.05,014,Svets o montage IQB,ET1,BOP1,w1,35.0,32.0,16 +P07,4507,Idrottshall Västerås,SB,styck,45,3.50,018,SB B/F-hall,ET1,BOP1,w1,16.0,18.0,5 +P07,4507,Idrottshall Västerås,HSQ,meter,400,2.05,011,FS IQB,ET1,BOP1,w2,45.0,48.0,26 +P07,4507,Idrottshall Västerås,HSQ,meter,400,2.05,016,Gjutning,ET1,BOP2,w2,20.0,22.0,5 +P07,4507,Idrottshall Västerås,HSQ,meter,400,2.05,017,Målning,ET1,BOP2,w3,18.0,16.0,15 +P08,4508,Bro E6 Halmstad,IQB,meter,800,1.80,011,FS IQB,ET1,BOP1,w1,65.0,62.0,36 +P08,4508,Bro E6 Halmstad,IQB,meter,800,1.80,012,Förmontering IQB,ET1,BOP1,w1,42.0,45.0,30 +P08,4508,Bro E6 Halmstad,IQB,meter,800,1.80,013,Montering IQB,ET1,BOP1,w1,35.0,38.0,25 +P08,4508,Bro E6 Halmstad,IQB,meter,800,1.80,014,Svets o montage IQB,ET1,BOP1,w1,40.0,44.0,22 +P08,4508,Bro E6 Halmstad,SP,styck,200,2.50,019,SP B/F-hall,ET1,BOP1,w1,20.0,18.0,8 +P08,4508,Bro E6 Halmstad,IQB,meter,800,1.80,011,FS IQB,ET1,BOP1,w2,65.0,68.0,42 +P08,4508,Bro E6 Halmstad,IQP,styck,95,2.93,015,Montering IQP,ET1,BOP2,w2,28.0,30.0,10 +P08,4508,Bro E6 Halmstad,IQB,meter,800,1.80,016,Gjutning,ET1,BOP2,w3,22.0,25.0,8 +P08,4508,Bro E6 Halmstad,SR,styck,15,180.0,021,SR B/F-hall,ET1,BOP2,w3,90.0,85.0,2 \ No newline at end of file diff --git a/submissions/praveen-singh/level6/data/factory_workers.csv b/submissions/praveen-singh/level6/data/factory_workers.csv new file mode 100644 index 000000000..3110285cc --- /dev/null +++ b/submissions/praveen-singh/level6/data/factory_workers.csv @@ -0,0 +1,15 @@ +worker_id,name,role,primary_station,can_cover_stations,certifications,hours_per_week,type +W01,Erik Lindberg,Operator,011,"011,012","MIG/MAG,TIG,ISO 9606",40,permanent +W02,Anna Berg,Operator,011,"011,014","MIG/MAG,TIG",40,permanent +W03,Lars Jensen,Operator,012,"012,013","Surface treatment,CE marking",40,permanent +W04,Maria Stone,Operator,013,"013","Blasting,Surface protection",40,permanent +W05,Johan Peters,Operator,014,"014,015","Hydraulics,Mechanics,Crane",40,permanent +W06,Karen Nilsen,Inspector,015,"015","SIS,SS-EN 1090,NDT",40,permanent +W07,Per Hansen,Operator,016,"016,017","Casting,Formwork",40,permanent +W08,Sofia Arden,Operator,017,"017","Surface treatment,Spray painting",40,permanent +W09,Magnus Stone,Operator,018,"018,019","Sheet metal,Assembly",40,permanent +W10,Elin Frank,Operator,019,"019,018","Assembly,Welding",32,permanent +W11,Victor Elm,Foreman,all,"011,012,013,014,015,016,017,018,019,021","Leadership,CE,ISO 9001",45,permanent +W12,Lena Dale,Quality Manager,015,"015","ISO 9001,SS-EN 1090,Audit",40,permanent +W13,Ahmed Hassan,Operator,011,"011","MIG/MAG",40,hired +W14,Petra Steen,Operator,012,"012,013","Surface treatment",40,hired \ No newline at end of file diff --git a/submissions/praveen-singh/level6/requirements.txt b/submissions/praveen-singh/level6/requirements.txt new file mode 100644 index 000000000..e0c6606d6 --- /dev/null +++ b/submissions/praveen-singh/level6/requirements.txt @@ -0,0 +1,5 @@ +streamlit +pandas +plotly +neo4j +python-dotenv \ No newline at end of file diff --git a/submissions/praveen-singh/level6/seed_graph.py b/submissions/praveen-singh/level6/seed_graph.py new file mode 100644 index 000000000..a49accab8 --- /dev/null +++ b/submissions/praveen-singh/level6/seed_graph.py @@ -0,0 +1,196 @@ +import pandas as pd +from neo4j import GraphDatabase +from dotenv import load_dotenv +import os + +# ========================= +# LOAD ENV +# ========================= + +load_dotenv() + +URI = os.getenv("NEO4J_URI") +USER = os.getenv("NEO4J_USER") +PASSWORD = os.getenv("NEO4J_PASSWORD") + +driver = GraphDatabase.driver( + URI, + auth=(USER, PASSWORD) +) + +# ========================= +# LOAD CSV FILES +# ========================= + +production_df = pd.read_csv("data/factory_production.csv") +workers_df = pd.read_csv("data/factory_workers.csv") +capacity_df = pd.read_csv("data/factory_capacity.csv") + +# ========================= +# SEED GRAPH +# ========================= + +def seed_graph(): + + with driver.session() as session: + + # ===================================== + # PRODUCTION DATA + # ===================================== + + for _, row in production_df.iterrows(): + + query = """ + MERGE (p:Project { + project_id: $project_id + }) + + SET + p.project_name = $project_name, + p.project_number = $project_number + + MERGE (prod:Product { + product_type: $product_type + }) + + SET + prod.unit = $unit, + prod.quantity = $quantity, + prod.unit_factor = $unit_factor + + MERGE (s:Station { + station_code: $station_code + }) + + SET + s.station_name = $station_name + + MERGE (w:Week { + week: $week + }) + + MERGE (p)-[:PRODUCES]->(prod) + + MERGE (p)-[:RUNS_IN]->(w) + + MERGE (p)-[r:PROCESSED_AT { + week: $week, + station_code: $station_code + }]->(s) + + SET + r.planned_hours = $planned_hours, + r.actual_hours = $actual_hours, + r.completed_units = $completed_units, + r.etapp = $etapp, + r.bop = $bop + """ + + session.run( + query, + { + "project_id": row["project_id"], + "project_name": row["project_name"], + "project_number": row["project_number"], + "product_type": row["product_type"], + "unit": row["unit"], + "quantity": float(row["quantity"]), + "unit_factor": float(row["unit_factor"]), + "station_code": row["station_code"], + "station_name": row["station_name"], + "week": row["week"], + "planned_hours": float(row["planned_hours"]), + "actual_hours": float(row["actual_hours"]), + "completed_units": float(row["completed_units"]), + "etapp": row["etapp"], + "bop": row["bop"], + } + ) + + # ===================================== + # WORKER DATA + # ===================================== + + for _, row in workers_df.iterrows(): + + query = """ + MERGE (w:Worker { + worker_id: $worker_id + }) + + SET + w.worker_name = $worker_name, + w.worker_type = $worker_type, + w.hours_per_week = $hours_per_week + + MERGE (s:Station { + station_code: $primary_station + }) + + MERGE (w)-[:PRIMARY_AT]->(s) + + MERGE (r:Role { + role_name: $role + }) + + MERGE (w)-[:HAS_ROLE]->(r) + """ + + session.run( + query, + { + "worker_id": row["worker_id"], + "worker_name": row["name"], + "worker_type": row["type"], + "hours_per_week": float(row["hours_per_week"]), + "primary_station": row["primary_station"], + "role": row["role"], + } + ) + + # ===================================== + # CAPACITY DATA + # ===================================== + + for _, row in capacity_df.iterrows(): + + query = """ + MERGE (c:CapacityWeek { + week: $week + }) + + SET + c.own_staff_count = $own_staff_count, + c.hired_staff_count = $hired_staff_count, + c.own_hours = $own_hours, + c.hired_hours = $hired_hours, + c.overtime_hours = $overtime_hours, + c.total_capacity = $total_capacity, + c.total_planned = $total_planned, + c.deficit = $deficit + """ + + session.run( + query, + { + "week": row["week"], + "own_staff_count": float(row["own_staff_count"]), + "hired_staff_count": float(row["hired_staff_count"]), + "own_hours": float(row["own_hours"]), + "hired_hours": float(row["hired_hours"]), + "overtime_hours": float(row["overtime_hours"]), + "total_capacity": float(row["total_capacity"]), + "total_planned": float(row["total_planned"]), + "deficit": float(row["deficit"]), + } + ) + + print("Graph seeded successfully!") + +# ========================= +# RUN +# ========================= + +if __name__ == "__main__": + seed_graph() + driver.close() \ No newline at end of file diff --git a/submissions/praveen-singh/level6/test_connection.py b/submissions/praveen-singh/level6/test_connection.py new file mode 100644 index 000000000..e2df890fe --- /dev/null +++ b/submissions/praveen-singh/level6/test_connection.py @@ -0,0 +1,20 @@ +from neo4j import GraphDatabase +from dotenv import load_dotenv +import os + +load_dotenv() + +URI = os.getenv("NEO4J_URI") +USER = os.getenv("NEO4J_USER") +PASSWORD = os.getenv("NEO4J_PASSWORD") + +driver = GraphDatabase.driver( + URI, + auth=(USER, PASSWORD) +) + +with driver.session() as session: + result = session.run("RETURN 'Neo4j Connected!' AS msg") + print(result.single()["msg"]) + +driver.close() \ No newline at end of file