Add today/by date or taxon_id stats and record access

This commit is contained in:
Samuel Ortion 2022-08-14 09:24:35 +02:00
parent e4488e1918
commit 128e3f33bb
15 changed files with 2285 additions and 9 deletions

View File

@ -24,7 +24,7 @@ header img#logo {
}
main {
min-height: 80vh;
min-height: 100vh;
padding: 5em;
}
@ -51,14 +51,14 @@ nav ul {
justify-content: space-between;
align-items: center;
background-color: lightgreen;
margin: 0;
padding: 0;
background-color: #333;
margin: auto;
min-width: 100vw;
}
nav ul li a,
.dropdown-button {
color: green;
color: white;
text-decoration: none;
display: block;
padding: 1em 0.5em;
@ -66,7 +66,13 @@ nav ul li a,
nav ul li a.active,
nav ul li a:hover {
background-color: aquamarine;
background-color: #999;
color: #101010
}
.dropdown-button:hover {
background-color: #900;
color: white
}
.dropdown:hover .dropdown-content {
@ -76,7 +82,6 @@ nav ul li a:hover {
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
@ -86,4 +91,8 @@ canvas {
display: block;
height: 100%;
width: 100%;
}
.scientific-name {
font-style: italic;
}

View File

@ -7,17 +7,22 @@
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.13",
"sensio/framework-extra-bundle": "^6.2",
"symfony/console": "6.1.*",
"symfony/dotenv": "6.1.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "6.1.*",
"symfony/proxy-manager-bridge": "6.1.*",
"symfony/runtime": "6.1.*",
"symfony/translation": "6.1.*",
"symfony/twig-bundle": "6.1.*",
"symfony/webpack-encore-bundle": "^1.15",
"symfony/yaml": "6.1.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/intl-extra": "^3.4",
"twig/twig": "^2.12|^3.0"
},
"config": {
@ -71,5 +76,8 @@
"allow-contrib": false,
"require": "6.1.*"
}
},
"require-dev": {
"symfony/maker-bundle": "^1.45"
}
}

1912
www/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,4 +6,7 @@ return [
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
];

View File

@ -0,0 +1,42 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '13'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: '%kernel.debug%'

View File

@ -0,0 +1,8 @@
version: '3'
services:
###> doctrine/doctrine-bundle ###
database:
ports:
- "5432"
###< doctrine/doctrine-bundle ###

21
www/docker-compose.yml Normal file
View File

@ -0,0 +1,21 @@
version: '3'
services:
###> doctrine/doctrine-bundle ###
database:
image: postgres:${POSTGRES_VERSION:-14}-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-app}
# You should definitely change the password in production
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
POSTGRES_USER: ${POSTGRES_USER:-app}
volumes:
- db-data:/var/lib/postgresql/data:rw
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
# - ./docker/db/data:/var/lib/postgresql/data:rw
###< doctrine/doctrine-bundle ###
volumes:
###> doctrine/doctrine-bundle ###
db-data:
###< doctrine/doctrine-bundle ###

0
www/migrations/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,146 @@
<?php
// src/Controller/TodayController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection;
class TodayController extends AbstractController
{
private Connection $connection;
/**
* @Route("/today", name="today")
*/
public function today(Connection $connection)
{
$this->connection = $connection;
$date = date('Y-m-d');
return $this->render('today/index.html.twig', [
"species" => $this->recorded_species_by_date($date),
]);
}
/**
* @Route("/today/species", name="today_species")
*/
public function today_species_page(Connection $connection)
{
$this->connection = $connection;
$date = date('Y-m-d');
return $this->render('today/index.html.twig', [
"species" => $this->recorded_species_by_date($date)
]);
}
/**
* @Route("/today/species/{id}", name="today_species_id")
*/
public function today_species_by_id(Connection $connection, $id)
{
$this->connection = $connection;
$date = date('Y-m-d');
return $this->render('today/species.html.twig', [
"results" => $this->recorded_species_by_id_and_date($id, $date)
]);
}
/**
* @Route("/today/{date}", name="today_date")
*/
public function today_date(Connection $connection, $date)
{
return $this->redirectToRoute('today_species_date', array('date' => $date));
}
/**
* @Route("/today/{date}/species", name="today_species_date")
*/
public function today_species_by_date(Connection $connection, $date)
{
$this->connection = $connection;
return $this->render('today/index.html.twig', [
"date" => $date,
"results" => $this->recorded_species_by_date($date)
]);
}
/**
* @Route("/today/{date}/species/{id}", name="today_species_id_and_date")
*/
public function today_species_by_id_and_date(Connection $connection, $date, $id)
{
$this->connection = $connection;
return $this->render('today/species.html.twig', [
"date" => $date,
"results" => $this->recorded_species_by_id_and_date($id, $date)
]);
}
private function recorded_species_by_date($date)
{
$sql = "SELECT `taxon`.`taxon_id`, `scientific_name`, `common_name`, COUNT(*) AS `contact_count`, MAX(`confidence`) AS max_confidence
FROM observation
INNER JOIN taxon
ON observation.taxon_id = taxon.taxon_id
WHERE strftime('%Y-%m-%d', `observation`.`date`) =:date
GROUP BY observation.taxon_id";
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(':date', $date);
$result = $stmt->executeQuery();
return $result->fetchAllAssociative();
}
private function recorded_species_by_id_and_date($id, $date)
{
/* Get taxon even if there is no record this date */
$sql = "SELECT * FROM `taxon` WHERE `taxon_id` = :id";
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(':id', $id);
$result = $stmt->executeQuery();
$taxon = $result->fetchAssociative();
if (!$taxon) {
return [];
}
/* Get daily stats */
$sql = "SELECT COUNT(*) AS `contact_count`, MAX(`confidence`) AS `max_confidence`
FROM `taxon`
INNER JOIN `observation`
ON `taxon`.`taxon_id` = `observation`.`taxon_id`
WHERE strftime('%Y-%m-%d', `observation`.`date`) = :date
AND `observation`.`taxon_id` = :id";
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(':id', $id);
$stmt->bindValue(':date', $date);
$result = $stmt->executeQuery();
$stat = $result->fetchAllAssociative();
$sql = "SELECT * FROM `observation`
WHERE `taxon_id` = :id
AND strftime('%Y-%m-%d', `observation`.`date`) = :date";
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(':id', $id);
$stmt->bindValue(':date', $date);
$result = $stmt->executeQuery();
$records = $result->fetchAllAssociative();
return array("taxon" => $taxon, "stat" => $stat, "records" => $records);
}
private function best_confidence_today($id, $date)
{
$sql = "SELECT MAX(`confidence`) AS confidence
FROM `observation`
WHERE strftime('%Y-%m-%d', `observation`.`date`) = :date
AND `taxon_id` = :id";
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(':id', $id);
$stmt->bindValue(':date', $date);
$result = $stmt->executeQuery();
return $result->fetchAllAssociative();
}
}

0
www/src/Entity/.gitignore vendored Normal file
View File

0
www/src/Repository/.gitignore vendored Normal file
View File

View File

@ -8,6 +8,33 @@
"ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05"
}
},
"doctrine/doctrine-bundle": {
"version": "2.7",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.4",
"ref": "da713d006953b90d1c085c1be480ecdd6c4a95e0"
},
"files": [
"config/packages/doctrine.yaml",
"src/Entity/.gitignore",
"src/Repository/.gitignore"
]
},
"doctrine/doctrine-migrations-bundle": {
"version": "3.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.1",
"ref": "ee609429c9ee23e22d6fa5728211768f51ed2818"
},
"files": [
"config/packages/doctrine_migrations.yaml",
"migrations/.gitignore"
]
},
"sensio/framework-extra-bundle": {
"version": "6.2",
"recipe": {
@ -63,6 +90,15 @@
"src/Kernel.php"
]
},
"symfony/maker-bundle": {
"version": "1.45",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/routing": {
"version": "6.1",
"recipe": {

View File

@ -0,0 +1,17 @@
{% extends "base.html.twig" %}
{% block content %}
<h2>{{ "Today's Detected Species" | trans }}</h2>
{# Display a list of records if any, else, print message #}
{% if results[0] is defined and results[0] | length > 0 %}
<ul>
{% for sp in results %}
<li class="species">
<a href="./species/{{ sp['taxon_id'] }}">
<span class="scientific-name">{{ sp["scientific_name"] }} (</span><span class="common-name">{{ sp["common_name"] }}</span>)
</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends "base.html.twig" %}
{% block content %}
{% if results is defined and results | length > 0 %}
{% set taxon = results["taxon"] %}
<h2>
{% set today = "now" | date("Y-m-d") %}
{% if today == date %}
{{ "Today's contact for" | trans }}
{% else %}
{{ "Contact on " | trans }}
{{ date | format_datetime("full", "none") }}
{{ " for " | trans }}
{% endif %}
<span class="scientific-name">
{{ taxon["scientific_name"] }}
</span>
(<span class="common-name">
{{ taxon["common_name"] }}
</span>)
</h2>
<div class="stats">
{% set stat = results["stat"][0] %}
<h3>{{ "Stats" | trans }}</h3>
<div class="contact-count">
{{ "Contact count:" | trans }}
<span class="counter">{{ stat["contact_count"] }}</span>
</div>
<div class="contact-confidence">
{{ "Max confidence" | trans }}
<span class="value">{{ stat["max_confidence"] }}</span>
</div>
</div>
{% set records = results["records"] %}
<div class="records">
<h3>{{ "Contact records" | trans }}</h3>
{% if records is defined and records | length > 0 %}
<table>
<thead>
<tr>
<th>{{ "Filename" | trans }}</th>
<th>{{ "Time" | trans }}</th>
<th>{{ "Confidence" | trans }}</th>
<th>{{ "Audio" | trans }}</th>
</thead>
<tbody>
{% for record in records %}
<tr>
<td>
<a title="{{ "Download audio file" | trans }}" href="/media/records/{{ record['audio_file'] }}">
{{ record["audio_file"] }}
</a>
</td>
<td>{{ record["date"] | date("H:m") }}</td>
<td>{{ record["confidence"] }}</td>
<td>
<audio controls>
<source src="/media/records/{{ record['audio_file'] }}" type="audio/wav">
Your browser does not support the audio element.
</audio>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{{ "No records this day for this species" | trans }}</p>
{% endif %}
</div>
{% endif %}
{% endblock %}