Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 160 additions & 210 deletions submissions/Dia-Vats/level5/answers.md

Large diffs are not rendered by default.

145 changes: 103 additions & 42 deletions submissions/Dia-Vats/level5/schema.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Level 5 — Graph Schema
**Dia Vats**

---

## Schema Diagram

Expand All @@ -9,24 +12,70 @@
## Mermaid Source

```mermaid
flowchart LR

Project -->|HAS_WORKORDER| WorkOrder
WorkOrder -->|AT_STATION| Station
WorkOrder -->|PRODUCES| Product
WorkOrder -->|SCHEDULED_IN| Week

Week -->|HAS_CAPACITY| CapacitySnapshot

Worker -->|ASSIGNED_TO| Station
Worker -->|CAN_COVER| Station
Worker -->|CERTIFIED_IN| Certification

Station -->|REQUIRES| Certification

Station -->|FEEDS_INTO| Station

WorkOrder -->|FOLLOWS| WorkOrder
flowchart TD
Project["**Project**
---
project_id
project_number
project_name
etapp
bop"]

WorkOrder["**WorkOrder**
---
planned_hours
actual_hours
completed_units"]

Station["**Station**
---
station_code
station_name"]

Product["**Product**
---
product_type
unit
unit_factor"]

Week["**Week**
---
week_id"]

Worker["**Worker**
---
worker_id
name
role
hours_per_week
type"]

Certification["**Certification**
---
name"]

CapacitySnapshot["**CapacitySnapshot**
---
own_staff_count
hired_staff_count
own_hours
hired_hours
overtime_hours
total_capacity
total_planned
deficit"]

Project -->|"HAS_WORKORDER"| WorkOrder
WorkOrder -->|"AT_STATION"| Station
WorkOrder -->|"PRODUCES"| Product
WorkOrder -->|"SCHEDULED_IN\n{planned_hours, actual_hours, completed_units}"| Week
Week -->|"HAS_CAPACITY"| CapacitySnapshot
Worker -->|"ASSIGNED_TO"| Station
Worker -->|"CAN_COVER"| Station
Worker -->|"CERTIFIED_IN"| Certification
Station -->|"REQUIRES"| Certification
Station -->|"FEEDS_INTO"| Station
WorkOrder -->|"FOLLOWS"| WorkOrder
```

---
Expand All @@ -35,34 +84,46 @@ flowchart LR

| Relationship | Properties |
|---|---|
| `SCHEDULED_IN` | planned_hours, actual_hours, completed_units |
| `PRODUCES` | quantity, unit_factor |
| `(WorkOrder)-[:SCHEDULED_IN]->(Week)` | `planned_hours`, `actual_hours`, `completed_units` |
| `(Project)-[:PRODUCES]->(Product)` | `quantity`, `unit_factor` *(project-level aggregate)* |

---

## Node Labels
## Node Summary (8 labels)

| # | Label | Key Properties | CSV Source |
|---|-------|---------------|-----------|
| 1 | `Project` | project_id, project_number, project_name, etapp, bop | factory_production.csv |
| 2 | `WorkOrder` | planned_hours, actual_hours, completed_units | factory_production.csv (one node per row) |
| 3 | `Station` | station_code, station_name | factory_production.csv |
| 4 | `Product` | product_type, unit, unit_factor | factory_production.csv |
| 5 | `Week` | week_id | both CSVs |
| 6 | `Worker` | worker_id, name, role, hours_per_week, type | factory_workers.csv |
| 7 | `Certification` | name | factory_workers.csv (split by comma) |
| 8 | `CapacitySnapshot` | own_staff_count, hired_staff_count, own_hours, hired_hours, overtime_hours, total_capacity, total_planned, deficit | factory_capacity.csv |

---

- `Project`
- `WorkOrder`
- `Station`
- `Product`
- `Week`
- `Worker`
- `Certification`
- `CapacitySnapshot`
## Relationship Summary (11 types)

| # | Relationship | Direction | Properties |
|---|-------------|-----------|-----------|
| 1 | `HAS_WORKORDER` | Project → WorkOrder | — |
| 2 | `AT_STATION` | WorkOrder → Station | — |
| 3 | `PRODUCES` | WorkOrder → Product | — |
| 4 | `SCHEDULED_IN` | WorkOrder → Week | `planned_hours`, `actual_hours`, `completed_units` |
| 5 | `HAS_CAPACITY` | Week → CapacitySnapshot | — |
| 6 | `ASSIGNED_TO` | Worker → Station | — |
| 7 | `CAN_COVER` | Worker → Station | — |
| 8 | `CERTIFIED_IN` | Worker → Certification | — |
| 9 | `REQUIRES` | Station → Certification | — |
| 10 | `FEEDS_INTO` | Station → Station | — |
| 11 | `FOLLOWS` | WorkOrder → WorkOrder | — |

---

## Relationship Types

- `HAS_WORKORDER`
- `AT_STATION`
- `PRODUCES`
- `SCHEDULED_IN`
- `FEEDS_INTO`
- `FOLLOWS`
- `ASSIGNED_TO`
- `CAN_COVER`
- `CERTIFIED_IN`
- `REQUIRES`
- `HAS_CAPACITY`
## Design Notes

`FEEDS_INTO` between stations captures the physical production flow (e.g. 011 FS IQB → 012 Förmontering → 013 Montering). This isn't in any CSV directly — it's derived from the station sequence implicit in the data. It lets you query downstream impact from a bottleneck station.

`FOLLOWS` between WorkOrders links the same project-station pair across consecutive weeks, making temporal progression traversable without aggregating by week in every query.
Binary file modified submissions/Dia-Vats/level5/schema.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions submissions/Dia-Vats/level6/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password_here
4 changes: 4 additions & 0 deletions submissions/Dia-Vats/level6/.streamlit/secrets.toml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[NEO4J]
NEO4J_URI = "bolt://localhost:7687"
NEO4J_USER = "neo4j"
NEO4J_PASSWORD = "your_password_here"
1 change: 1 addition & 0 deletions submissions/Dia-Vats/level6/DASHBOARD_URL.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://diavats-l6.streamlit.app
108 changes: 108 additions & 0 deletions submissions/Dia-Vats/level6/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Steel Factory Production Dashboard
**Dia Vats | Level 6 - LifeAtlas LPI Developer Kit**
**Live: https://diavats-l6.streamlit.app**

---

## What I Built

The raw data was 3 CSVs describing a Swedish steel factory - 8 projects, 9 stations, 14 workers, 8 weeks. I turned it into a Neo4j knowledge graph and built a 7-page Streamlit dashboard on top of it.

The point wasn't to build a chart on top of a spreadsheet. The graph models actual operational dependencies - which work orders flow through which stations, which workers can cover which stations if someone's out, and where downstream risk accumulates when a station overruns. The dashboard surfaces that reasoning directly.

I also completed 2 bonus pages (Bonus B — Factory Floor, Bonus C — Forecast).

**Graph stats: 148 nodes, 446 relationships, 9 labels, 12 relationship types.**

---

## Graph Schema

**Nodes:** Project, WorkOrder, Station, Product, Week, Worker, Certification, CapacitySnapshot, Etapp

**Relationships:** HAS_WORKORDER, AT_STATION, PRODUCES, SCHEDULED_IN, HAS_CAPACITY, ASSIGNED_TO, CAN_COVER, CERTIFIED_IN, REQUIRES, FEEDS_INTO, FOLLOWS, IN_ETAPP

A few things worth noting:

- WorkOrder is the core unit - one node per row in the production CSV. It sits between Project and Station and carries planned hours, actual hours, variance %, and bottleneck flag.
- FEEDS_INTO chains stations in physical flow order (011 → 012 → ... → 021). This lets you trace downstream impact from any overrunning station.
- FOLLOWS links the same project-station pair across consecutive weeks so you can traverse time without aggregating in every query.
- Bottleneck rule: `actual_hours > planned_hours × 1.1`
- WorkOrder ID format: `P01_011_w1_IQB`

---

## Dashboard Pages

**Page 1 - Project Overview**
KPI cards, grouped bar chart (planned vs actual per project), variance table with red highlights where variance exceeds 10%.

**Page 2 - Station Load**
Plotly heatmap, stations vs weeks, coloured green/yellow/red by variance. Station 016 shows up clearly as the recurring problem.

**Page 3 - Capacity Tracker**
Weekly capacity vs planned demand. Deficit weeks in red. Calls out that 5 of 8 weeks run over capacity, worst at -132 hours in week 1.

**Page 4 - Worker Coverage**
Shows who covers which station and flags single points of failure. Station 016 is marked CRITICAL — Victor Elm is the only backup for Per Hansen, and 4 projects run through that station.

**Page 5 - Factory Floor (Bonus B)**
Scatter-based floor plan with stations on a physical grid, coloured by load severity. Hover shows active projects and overload %.

**Page 6 - Forecast (Bonus C)**
Linear extrapolation from weeks 1–8 per station, projecting week 9. Shows which stations are trending toward overload.

**Page 7 - Self-Test**
6 automated Neo4j checks, scored out of 20. Runs on every page load.

---

## Project Structure

```
submissions/Dia-Vats/level6/
├── app.py
├── db.py
├── seed_graph.py
├── requirements.txt
├── .env.example
├── DASHBOARD_URL.txt
├── README.md
├── data/
│ ├── factory_production.csv
│ ├── factory_workers.csv
│ └── factory_capacity.csv
├── pages_impl/
│ ├── page1_overview.py
│ ├── page2_station.py
│ ├── page3_capacity.py
│ ├── page4_workers.py
│ ├── page5_floor.py
│ ├── page6_forecast.py
│ └── page7_selftest.py
└── .streamlit/
└── secrets.toml.example
```

---

## Running It

```bash
pip install -r requirements.txt
python seed_graph.py
streamlit run app.py
```

For Streamlit Cloud, add credentials under Settings > Secrets:
```toml
NEO4J_URI = "neo4j+s://your-instance.databases.neo4j.io"
NEO4J_USER = "your-username"
NEO4J_PASSWORD = "your-password"
```

`seed_graph.py` uses MERGE everywhere — safe to re-run without duplicating data.

---

*Made by Dia Vats*
105 changes: 105 additions & 0 deletions submissions/Dia-Vats/level6/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
app.py — Swedish Steel Factory Dashboard
Author: Dia Vats
7-page Streamlit + Neo4j dashboard.
"""
import streamlit as st

st.set_page_config(
page_title="Steel Factory Dashboard",
page_icon="[SF]",
layout="wide",
initial_sidebar_state="expanded",
)

# ── Global styles ─────────────────────────────────────────────────────────────
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');

html, body, [class*="css"] { font-family: 'Inter', sans-serif; }

h1 { font-family: 'Inter', sans-serif; font-weight: 600; color: #1a1a2e; letter-spacing: -0.5px; }
h2 { font-family: 'Inter', sans-serif; font-weight: 500; color: #2d2d44; }
.section-divider { border: none; border-top: 1px solid #e0e0e0; margin: 1rem 0; }

section[data-testid="stSidebar"] {
background: linear-gradient(180deg,#0f1117 0%,#1a1d2e 100%);
}
section[data-testid="stSidebar"] * { color: #e2e8f0 !important; }

.metric-card {
background: linear-gradient(135deg,#1e2235,#252a3f);
border: 1px solid #2d3454;
border-radius: 12px;
padding: 18px 22px;
margin-bottom: 8px;
}
.metric-card .label { color:#94a3b8; font-size:0.78rem; text-transform:uppercase; letter-spacing:1px; }
.metric-card .value { color:#f1f5f9; font-size:2rem; font-weight:700; }

.spof-badge {
color: #E53935; font-weight: 700; font-size: 0.82rem;
border: 1px solid #E53935; border-radius: 4px;
padding: 1px 7px; letter-spacing: 0.5px;
}
.ok-badge {
color: #43A047; font-weight: 700; font-size: 0.82rem;
border: 1px solid #43A047; border-radius: 4px;
padding: 1px 7px; letter-spacing: 0.5px;
}
.footer-bar {
text-align:center; color:#64748b; font-size:0.75rem;
margin-top:40px; padding-top:16px;
border-top:1px solid #1e2235;
}
.page-title {
border-left: 4px solid #4A90D9;
padding-left: 12px;
margin-bottom: 0.25rem;
font-size: 1.6rem;
font-weight: 600;
color: #1a1a2e;
}
</style>
""", unsafe_allow_html=True)

PAGES = [
"Project Overview",
"Station Load",
"Capacity Tracker",
"Worker Coverage",
"Factory Floor",
"Forecast",
"Self-Test",
]

# ── Sidebar navigation ────────────────────────────────────────────────────────
with st.sidebar:
st.image("https://img.icons8.com/fluency/96/factory.png", width=56)
st.markdown("## Steel Factory")
st.markdown("*Neo4j Production Dashboard*")
st.markdown("---")
page = st.radio("Navigate", PAGES, label_visibility="collapsed")
st.markdown("---")
st.caption("Made by **Dia Vats**")

# ── Footer helper ─────────────────────────────────────────────────────────────
def footer():
st.caption("Made by Dia Vats")

# ── Route to page ─────────────────────────────────────────────────────────────
if page == PAGES[0]:
from pages_impl.page1_overview import render; render(); footer()
elif page == PAGES[1]:
from pages_impl.page2_station import render; render(); footer()
elif page == PAGES[2]:
from pages_impl.page3_capacity import render; render(); footer()
elif page == PAGES[3]:
from pages_impl.page4_workers import render; render(); footer()
elif page == PAGES[4]:
from pages_impl.page5_floor import render; render(); footer()
elif page == PAGES[5]:
from pages_impl.page6_forecast import render; render(); footer()
elif page == PAGES[6]:
from pages_impl.page7_selftest import render; render(); footer()
Loading
Loading