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

Feat/refine species page #3174

Merged
Merged
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
76 changes: 76 additions & 0 deletions backend/geonature/core/gn_synthese/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,82 @@ def general_stats(permissions):
return data


@routes.route("/species_stats/<int:cd_ref>", methods=["GET"])
@permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE")
@json_resp
def species_stats(scope, cd_ref):
"""Return stats about distinct species."""

area_type = request.args.get("area_type")

if not area_type:
raise BadRequest("Missing area_type parameter")

# Ensure area_type is valid
valid_area_types = (
db.session.query(BibAreasTypes.type_code)
.distinct()
.filter(BibAreasTypes.type_code == area_type)
.scalar()
)
if not valid_area_types:
raise BadRequest("Invalid area_type")

# Subquery to fetch areas based on area_type
areas_subquery = (
select([LAreas.id_area])
.where(LAreas.id_type == BibAreasTypes.id_type)
.where(BibAreasTypes.type_code == area_type)
.alias("areas")
)

taxref_cd_nom_list = db.session.scalars(select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref))

# Main query to fetch stats
query = (
select(
[
func.count(distinct(Synthese.id_synthese)).label("observation_count"),
func.count(distinct(Synthese.observers)).label("observer_count"),
func.count(distinct(areas_subquery.c.id_area)).label("area_count"),
func.min(Synthese.altitude_min).label("altitude_min"),
func.max(Synthese.altitude_max).label("altitude_max"),
func.min(Synthese.date_min).label("date_min"),
func.max(Synthese.date_max).label("date_max"),
]
)
.select_from(
sa.join(
Synthese,
CorAreaSynthese,
Synthese.id_synthese == CorAreaSynthese.id_synthese,
)
.join(areas_subquery, CorAreaSynthese.id_area == areas_subquery.c.id_area)
.join(LAreas, CorAreaSynthese.id_area == LAreas.id_area)
.join(BibAreasTypes, LAreas.id_type == BibAreasTypes.id_type)
)
.where(Synthese.cd_nom.in_(taxref_cd_nom_list))
)

synthese_query_obj = SyntheseQuery(Synthese, query, {})
synthese_query_obj.filter_query_with_cruved(g.current_user, scope)
result = DB.session.execute(synthese_query_obj.query)
synthese_stats = result.fetchone()

data = {
"cd_ref": cd_ref,
"observation_count": synthese_stats["observation_count"],
"observer_count": synthese_stats["observer_count"],
"area_count": synthese_stats["area_count"],
"altitude_min": synthese_stats["altitude_min"],
"altitude_max": synthese_stats["altitude_max"],
"date_min": synthese_stats["date_min"],
"date_max": synthese_stats["date_max"],
}

return data


@routes.route("/taxons_tree", methods=["GET"])
@login_required
@json_resp
Expand Down
74 changes: 74 additions & 0 deletions backend/geonature/core/gn_synthese/synthese_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,77 @@
{"prop": "dataset_name", "name": "JDD", "max_width": 200},
{"prop": "observers", "name": "observateur", "max_width": 200},
]


class DefaultProfile:
ENABLED = True
## DEFAULT PROFILE INDICATORS
LIST_INDICATORS = [
{
"name": "observation(s) valide(s)",
"matIcon": "search",
"field": "count_valid_data",
"type": "number",
},
{
"name": "Première observation",
"matIcon": "schedule",
"field": "first_valid_data",
"type": "date",
},
{
"name": "Dernière observation",
"matIcon": "search",
"field": "last_valid_data",
"type": "date",
},
{
"name": "Plage d'altitude(s)",
"matIcon": "terrain",
"field": ["altitude_min", "altitude_max"],
"unit": "m",
"type": "number",
},
]


class DefaultGeographicOverview:
pass


class DefaultSpeciesSheet:
## DEFAULT SPECIES SHEET INDICATORS
LIST_INDICATORS = [
{
"name": "observation(s)",
"matIcon": "search",
"field": "observation_count",
"type": "number",
},
{
"name": "observateur(s)",
"matIcon": "people",
"field": "observer_count",
"type": "number",
},
{
"name": "commune(s)",
"matIcon": "location_on",
"field": "area_count",
"type": "number",
},
{
"name": "Plage d'altitude(s)",
"matIcon": "terrain",
"unit": "m",
"type": "number",
"field": ["altitude_min", "altitude_max"],
},
{
"name": "Plage d'observation(s)",
"matIcon": "date_range",
"type": "date",
"field": ["date_min", "date_max"],
"separator": "-",
},
]
27 changes: 27 additions & 0 deletions backend/geonature/utils/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
from geonature.core.gn_synthese.synthese_config import (
DEFAULT_EXPORT_COLUMNS,
DEFAULT_LIST_COLUMN,
DefaultGeographicOverview,
edelclaux marked this conversation as resolved.
Show resolved Hide resolved
DefaultProfile,
DefaultSpeciesSheet,
)
from geonature.utils.env import GEONATURE_VERSION, BACKEND_DIR, ROOT_DIR
from geonature.utils.module import iter_modules_dist, get_module_config
Expand Down Expand Up @@ -274,6 +277,26 @@ class ExportObservationSchema(Schema):
geojson_local_field = fields.String(load_default="geojson_local")


class SpeciesSheetProfile(Schema):
ENABLED = fields.Boolean(load_default=DefaultProfile.ENABLED)
LIST_INDICATORS = fields.List(fields.Dict, load_default=DefaultProfile.LIST_INDICATORS)


class SpeciesSheetGeographicOverview(Schema):
pass


class SpeciesSheet(Schema):
# --------------------------------------------------------------------
# SYNTHESE - SPECIES_SHEET
LIST_INDICATORS = fields.List(fields.Dict, load_default=DefaultSpeciesSheet.LIST_INDICATORS)

GEOGRAPHIC_OVERVIEW = fields.Dict(
load_default=SpeciesSheetGeographicOverview().load({})
) # rename
PROFILE = fields.Nested(SpeciesSheetProfile, load_default=SpeciesSheetProfile().load({}))


class Synthese(Schema):
# --------------------------------------------------------------------
# SYNTHESE - SEARCH FORM
Expand Down Expand Up @@ -428,6 +451,10 @@ class Synthese(Schema):
# Activate the blurring of sensitive observations. Otherwise, exclude them
BLUR_SENSITIVE_OBSERVATIONS = fields.Boolean(load_default=True)

# --------------------------------------------------------------------
# SYNTHESE - SPECIES_SHEET
SPECIES_SHEET = fields.Nested(SpeciesSheet, load_default=SpeciesSheet().load({}))

@pre_load
def warn_deprecated(self, data, **kwargs):
deprecated = {
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/app/GN2CommonModule/form/data-form.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface ParamsDict {
[key: string]: any;
}

export type Profile = GeoJSON.Feature;

export const FormatMapMime = new Map([
['csv', 'text/csv'],
['json', 'application/json'],
Expand Down Expand Up @@ -642,8 +644,10 @@ export class DataFormService {
return this._http.get<any>(`${this.config.API_TAXHUB}/bdc_statuts/status_values/${statusType}`);
}

getProfile(cdRef) {
return this._http.get<any>(`${this.config.API_ENDPOINT}/gn_profiles/valid_profile/${cdRef}`);
getProfile(cdRef): Observable<Profile> {
return this._http.get<Profile>(
`${this.config.API_ENDPOINT}/gn_profiles/valid_profile/${cdRef}`
);
}

getPhenology(cdRef, idNomenclatureLifeStage?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export class SyntheseDataService {
return this._api.get<any>(`${this.config.API_ENDPOINT}/synthese/general_stats`);
}

getSyntheseSpeciesSheetStat(cd_ref: number, areaType: string = 'COM') {
return this._api.get<any>(`${this.config.API_ENDPOINT}/synthese/species_stats/${cd_ref}`, {
params: new HttpParams().append('area_type', areaType),
});
}

getTaxaCount(params = {}) {
let queryString = new HttpParams();
for (let key in params) {
Expand Down
35 changes: 32 additions & 3 deletions frontend/src/app/syntheseModule/synthese.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,33 @@ import { SyntheseModalDownloadComponent } from './synthese-results/synthese-list
import { DiscussionCardComponent } from '@geonature/shared/discussionCardModule/discussion-card.component';
import { AlertInfoComponent } from '../shared/alertInfoModule/alert-Info.component';
import { TaxonSheetComponent } from './taxon-sheet/taxon-sheet.component';
import {
RouteService,
ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES,
ROUTE_MANDATORY,
} from './taxon-sheet/taxon-sheet.route.service';

const routes: Routes = [
{ path: '', component: SyntheseComponent },
{ path: 'occurrence/:id_synthese', component: SyntheseComponent, pathMatch: 'full' },
{ path: 'taxon/:cd_nom', component: TaxonSheetComponent },
{
path: 'taxon/:cd_ref',
component: TaxonSheetComponent,
canActivateChild: [RouteService],
children: [
{
path: '',
redirectTo: ROUTE_MANDATORY.path,
pathMatch: 'prefix',
},
...ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.map((tab) => {
return {
path: tab.path,
component: tab.component,
};
}),
],
},
];

@NgModule({
Expand All @@ -29,20 +52,26 @@ const routes: Routes = [
SharedSyntheseModule,
CommonModule,
TreeModule,
TaxonSheetComponent,
],
declarations: [
SyntheseComponent,
SyntheseListComponent,
SyntheseCarteComponent,
SyntheseModalDownloadComponent,
TaxonSheetComponent,
],
entryComponents: [
SyntheseInfoObsComponent,
SyntheseModalDownloadComponent,
DiscussionCardComponent,
AlertInfoComponent,
],
providers: [MapService, DynamicFormService, TaxonAdvancedStoreService, SyntheseFormService],
providers: [
MapService,
DynamicFormService,
TaxonAdvancedStoreService,
SyntheseFormService,
RouteService,
],
})
export class SyntheseModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div [className]="small ? 'card Indicator Indicator--small' : 'card Indicator'">
<mat-icon class="Indicator__icon">{{ indicator.matIcon }}</mat-icon>
<span class="Indicator__value">{{ indicator.value }}</span>
<span class="Indicator__name text-secondary">{{ indicator.name }}</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.Indicator {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
row-gap: 0rem;
padding: 1rem;
height: 9rem;
width: auto;
min-width: 12rem;
&__icon {
$icon-size: 2rem;
font-size: $icon-size;
height: $icon-size;
width: $icon-size;
min-height: $icon-size;
}
&__value {
font-size: 1.5rem;
white-space: nowrap;
&Unit {
opacity: 0.5;
}
}
&__name {
font-size: 1rem;
text-align: center;
}
}

.Indicator--small {
border: none;
height: auto;
.Indicator__icon {
$icon-size: 1.5rem;
font-size: $icon-size;
height: $icon-size;
width: $icon-size;
min-height: $icon-size;
}
.Indicator__value {
font-size: 1.3rem;
}
.Indicator__name {
font-size: 1rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { Indicator } from './indicator';

@Component({
standalone: true,
selector: 'indicator',
templateUrl: 'indicator.component.html',
styleUrls: ['indicator.component.scss'],
imports: [MatIconModule],
})
export class IndicatorComponent {
@Input()
indicator: Indicator;

@Input()
small: boolean = false;
}
Loading
Loading