Add chips to improve precision

This commit is contained in:
Sijmen 2018-10-23 13:16:23 +02:00
parent 3cd7b78d81
commit 9839634e1b
6 changed files with 84 additions and 25 deletions

View File

@ -9,7 +9,6 @@ import click
from .question import Question from .question import Question
from .question_search import QuestionSearch from .question_search import QuestionSearch
app = Flask(__name__) app = Flask(__name__)
connections.create_connection(hosts=["localhost"]) connections.create_connection(hosts=["localhost"])
@ -39,6 +38,15 @@ def index():
"category": { "category": {
"terms": {"field": "category"}, "terms": {"field": "category"},
}, },
"suggestions": {
"significant_terms": {
"field": "body",
"mutual_information": {
"include_negatives": True,
},
"size": 40,
},
}
}, },
}) })
@ -51,6 +59,9 @@ def index():
for bucket in response.aggregations.category.buckets for bucket in response.aggregations.category.buckets
] ]
suggestions = [{"key": bucket.key, "count": bucket.doc_count}
for bucket in response.aggregations.suggestions.buckets]
date_facets = [] date_facets = []
results = [] results = []
@ -59,14 +70,17 @@ def index():
url = Question.url(hit) url = Question.url(hit)
results.append({ results.append({
"id": hit.meta.id, "score": hit.meta.score, "id": hit.meta.id, "score": hit.meta.score, "title": hit.title,
"title": hit.title, "body": summary, "body": summary, "category": hit.category, "date": hit.date,
"category": hit.category, "date": hit.date,
"url": url, "url": url,
}) })
return jsonify( return jsonify(
facets={"months": date_facets, "categories": category_facets}, facets={
"months": date_facets,
"categories": category_facets,
},
suggestions=suggestions,
results=results, results=results,
hits=round_sigfig(response.hits.total, 4), hits=round_sigfig(response.hits.total, 4),
took=response.took / 1000, took=response.took / 1000,

View File

@ -3,7 +3,10 @@ from elasticsearch_dsl import Document, Date, Keyword, Text
class Question(Document): class Question(Document):
title = Text(analyzer="snowball") title = Text(analyzer="snowball")
body = Text(analyzer="snowball") body = Text(
analyzer="snowball",
fielddata=True,
)
category = Keyword() category = Keyword()
date = Date() date = Date()

View File

@ -1,5 +1,12 @@
<template> <template>
<div class="body"> <div class="body">
<div class="suggestions">
<div v-for="suggestion in json.suggestions" class="suggestion"
@click="appendToQuery(suggestion.key)">
{{suggestion.key}}
</div>
</div>
<div class="result-count"> <div class="result-count">
Ongeveer {{hits}} resultaten ({{responseTime.toFixed(2)}} seconden) Ongeveer {{hits}} resultaten ({{responseTime.toFixed(2)}} seconden)
</div> </div>
@ -30,6 +37,8 @@
</a> </a>
</div> </div>
</div> </div>
<!--pre>{{JSON.stringify(json, null, 2)}}</pre-->
</div> </div>
</div> </div>
</template> </template>
@ -40,19 +49,25 @@ import {Component, Prop, Watch} from "vue-property-decorator";
@Component @Component
export default class ResultBody extends Vue { export default class ResultBody extends Vue {
@Prop() query; @Prop() value;
json = null;
activeCategories = []; activeCategories = [];
hits = 0; hits = 0;
results = []; results = [];
responseTime = 0; responseTime = 0;
facets = {}; facets = {};
@Watch("query") @Watch("value")
async onQueryChanged(value) { async onQueryChanged(value) {
await this.search(); await this.search();
} }
@Watch("$route")
async onRouteChanged() {
this.$emit("input", this.$route.query.q);
}
async toggleCategory(category) { async toggleCategory(category) {
const name = category.category; const name = category.category;
@ -67,7 +82,7 @@ export default class ResultBody extends Vue {
} }
async search() { async search() {
const query = encodeURIComponent(this.query); const query = encodeURIComponent(this.value);
let queryString = `?q=${query}`; let queryString = `?q=${query}`;
if (this.activeCategories.length > 0) { if (this.activeCategories.length > 0) {
@ -79,12 +94,16 @@ export default class ResultBody extends Vue {
const url = `/api/${queryString}`; const url = `/api/${queryString}`;
let response = await fetch(url); let response = await fetch(url);
let json = await response.json(); this.json = await response.json();
this.results = json.results; this.results = this.json.results;
this.hits = json.hits; this.hits = this.json.hits;
this.responseTime = json.took; this.responseTime = this.json.took;
this.facets = json.facets; this.facets = this.json.facets;
}
appendToQuery(value) {
this.$emit("input", `${this.value} AND ${value}`)
} }
} }
</script> </script>
@ -171,4 +190,25 @@ export default class ResultBody extends Vue {
.category__count { .category__count {
text-align: right; text-align: right;
} }
.suggestions {
display: flex;
flex-flow: row wrap;
max-width: 1000px;
margin: 12px 0;
height: 70px;
overflow-y: hidden;
}
.suggestion {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 12px;
background-color: #f8f8f8;
padding: 7px 16px;
margin: 2px;
cursor: pointer;
}
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<top-bar v-model="query" /> <top-bar v-model="query" />
<result-body :query="query" /> <result-body v-model="query" />
</div> </div>
</template> </template>

View File

@ -37,8 +37,10 @@ import {Component, Prop} from "vue-property-decorator";
export default class TopBar extends Vue { export default class TopBar extends Vue {
@Prop() value = ""; @Prop() value = "";
search(event) { search (event) {
this.$emit("input", event.target.value) if (event.target.value.trim().length > 0) {
this.$emit("input", event.target.value)
}
} }
} }
</script> </script>

View File

@ -1763,9 +1763,9 @@ copy-descriptor@^0.1.0:
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-webpack-plugin@^4.0.1: copy-webpack-plugin@^4.0.1:
version "4.5.3" version "4.5.4"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.3.tgz#14a224d205e46f7a79f7956028e1da6df2225ff2" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.4.tgz#f2b2782b3cd5225535c3dc166a80067e7d940f27"
integrity sha512-VKCiNXQcc8zyznaepXfKpCH2cZD+/j3T3B+gsFY97P7qMlEsj34wr/sI9OCG7QPUUh7gAHVx3q8Q1rdQIDM4bA== integrity sha512-0lstlEyj74OAtYMrDxlNZsU7cwFijAI3Ofz2fD6Mpo9r4xCv4yegfa3uHIKvZY1NSuOtE9nvG6TAhJ+uz9gDaQ==
dependencies: dependencies:
cacache "^10.0.4" cacache "^10.0.4"
find-cache-dir "^1.0.0" find-cache-dir "^1.0.0"
@ -4538,12 +4538,12 @@ node-libs-browser@^2.0.0:
vm-browserify "0.0.4" vm-browserify "0.0.4"
node-notifier@^5.1.2: node-notifier@^5.1.2:
version "5.2.1" version "5.3.0"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01"
integrity sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg== integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==
dependencies: dependencies:
growly "^1.3.0" growly "^1.3.0"
semver "^5.4.1" semver "^5.5.0"
shellwords "^0.1.1" shellwords "^0.1.1"
which "^1.3.0" which "^1.3.0"
@ -5971,7 +5971,7 @@ selfsigned@^1.9.1:
dependencies: dependencies:
node-forge "0.7.5" node-forge "0.7.5"
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0:
version "5.6.0" version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==