pr0nstar

V

...@@ -14,7 +14,8 @@ from aiocache import caches ...@@ -14,7 +14,8 @@ from aiocache import caches
14 14
15 from presupuestov1.routers import ( 15 from presupuestov1.routers import (
16 project, 16 project,
17 - entity 17 + entity,
18 + classifier
18 ) 19 )
19 20
20 21
...@@ -40,6 +41,10 @@ app = FastAPI( ...@@ -40,6 +41,10 @@ app = FastAPI(
40 ) 41 )
41 app.include_router(project.router) 42 app.include_router(project.router)
42 app.include_router(entity.router) 43 app.include_router(entity.router)
44 +app.include_router(classifier.router_objeto)
45 +app.include_router(classifier.router_acteco)
46 +app.include_router(classifier.router_finfun)
47 +
43 48
44 49
45 @app.on_event('startup') 50 @app.on_event('startup')
......
...@@ -72,3 +72,31 @@ class TreemapActeco(Base): ...@@ -72,3 +72,31 @@ class TreemapActeco(Base):
72 desc_acteco: Mapped[str | None] = mapped_column(Text) 72 desc_acteco: Mapped[str | None] = mapped_column(Text)
73 parent: Mapped[str | None] = mapped_column(String(9)) 73 parent: Mapped[str | None] = mapped_column(String(9))
74 devengado: Mapped[float | None] = mapped_column(Numeric(30, 15)) 74 devengado: Mapped[float | None] = mapped_column(Numeric(30, 15))
75 +
76 +
77 +###############################################################################
78 +# fns
79 +###############################################################################
80 +
81 +def get_entity_connections(entity_id, db, query, ClassObject, object_id):
82 + query_filters = [
83 + ClassObject.entidad == entity_id,
84 + ]
85 +
86 + return get_clas_entities(query_filters, db, query, ClassObject, object_id)
87 +
88 +
89 +def get_clas_entities(query_filters, db, query, ClassObject, object_id):
90 + if query.gestion is None:
91 + query.gestion = 0
92 +
93 + query_filters.append(ClassObject.gestion == query.gestion)
94 +
95 + if query.id is not None:
96 + ids = query.id.split(',')
97 + ids = [int(_.strip()) for _ in ids]
98 +
99 + ids = or_([getattr(ClassObject, object_id) == _ for _ in ids])
100 + query_filters.append(ids)
101 +
102 + return db.query(ClassObject).filter(and_(*query_filters)).all()
......
1 +fastapi>=0.110,<1.0
2 +uvicorn[standard]>=0.29,<1.0
3 +sqlalchemy>=2.0,<3.0
4 +pydantic>=2.6,<3.0
5 +starlette-context>=0.3,<1.0
6 +aiocache>=0.12,<1.0
7 +numpy>=1.26,<3.0
8 +typesense>=0.21,<1.0
9 +psycopg2-binary>=2.9,<3.0
1 +import time
2 +
3 +from typing import List, Any, Optional, Union
4 +
5 +from fastapi import APIRouter, Depends, Request, HTTPException, Body
6 +from sqlalchemy.orm import Session
7 +from sqlalchemy import and_, or_
8 +
9 +from presupuestov1 import model, schema, constant, schemas, models, search
10 +
11 +from aiocache import cached
12 +
13 +
14 +router_objeto = APIRouter(
15 + prefix='/objeto',
16 + tags=['objeto'],
17 +)
18 +
19 +router_acteco = APIRouter(
20 + prefix='/acteco',
21 + tags=['acteco'],
22 +)
23 +
24 +router_finfun = APIRouter(
25 + prefix='/finfun',
26 + tags=['finfun'],
27 +)
28 +
29 +
30 +###############################################################################
31 +# GET
32 +###############################################################################
33 +
34 +@router_objeto.get('/clasificador', response_model=List[schemas.classifier.ClasObjetos])
35 +def fetch_object_classifier(db: Session = Depends(model.get_db)):
36 + return _fetch_classifier('objeto', db)
37 +
38 +
39 +@router_acteco.get('/clasificador', response_model=List[schemas.classifier.ClasActeco])
40 +def fetch_object_classifier(db: Session = Depends(model.get_db)):
41 + return _fetch_classifier('acteco', db)
42 +
43 +
44 +@router_finfun.get('/clasificador', response_model=List[schemas.classifier.ClasFinfun])
45 +def fetch_object_classifier(db: Session = Depends(model.get_db)):
46 + return _fetch_classifier('finfun', db)
47 +
48 +
49 +@router_objeto.get('/{object_id}', response_model=schemas.classifier.ClasObjetos)
50 +def fetch_clas(
51 + object_id: str,
52 + db: Session = Depends(model.get_db),
53 +):
54 + ClasObjetos = models.classifier.ClasObjetos
55 + return db.query(ClasObjetos).filter(
56 + ClasObjetos.objeto == '{}.0'.format(object_id)
57 + ).one()
58 +
59 +
60 +@router_objeto.get('/{object_id}/entidades', response_model=List[schemas.treemap.TreemapObjeto])
61 +def fetch_clas(
62 + object_id: str,
63 + db: Session = Depends(model.get_db),
64 +):
65 + Treemap = models.treemap.TreemapObjeto
66 + return models.treemap.get_clas_entities(
67 + [
68 + Treemap.objeto == object_id
69 + ], db, query, Treemap, 'entidad'
70 + )
71 +
72 +@router_acteco.get('/{acteco_id}', response_model=schemas.classifier.ClasActeco)
73 +def fetch_clas(
74 + acteco_id: str,
75 + db: Session = Depends(model.get_db),
76 +):
77 + ClasActeco = models.classifier.ClasActeco
78 + return db.query(ClasActeco).filter(
79 + ClasActeco.acteco == acteco_id
80 + ).one()
81 +
82 +
83 +@router_acteco.get('/{acteco_id}/entidades', response_model=List[schemas.treemap.TreemapActeco])
84 +def fetch_clas(
85 + acteco_id: str,
86 + db: Session = Depends(model.get_db),
87 +):
88 + Treemap = models.treemap.TreemapActeco
89 + return models.treemap.get_clas_entities(
90 + [
91 + Treemap.acteco == acteco_id
92 + ], db, query, Treemap, 'entidad'
93 + )
94 +
95 +
96 +@router_finfun.get('/{finfun_id}', response_model=schemas.classifier.ClasFinfun)
97 +def fetch_clas(
98 + finfun_id: str,
99 + db: Session = Depends(model.get_db),
100 +):
101 + ClasFinfun = models.classifier.ClasFinfun
102 + return db.query(ClasFinfun).filter(
103 + ClasFinfun.finfun == finfun_id
104 + ).one()
105 +
106 +
107 +@router_finfun.get('/{finfun_id}/entidades', response_model=List[schemas.treemap.TreemapFinFun])
108 +def fetch_clas(
109 + finfun_id: str,
110 + db: Session = Depends(model.get_db),
111 +):
112 + Treemap = models.treemap.TreemapFinFun
113 + return models.treemap.get_clas_entities(
114 + [
115 + Treemap.finfun == finfun_id
116 + ], db, query, Treemap, 'entidad'
117 + )
118 +
119 +
120 +###############################################################################
121 +# priv
122 +###############################################################################
123 +
124 +def _fetch_classifier(clas, db):
125 + ClassObject = {
126 + 'objeto': models.classifier.ClasObjetos,
127 + 'acteco': models.classifier.ClasActeco,
128 + 'finfun': models.classifier.ClasFinfun,
129 + }[clas]
130 + return db.query(ClassObject).all()
...@@ -46,7 +46,9 @@ def fetch_entity_objetos( ...@@ -46,7 +46,9 @@ def fetch_entity_objetos(
46 query: schema.QueryParams = Depends(), 46 query: schema.QueryParams = Depends(),
47 ): 47 ):
48 TreemapObjeto = models.treemap.TreemapObjeto 48 TreemapObjeto = models.treemap.TreemapObjeto
49 - return get_entity_connections(entity_id, db, query, TreemapObjeto, 'objeto') 49 + return models.treemap.get_entity_connections(
50 + entity_id, db, query, TreemapObjeto, 'objeto'
51 + )
50 52
51 53
52 router.get('/{entity_id:int}/finfuns', response_model=List[schemas.treemap.TreemapFinFun]) 54 router.get('/{entity_id:int}/finfuns', response_model=List[schemas.treemap.TreemapFinFun])
...@@ -56,7 +58,9 @@ def fetch_entity_finfuns( ...@@ -56,7 +58,9 @@ def fetch_entity_finfuns(
56 query: schema.QueryParams = Depends(), 58 query: schema.QueryParams = Depends(),
57 ): 59 ):
58 TreemapFinFun = models.treemap.TreemapFinFun 60 TreemapFinFun = models.treemap.TreemapFinFun
59 - return get_entity_connections(entity_id, db, query, TreemapFinFun, 'finfun') 61 + return models.treemap.get_entity_connections(
62 + entity_id, db, query, TreemapFinFun, 'finfun'
63 + )
60 64
61 65
62 @router.get('/{entity_id:int}/actecos', response_model=List[schemas.treemap.TreemapActeco]) 66 @router.get('/{entity_id:int}/actecos', response_model=List[schemas.treemap.TreemapActeco])
...@@ -66,29 +70,7 @@ def fetch_entity_actecos( ...@@ -66,29 +70,7 @@ def fetch_entity_actecos(
66 query: schema.QueryParams = Depends(), 70 query: schema.QueryParams = Depends(),
67 ): 71 ):
68 TreemapActeco = models.treemap.TreemapActeco 72 TreemapActeco = models.treemap.TreemapActeco
69 - return get_entity_connections(entity_id, db, query, TreemapActeco, 'acteco') 73 + return models.treemap.get_entity_connections(
74 + entity_id, db, query, TreemapActeco, 'acteco'
75 + )
70 76
71 -
72 -###############################################################################
73 -# Private
74 -###############################################################################
75 -
76 -def get_entity_connections(entity_id, db, query, ClassObject, object_id):
77 - if query.gestion is None:
78 - query.gestion = 0
79 -
80 - query_filters = [
81 - ClassObject.entidad == entity_id,
82 - ClassObject.gestion == query.gestion,
83 - ]
84 -
85 - print(query.id is not None)
86 -
87 - if query.id is not None:
88 - ids = query.id.split(',')
89 - ids = [int(_.strip()) for _ in ids]
90 -
91 - ids = or_([getattr(ClassObject, object_id) == _ for _ in ids])
92 - query_filters.append(ids)
93 -
94 - return db.query(ClassObject).filter(and_(*query_filters)).all()
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -34,15 +34,23 @@ def fetch_search( ...@@ -34,15 +34,23 @@ def fetch_search(
34 query: schema.SearchQueryParams = Depends(), 34 query: schema.SearchQueryParams = Depends(),
35 ): 35 ):
36 if query.q is None or len(query.q.strip()) < 3: 36 if query.q is None or len(query.q.strip()) < 3:
37 - return [] 37 + return {
38 + 'facet_counts': [],
39 + 'found': 0,
40 + 'out_of': 0,
41 + 'page': 0,
42 + 'search_time_ms': 0,
43 + 'hits': [],
44 + }
38 45
39 - return do_search(query.q, query.page, query.is_class) 46 + return search.do_search(
47 + query.q, query.page, query.is_class, query.class_, query.order
48 + )
40 49
41 50
42 ############################################################################### 51 ###############################################################################
43 # GET 52 # GET
44 ############################################################################### 53 ###############################################################################
45 54
46 -# @cached(ttl=10)
47 -def do_search(query, page, is_class):
48 - return search.do_search(query, page, is_class)
...\ No newline at end of file ...\ No newline at end of file
55 +def do_search(query, page, is_class, class_):
56 + return search.do_search(query, page, is_class, class_)
......
...@@ -11,8 +11,10 @@ class OrmBaseModel(BaseModel): ...@@ -11,8 +11,10 @@ class OrmBaseModel(BaseModel):
11 11
12 class SearchQueryParams(BaseModel): 12 class SearchQueryParams(BaseModel):
13 page: Optional[int] = Query(default=1) 13 page: Optional[int] = Query(default=1)
14 - q: Optional[str] = Query(default=None) 14 + q: Optional[str] = Query(default=None, max_length=128)
15 + order: Optional[str] = Query(default=None, max_length=5)
15 is_class: Optional[bool] = Query(default=None) 16 is_class: Optional[bool] = Query(default=None)
17 + class_: Optional[str] = Query(default=None)
16 18
17 class QueryParams(BaseModel): 19 class QueryParams(BaseModel):
18 id: Optional[str] = Query(default=None) 20 id: Optional[str] = Query(default=None)
......
1 from typing import Any 1 from typing import Any
2 2
3 from presupuestov1.schema import OrmBaseModel 3 from presupuestov1.schema import OrmBaseModel
4 +from pydantic import create_model
4 5
5 6
6 class ClasInstitucional(OrmBaseModel): 7 class ClasInstitucional(OrmBaseModel):
...@@ -28,6 +29,13 @@ class ClasInstitucional(OrmBaseModel): ...@@ -28,6 +29,13 @@ class ClasInstitucional(OrmBaseModel):
28 n_gestiones: int | None 29 n_gestiones: int | None
29 30
30 31
32 +def get_clasdesc(name):
33 + return create_model(
34 + '_ClasDesc{}'.format(name),
35 + **{'gestion': str, 'desc_{}'.format(name): str},
36 + )
37 +
38 +
31 class ClasObjetos(OrmBaseModel): 39 class ClasObjetos(OrmBaseModel):
32 nivel: str | None 40 nivel: str | None
33 objeto: str 41 objeto: str
...@@ -35,7 +43,7 @@ class ClasObjetos(OrmBaseModel): ...@@ -35,7 +43,7 @@ class ClasObjetos(OrmBaseModel):
35 gestiones: str | None 43 gestiones: str | None
36 n_gestiones: int | None 44 n_gestiones: int | None
37 n_variaciones: int | None 45 n_variaciones: int | None
38 - descripciones: list[dict[str, Any]] | None 46 + descripciones: list[get_clasdesc('objeto')] | None
39 47
40 48
41 class ClasRubros(OrmBaseModel): 49 class ClasRubros(OrmBaseModel):
...@@ -45,7 +53,7 @@ class ClasRubros(OrmBaseModel): ...@@ -45,7 +53,7 @@ class ClasRubros(OrmBaseModel):
45 gestiones: str | None 53 gestiones: str | None
46 n_gestiones: int | None 54 n_gestiones: int | None
47 n_variaciones: int | None 55 n_variaciones: int | None
48 - descripciones: list[dict[str, Any]] | None 56 + descripciones: list[get_clasdesc('rubro')] | None
49 57
50 58
51 class ClasFinfun(OrmBaseModel): 59 class ClasFinfun(OrmBaseModel):
...@@ -55,4 +63,14 @@ class ClasFinfun(OrmBaseModel): ...@@ -55,4 +63,14 @@ class ClasFinfun(OrmBaseModel):
55 gestiones: str | None 63 gestiones: str | None
56 n_gestiones: int | None 64 n_gestiones: int | None
57 n_variaciones: int | None 65 n_variaciones: int | None
58 - descripciones: list[dict[str, Any]] | None 66 + descripciones: list[get_clasdesc('finfun')] | None
67 +
68 +
69 +class ClasActeco(OrmBaseModel):
70 + nivel: str | None
71 + acteco: str
72 + desc_acteco: str | None
73 + gestiones: str | None
74 + n_gestiones: int | None
75 + n_variaciones: int | None
76 + descripciones: list[get_clasdesc('acteco')] | None
......
...@@ -7,6 +7,7 @@ class ProjectSearch(BaseModel): ...@@ -7,6 +7,7 @@ class ProjectSearch(BaseModel):
7 gestion: int 7 gestion: int
8 devengado: float 8 devengado: float
9 is_class: bool 9 is_class: bool
10 + class_: str
10 texto: str 11 texto: str
11 metadatos: str 12 metadatos: str
12 13
......
1 +import numpy as np
2 +
1 from presupuestov1.schema import OrmBaseModel 3 from presupuestov1.schema import OrmBaseModel
4 +from pydantic import field_serializer
2 5
3 6
4 -class TreemapObjeto(OrmBaseModel): 7 +class Treemap(OrmBaseModel):
5 gestion: int 8 gestion: int
6 entidad: int 9 entidad: int
7 - objeto: int
8 nivel: str | None 10 nivel: str | None
9 - desc_objeto: str | None
10 parent: int | None 11 parent: int | None
11 devengado: float | None 12 devengado: float | None
12 13
14 + @field_serializer('devengado')
15 + def do_round(self, v):
16 + if v is not None:
17 + return np.round(v, 2)
13 18
14 -class TreemapFinFun(OrmBaseModel): 19 +
15 - gestion: int 20 +class TreemapObjeto(Treemap):
16 - entidad: int 21 + objeto: str
17 - finfun: int 22 + desc_objeto: str | None
18 - nivel: str | None 23 +
24 +
25 +class TreemapFinFun(Treemap):
26 + finfun: str
19 desc_finfun: str | None 27 desc_finfun: str | None
20 - parent: int | None
21 - devengado: float | None
22 28
23 29
24 -class TreemapActeco(OrmBaseModel): 30 +class TreemapActeco(Treemap):
25 - gestion: int 31 + acteco: str
26 - entidad: int
27 - acteco: int
28 - nivel: str | None
29 desc_actividad: str | None 32 desc_actividad: str | None
30 - parent: int | None
31 - devengado: float | None
......
...@@ -18,6 +18,7 @@ schema = { ...@@ -18,6 +18,7 @@ schema = {
18 {'name': 'gestion', 'type': 'int32', 'facet': True}, 18 {'name': 'gestion', 'type': 'int32', 'facet': True},
19 {'name': 'devengado', 'type': 'float'}, 19 {'name': 'devengado', 'type': 'float'},
20 {'name': 'is_class', 'type': 'bool'}, 20 {'name': 'is_class', 'type': 'bool'},
21 + {'name': 'class_', 'type': 'string'},
21 {'name': 'texto', 'type': 'string'}, 22 {'name': 'texto', 'type': 'string'},
22 {'name': 'metadatos', 'type': 'string'}, 23 {'name': 'metadatos', 'type': 'string'},
23 ], 24 ],
...@@ -33,8 +34,10 @@ search_parameters = { ...@@ -33,8 +34,10 @@ search_parameters = {
33 } 34 }
34 35
35 36
36 - 37 +AVAILABLE_CLASSES = [
37 -def do_search(query, page, is_class): 38 + 'programa_proyecto', 'entidad', 'objeto', 'finfun', 'acteco'
39 +]
40 +def do_search(query, page, is_class, class_=None, order=None):
38 search_parameters_ = search_parameters.copy() 41 search_parameters_ = search_parameters.copy()
39 search_parameters_['q'] = query 42 search_parameters_['q'] = query
40 43
...@@ -47,9 +50,18 @@ def do_search(query, page, is_class): ...@@ -47,9 +50,18 @@ def do_search(query, page, is_class):
47 50
48 search_parameters_['filter_by'] = filter_c 51 search_parameters_['filter_by'] = filter_c
49 52
50 - if (page is not None) and (page > 0): 53 + if (class_ is not None):
54 + class_ = [_ for _ in class_.split(',') if _ in AVAILABLE_CLASSES]
55 + if len(class_):
56 + search_parameters_['filter_by'] = 'class_:[{}]'.format(', '.join(class_))
57 +
58 + if (page is not None) and (page > 0) and (page < 100):
51 search_parameters_['page'] = page 59 search_parameters_['page'] = page
52 60
61 + search_parameters_['sort_by'] = 'devengado:desc'
62 + if order == 'asc':
63 + search_parameters_['sort_by'] = 'devengado:asc'
64 +
53 return client.collections[COLLECTION].documents.search( 65 return client.collections[COLLECTION].documents.search(
54 search_parameters_ 66 search_parameters_
55 ) 67 )
......