Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hannah eval ingress #244

Merged
merged 11 commits into from
Jul 24, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@

.sidenav-content {
width: 50%;
}

mat-tab-body-content {
overflow: hidden;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@

td.mat-cell{
border-bottom-style: none;
}

.instructions {
padding-bottom: 1rem;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<div *ngIf="!!template">
<div class="instructions">
Either
<button mat-raised-button color="primary">
<label for="file">Choose File</label>
<input type="file" id="file" hidden (change)="handleFileInput($event)">
</button>
or fill out the template:
</div>
<table mat-table [dataSource]="dataSource" class="table">
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

Expand All @@ -25,7 +33,7 @@
mat-raised-button
color="primary"
(click)="evaluateClicked()"
[disabled]="parametersForm.invalid"
matStepperNext>Evaluate
[disabled]="parametersForm.invalid">
Evaluate
</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,25 @@ export class TemplateEvaluateFormComponent implements OnInit {
displayedColumns: string[] = ['parameterName', 'parametersValueControl'];
parametersForm: FormGroup = new FormGroup({});
dataSource: {parameterName: string, parametersValueControl: FormControl}[] = [];
fileToUpload: File | null = null;

@Output() parameterFormValuesEvent = new EventEmitter<{[name: string]: string}>();
@Output() ingressFileEvent = new EventEmitter<File>();

constructor() {}

evaluateClicked(): void {
this.parameterFormValuesEvent.emit(this.parametersForm.value);
}

handleFileInput(event: Event) {
const element = event.currentTarget as HTMLInputElement;
let files: FileList | null = element.files;
this.fileToUpload = files?.item(0) ?? null;

if(this.fileToUpload) this.ingressFileEvent.emit(this.fileToUpload);
}

ngOnInit(): void {
if (this.template == undefined) return // We shouldn't trigger this.
if (this.template.parameters == undefined) return // We shouldn't trigger this.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
<ng-template matStepLabel>Fill out template</ng-template>
<app-template-evaluate-form
[template]="template"
(parameterFormValuesEvent)="parameterFormValuesEvent($event)">
(parameterFormValuesEvent)="parameterFormValuesEvent($event)"
(ingressFileEvent)="ingressFileEvent($event)">
</app-template-evaluate-form>
</mat-step>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Component, OnInit, Inject, Input } from '@angular/core';
import { Component, OnInit, Inject, Input, ViewChild } from '@angular/core';
import { Template } from '../types';
import { TemplateEvaluateService } from './template-evaluate.service';
import { TemplateDetailService } from '../template-detail/template-detail.service';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatStepper } from '@angular/material/stepper';

export interface DialogData {
templateId: number;
Expand All @@ -20,6 +21,7 @@ export class TemplateEvaluateComponent implements OnInit {
template: Template | undefined = undefined;
evaluatedGraph?: string;
modelId?: number;
@ViewChild("stepper") stepper?: MatStepper;

constructor(
@Inject(MAT_DIALOG_DATA) public data: DialogData,
Expand All @@ -39,13 +41,25 @@ export class TemplateEvaluateComponent implements OnInit {
});
}

parameterFormValuesEvent(parameterFormValues: {[name: string]: string}): void {
parameterFormValuesEvent(parameterFormValues: {[name: string]: string}): void {
if (this.template == undefined) return;
if (this.modelId == undefined) return;

const evaluateTemplate = this.templateEvaluateService.evaluateTemplate(this.template.id, this.modelId, parameterFormValues);
const evaluateTemplate = this.templateEvaluateService.evaluateTemplateBindings(this.template.id, this.modelId, parameterFormValues);
evaluateTemplate.subscribe((result) => {
this.evaluatedGraph = result
this.evaluatedGraph = result;
this.stepper?.next();
});
}

ingressFileEvent(file: File): void {
if (this.template == undefined) return;
if (this.modelId == undefined) return;

const evaluateTemplate = this.templateEvaluateService.evaluateTemplateIngress(this.template.id, this.modelId, file);
evaluateTemplate.subscribe((result) => {
this.evaluatedGraph = result;
this.stepper?.next();
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export class TemplateEvaluateService {

constructor(private http: HttpClient) { }

evaluateTemplate(templateId: number, modelId: number, parameters: {[name: string]: string}) {
evaluateTemplateBindings(templateId: number, modelId: number, parameters: {[name: string]: string}) {
const bindings = Object.entries(parameters).reduce((acc, [name, value]) => {
return {...acc, [name]: {"@id": value}}
}, {})

return this.http.post(
`http://localhost:5000/templates/${templateId}/evaluate`,
`http://localhost:5000/templates/${templateId}/evaluate/bindings`,
{model_id: modelId, bindings},
{responseType: 'text'}
)
Expand All @@ -36,6 +36,19 @@ export class TemplateEvaluateService {

}

evaluateTemplateIngress(templateId: number, modelId: number, file: File) {
return this.http.post(
`http://localhost:5000/templates/${templateId}/evaluate/ingress?model_id=${modelId}`,
file,
{responseType: 'text'}
)
.pipe(
retry(3), // retry a failed request up to 3 times
catchError(this.handleError) // then handle the error
);

}

private handleError(error: HttpErrorResponse) {
if (error.status === 0) {
// A client-side or network error occurred. Handle it accordingly.
Expand Down
52 changes: 50 additions & 2 deletions buildingmotif/api/views/template.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from io import StringIO
from typing import Dict

import flask
Expand All @@ -9,6 +10,7 @@

from buildingmotif.api.serializers.template import serialize
from buildingmotif.dataclasses import Model, Template
from buildingmotif.ingresses import CSVIngress, TemplateIngress

blueprint = Blueprint("templates", __name__)

Expand Down Expand Up @@ -48,8 +50,54 @@ def get_template(templates_id: int) -> flask.Response:
return jsonify(serialize(template, include_parameters)), status.HTTP_200_OK


@blueprint.route("/<template_id>/evaluate", methods=(["POST"]))
def evaluate(template_id: int) -> flask.Response:
@blueprint.route("/<template_id>/evaluate/ingress", methods=(["POST"]))
def evaluate_ingress(template_id: int) -> flask.Response:
# get template
try:
template = Template.load(template_id)
except NoResultFound:
return {
"message": f"No template with id {template_id}"
}, status.HTTP_404_NOT_FOUND

# get model
model_id = request.args.get("model_id")
if model_id is None:
return {
"message": "must contain query param 'model_id'"
}, status.HTTP_400_BAD_REQUEST
try:
model = Model.load(model_id)
except NoResultFound:
return {"message": f"No model with id {model_id}"}, status.HTTP_404_NOT_FOUND

# get file
raw_data = flask.request.get_data()
if raw_data is None:
return {"message": "no file recieved."}, status.HTTP_404_NOT_FOUND

# evaluate template
try:
data = StringIO(raw_data.decode("utf-8"))
csv_ingress = CSVIngress(data=data)
template_ingress = TemplateIngress(
template.inline_dependencies(), None, csv_ingress
)
graph_or_template = template_ingress.graph(model.name)
except Exception:
return {"message": "Invalid csv."}, status.HTTP_400_BAD_REQUEST

# parse bindings from input JSON
if isinstance(graph_or_template, Template):
graph = graph_or_template.body
else:
graph = graph_or_template

return graph.serialize(format="ttl"), status.HTTP_200_OK


@blueprint.route("/<template_id>/evaluate/bindings", methods=(["POST"]))
def evaluate_bindings(template_id: int) -> flask.Response:
"""evaluate template with giving binding

:param template_id: id of template
Expand Down
3 changes: 3 additions & 0 deletions buildingmotif/ingresses/brick.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def graph(self, ns: Namespace) -> Graph:
:rtype: Graph
"""
g = Graph()
# ensure 'ns' is a Namespace or URI forming won't work
if not isinstance(ns, Namespace):
ns = Namespace(ns)
records = self.upstream.records
assert records is not None
for record in records:
Expand Down
28 changes: 22 additions & 6 deletions buildingmotif/ingresses/csv.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from csv import DictReader
from functools import cached_property
from io import StringIO
from pathlib import Path
from typing import List
from typing import List, Optional, Union

from buildingmotif.ingresses.base import Record, RecordIngressHandler

Expand All @@ -11,16 +12,31 @@ class CSVIngress(RecordIngressHandler):
The type of the record is the name of the CSV file
"""

def __init__(self, filename: Path):
self.filename = filename
def __init__(
self,
filename: Optional[Path] = None,
haneslinger marked this conversation as resolved.
Show resolved Hide resolved
data: Optional[Union[str, StringIO]] = None,
):
if filename is not None and data is not None:
raise ValueError("Both filename and data are defined.")

if filename:
self.dict_reader = DictReader(open(filename))
self.rtype = str(filename)

elif data:
self.dict_reader = DictReader(data, delimiter=",")
self.rtype = "data stream"

else:
raise ValueError("Either filename or data must be defined.")

@cached_property
def records(self) -> List[Record]:
records = []
rdr = DictReader(open(self.filename))
for row in rdr:
for row in self.dict_reader:
rec = Record(
rtype=str(self.filename),
rtype=self.rtype,
fields=row,
)
records.append(rec)
Expand Down
9 changes: 9 additions & 0 deletions buildingmotif/ingresses/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def __init__(
def graph(self, ns: Namespace) -> Graph:
g = Graph()

# ensure 'ns' is a Namespace or URI forming won't work
if not isinstance(ns, Namespace):
ns = Namespace(ns)

records = self.upstream.records
assert records is not None
for rec in records:
Expand Down Expand Up @@ -105,6 +109,10 @@ def __init__(
def graph(self, ns: Namespace) -> Graph:
g = Graph()

# ensure 'ns' is a Namespace or URI forming won't work
if not isinstance(ns, Namespace):
ns = Namespace(ns)

records = self.upstream.records
assert records is not None
for rec in records:
Expand All @@ -119,6 +127,7 @@ def graph(self, ns: Namespace) -> Graph:


def _get_term(field_value: str, ns: Namespace) -> Node:
assert isinstance(ns, Namespace), f"{ns} must be a rdflib.Namespace instance"
try:
uri = URIRef(ns[field_value])
uri.n3() # raises an exception if invalid URI
Expand Down
Loading