|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
from rdflib import Graph, Namespace |
|
|
import os |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="Cars Knowledge Graph Demo", layout="wide") |
|
|
|
|
|
|
|
|
@st.cache_resource |
|
|
def load_graph(): |
|
|
g = Graph() |
|
|
graph_path = "cars_knowledge_graph.ttl" |
|
|
if os.path.exists(graph_path): |
|
|
g.parse(graph_path, format="turtle") |
|
|
return g |
|
|
|
|
|
try: |
|
|
g = load_graph() |
|
|
except Exception as e: |
|
|
st.error(f"Failed to load graph: {e}") |
|
|
st.stop() |
|
|
|
|
|
if len(g) == 0: |
|
|
st.warning("Graph is empty or not found. Please run 'src/convert_data.py' first.") |
|
|
st.stop() |
|
|
|
|
|
|
|
|
EX = Namespace("http://example.org/cars/") |
|
|
|
|
|
|
|
|
st.sidebar.header("Filter Cars") |
|
|
|
|
|
|
|
|
manu_query = """ |
|
|
PREFIX ex: <http://example.org/cars/> |
|
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
|
|
SELECT DISTINCT ?name WHERE { |
|
|
?m a ex:Manufacturer ; rdfs:label ?name . |
|
|
} ORDER BY ?name |
|
|
""" |
|
|
manufacturers = ["All"] + [str(row.name) for row in g.query(manu_query)] |
|
|
selected_manu = st.sidebar.selectbox("Manufacturer", manufacturers) |
|
|
|
|
|
|
|
|
price_query = """ |
|
|
PREFIX ex: <http://example.org/cars/> |
|
|
SELECT (MIN(?p) as ?min) (MAX(?p) as ?max) WHERE { ?s ex:hasPriceValue ?p } |
|
|
""" |
|
|
price_res = list(g.query(price_query))[0] |
|
|
min_price, max_price = float(price_res.min), float(price_res.max) |
|
|
selected_price = st.sidebar.slider("Max Price (USD)", min_price, max_price, max_price) |
|
|
|
|
|
|
|
|
hp_query = """ |
|
|
PREFIX ex: <http://example.org/cars/> |
|
|
SELECT (MIN(?hp) as ?min) (MAX(?hp) as ?max) WHERE { ?s ex:hasHorsePowerValue ?hp } |
|
|
""" |
|
|
hp_res = list(g.query(hp_query))[0] |
|
|
min_hp, max_hp = int(hp_res.min), int(hp_res.max) |
|
|
selected_hp = st.sidebar.slider("Min Horsepower", min_hp, max_hp, min_hp) |
|
|
|
|
|
|
|
|
st.title("π Cars Knowledge Graph Explorer") |
|
|
st.markdown("This application queries the RDF Knowledge Graph directly using **SPARQL**.") |
|
|
|
|
|
|
|
|
sparql_query = f""" |
|
|
PREFIX ex: <http://example.org/cars/> |
|
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
|
|
|
|
|
SELECT ?carName ?manuName ?price ?hp ?topSpeed ?seats |
|
|
WHERE {{ |
|
|
?car a ex:Car ; |
|
|
rdfs:label ?carName ; |
|
|
ex:hasManufacturer ?manu ; |
|
|
ex:hasPriceValue ?price ; |
|
|
ex:hasHorsePowerValue ?hp ; |
|
|
ex:hasTopSpeedKMH ?topSpeed ; |
|
|
ex:hasSeatCount ?seats . |
|
|
|
|
|
?manu rdfs:label ?manuName . |
|
|
|
|
|
FILTER (?price <= {selected_price}) |
|
|
FILTER (?hp >= {selected_hp}) |
|
|
{f'FILTER (?manuName = "{selected_manu}")' if selected_manu != "All" else ""} |
|
|
}} |
|
|
ORDER BY DESC(?price) |
|
|
LIMIT 100 |
|
|
""" |
|
|
|
|
|
|
|
|
results = g.query(sparql_query) |
|
|
|
|
|
|
|
|
data = [] |
|
|
for row in results: |
|
|
data.append({ |
|
|
"Car Model": str(row.carName), |
|
|
"Manufacturer": str(row.manuName), |
|
|
"Price ($)": f"${float(row.price):,.2f}", |
|
|
"Horsepower": int(row.hp), |
|
|
"Top Speed (km/h)": int(row.topSpeed), |
|
|
"Seats": int(row.seats) |
|
|
}) |
|
|
|
|
|
df = pd.DataFrame(data) |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
col1.metric("Total Cars Found", len(df)) |
|
|
col2.metric("Graph Triples", len(g)) |
|
|
col3.metric("Selected Manufacturer", selected_manu) |
|
|
|
|
|
if not df.empty: |
|
|
st.dataframe(df, use_container_width=True) |
|
|
else: |
|
|
st.info("No cars match your filters.") |
|
|
|
|
|
|
|
|
with st.expander("Run Custom SPARQL Query"): |
|
|
custom_query = st.text_area("SPARQL Query", """ |
|
|
PREFIX ex: <http://example.org/cars/> |
|
|
SELECT ?name ?price WHERE { |
|
|
?c ex:hasPriceValue ?price ; |
|
|
rdfs:label ?name . |
|
|
} LIMIT 5 |
|
|
""") |
|
|
if st.button("Run Query"): |
|
|
try: |
|
|
raw_res = g.query(custom_query) |
|
|
st.write(list(raw_res)) |
|
|
except Exception as e: |
|
|
st.error(f"Error: {e}") |
|
|
|