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

merge dev into master #2655

Merged
merged 14 commits into from
May 26, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
docker logs --details db

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: unittests
Expand Down
45 changes: 42 additions & 3 deletions anyway/flask_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
from flask_babel import Babel, gettext
from flask_compress import Compress
from flask_cors import CORS
from flask_restx import Resource, fields, reqparse
from flask_restx import Resource, fields, reqparse, Model
from sqlalchemy import and_, not_, or_
from sqlalchemy import func
from webassets import Environment as AssetsEnvironment, Bundle as AssetsBundle
from webassets.ext.jinja2 import AssetsExtension
from werkzeug.exceptions import BadRequestKeyError
import json

from anyway import utilities, secrets
from anyway.app_and_db import api, get_cors_config
Expand Down Expand Up @@ -57,7 +58,8 @@
City,
Streets,
Comment,
TelegramForwardedMessages
TelegramForwardedMessages,
NewsFlash
)
from anyway.request_params import get_request_params_from_request_values
from anyway.views.news_flash.api import (
Expand All @@ -70,6 +72,7 @@
DEFAULT_LIMIT_REQ_PARAMETER,
DEFAULT_OFFSET_REQ_PARAMETER,
DEFAULT_NUMBER_OF_YEARS_AGO,
search_newsflashes_by_resolution
)
from anyway.views.schools.api import (
schools_description_api,
Expand Down Expand Up @@ -1128,6 +1131,17 @@ def acc_in_area_query():
help="limit number of retrieved items to given limit",
)

newsflash_fields = tuple(column.name for column in NewsFlash.__table__.columns)
nfbr_parser = reqparse.RequestParser()
nfbr_parser.add_argument("resolutions", type=str, action="append", required=True,
help="List of resolutions to filter by")
nfbr_parser.add_argument("include", type=str, required=True, choices=["True", "False"],
help="Flag to include or exclude the specified resolutions (True\False)")
nfbr_parser.add_argument("limit", type=int, help="Maximum number of records to return")
nfbr_parser.add_argument("fields", type=str, choices=(*newsflash_fields, ""), action="append",
help=f"List of fields to include in the response (Empty array to fetch all).\n"
f"Available values: {', '.join(newsflash_fields)}")


def datetime_to_str(val: datetime.datetime) -> str:
return val.strftime("%Y-%m-%d %H:%M:%S") if isinstance(val, datetime.datetime) else "None"
Expand Down Expand Up @@ -1184,7 +1198,32 @@ def patch(self, news_flash_id):
return update_news_flash_qualifying(news_flash_id)
def options(self, news_flash_id):
return single_news_flash(news_flash_id)



def filter_json_fields(json_data, fields):
return {field: json_data[field] for field in fields if field in json_data}


@api.route("/api/news-flash/by-resolution", methods=["GET"])
class RetrieveNewsFlashByResolution(Resource):
@api.doc("get news flash records by resolution")
@api.expect(nfbr_parser)
@api.response(404, "Parameter value not supported or missing")
@api.response(
200, "Retrieve news-flash items filtered by given parameters", news_flash_list_model
)
def get(self):
args = nfbr_parser.parse_args()
limit = args["limit"] if "limit" in args else None
query = search_newsflashes_by_resolution(db.session, args["resolutions"], args["include"], limit)
res = query.all()
news_flashes_jsons = [n.serialize() for n in res]
if not args["fields"]:
filtered_jsons = news_flashes_jsons
else:
filtered_jsons = [filter_json_fields(json_data, args["fields"]) for json_data in news_flashes_jsons]
return Response(json.dumps(filtered_jsons, default=str), mimetype="application/json")


@api.route("/api/news-flash-new", methods=["GET"])
class RetrieveNewsFlash(Resource):
Expand Down
2 changes: 1 addition & 1 deletion anyway/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ def set_critical(
resolution = BE_CONST.ResolutionCategories(self.resolution)
end_time = last_accident_date.to_pydatetime().date()
start_time = datetime.date(end_time.year + 1 - years_before, 1, 1)
location_info = LocationInfo()
location_info: LocationInfo = {}
if resolution == BE_CONST.ResolutionCategories.SUBURBAN_ROAD:
location_info["road1"] = self.road1
location_info["road_segment_id"] = self.road_segment_id
Expand Down
16 changes: 16 additions & 0 deletions anyway/views/news_flash/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,19 @@ def get_downloaded_data(format, years_ago):

headers = { 'Content-Disposition': f'attachment; filename=anyway_download_{datetime.datetime.now().strftime("%d_%m_%Y_%H_%M_%S")}.{file_type}' }
return Response(buffer.getvalue(), mimetype=mimetype, headers=headers)


def search_newsflashes_by_resolution(session, resolutions, include_resolutions, limit=None):
query = session.query(NewsFlash)
if include_resolutions:
query = query.filter(NewsFlash.resolution.in_(resolutions))
else:
query = query.filter(NewsFlash.resolution.notin_(resolutions))

query = query.filter(NewsFlash.accident == True) \
.order_by(NewsFlash.date.desc())

limit = DEFAULT_LIMIT_REQ_PARAMETER if not limit else limit
query = query.limit(limit)

return query
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from flask_sqlalchemy import BaseQuery
from sqlalchemy import func, asc
# noinspection PyProtectedMember
from flask_babel import _

from anyway.app_and_db import db
Expand Down Expand Up @@ -34,6 +35,8 @@ def filter_and_group_injured_count_per_age_group(
) -> Dict[str, Dict[int, int]]:
start_time = request_params.start_time
end_time = request_params.end_time
cache_key = None # prevent pylint warning

if request_params.resolution == BE_CONST.ResolutionCategories.STREET:
involve_yishuv_name = request_params.location_info["yishuv_name"]
street1_hebrew = request_params.location_info["street1_hebrew"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def is_included(self) -> bool:
segment_others = item["count"]
else:
raise ValueError
segment_total = segment_h2h + segment_others
segment_total = segment_h2h + segment_others # pylint: disable=E0606
all_items = self.items[self.ALL_ROADS_SUBTITLE]
for item in all_items:
if item["desc"] == "frontal":
Expand All @@ -116,7 +116,7 @@ def is_included(self) -> bool:
all_others = item["count"]
else:
raise ValueError
all_total = all_h2h + all_others
all_total = all_h2h + all_others # pylint: disable=E0606
return segment_h2h > 0 and (segment_h2h / segment_total) > all_h2h / all_total


Expand Down
18 changes: 12 additions & 6 deletions anyway/widgets/segment_junctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ def __calc_fill_segment_junctions():
if t.road not in rkj:
rkj[t.road] = {}
road_last_junction_km[t.road] = -1
rkj[t.road][t.km] = t.non_urban_intersection
if t.km not in rkj[t.road]:
rkj[t.road][t.km] = []
else:
logging.debug(
f"Two junctions in same location:road:{t.road},km:{t.km},1:"
f"{rkj[t.road][t.km]},2:{t.non_urban_intersection}."
)
rkj[t.road][t.km].append(t.non_urban_intersection)
if road_last_junction_km[t.road] < t.km:
road_last_junction_km[t.road] = t.km
tmp: List[RoadSegments] = db.session.query(RoadSegments).all()
Expand All @@ -45,11 +52,10 @@ def __calc_fill_segment_junctions():
if seg.road not in rkj:
logging.warning(f"No junctions in road {seg.road}.")
continue
junctions = [
rkj[seg.road][km]
for km in rkj[seg.road].keys()
if is_junction_km_in_segment(km, seg, road_last_junction_km.get(seg.road))
]
junctions = []
for km in rkj[seg.road].keys():
if is_junction_km_in_segment(km, seg, road_last_junction_km.get(seg.road)):
junctions.extend(rkj[seg.road][km])
res[seg_id] = junctions
return res

Expand Down
8 changes: 7 additions & 1 deletion tests/test_infographics_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ class TestInfographicsUtilsCase(unittest.TestCase):
rjks = [
RoadJunctionKM(road=1, non_urban_intersection=1, km=1.0),
RoadJunctionKM(road=1, non_urban_intersection=2, km=2.0),
RoadJunctionKM(road=1, non_urban_intersection=22, km=2.0),
RoadJunctionKM(road=1, non_urban_intersection=3, km=3.0),
RoadJunctionKM(road=10, non_urban_intersection=1, km=10.0),
RoadJunctionKM(road=20, non_urban_intersection=2, km=10.0),
RoadJunctionKM(road=20, non_urban_intersection=22, km=10.0),
RoadJunctionKM(road=30, non_urban_intersection=3, km=10.0),
]
segments = [
Expand Down Expand Up @@ -90,7 +92,11 @@ def test_get_segment_junctions(self, db):
actual = sg.get_segment_junctions(2)
self.assertEqual([1], actual, "2")
actual = sg.get_segment_junctions(3)
self.assertEqual([1, 2, 3], actual, "3")
self.assertEqual([1, 2, 22, 3], actual, "3")
actual = sg.get_segment_junctions(4)
self.assertEqual([3], actual, "4")
actual = sg.get_segment_junctions(21)
self.assertEqual([2, 22], actual, "5")

def test_get_filter_expression(self):
actual = get_filter_expression(AccidentMarkerView, "road_segment_name", "seg1")
Expand Down
1 change: 1 addition & 0 deletions tests/test_news_flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def test_twitter_parse():
for k in raw_fields:
assert getattr(actual, k) == getattr(expected, k)

actual.set_critical()
actual.accident = classify_tweets(actual.description)
assert actual.accident == expected.accident

Expand Down
Loading