Skip to content

Commit 768c993

Browse files
authored
Merge pull request #554 from CodeForPhilly/538-run_dates
API for last update times
2 parents 0931219 + 86e6d9b commit 768c993

17 files changed

+461
-574
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""rmv shifts uniqueness constraint
2+
3+
Revision ID: d80cb6df0fa2
4+
Revises: 90f471ac445c
5+
Create Date: 2023-03-18 16:22:23.282568
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'd80cb6df0fa2'
14+
down_revision = '90f471ac445c'
15+
branch_labels = None
16+
depends_on = None
17+
18+
# It's probably more likely that a duplicate row is actually a real shift with a bad (dupe)
19+
# like Saturday, Saturday instead of Saturday, Sunday
20+
# We really care about last shift so this is not critical
21+
22+
def upgrade():
23+
op.drop_constraint( "uq_shift", "volgisticsshifts")
24+
25+
def downgrade():
26+
# op.create_unique_constraint( "uq_shift", "volgisticsshifts", ["volg_id", "assignment", "from_date", "hours"] )
27+
# This will fail if you have any dupes
28+
# running
29+
# ALTER TABLE "public"."volgisticsshifts" ADD CONSTRAINT "uq_shift" UNIQUE( "volg_id", "assignment", "from_date", "hours" );
30+
# will fail and tell you of any dupes so you can fix
31+
32+
pass

src/server/api/API_ingest/ingest_sources_from_api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from api.API_ingest import shelterluv_people, salesforce_contacts, sl_animal_events
22
import structlog
3+
4+
from pipeline.log_db import log_shelterluv_update
35
logger = structlog.get_logger()
46

57
def start():
@@ -16,6 +18,7 @@ def start():
1618
logger.debug(" Fetching Shelterluv events")
1719
sle_count = sl_animal_events.store_all_animals_and_events()
1820
logger.debug(" Finished fetching Shelterluv events - %d records" , sle_count)
21+
log_shelterluv_update()
1922

2023
logger.debug("Finished fetching raw data from different API sources")
2124

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from sqlalchemy import Table, MetaData
2+
from sqlalchemy.orm import sessionmaker
3+
4+
from config import engine
5+
6+
import structlog
7+
logger = structlog.get_logger()
8+
9+
def insert_volgistics_people(row_list):
10+
11+
row_count = 0
12+
try:
13+
Session = sessionmaker(engine)
14+
session = Session()
15+
metadata = MetaData()
16+
volg_table = Table("volgistics", metadata, autoload=True, autoload_with=engine)
17+
18+
result = session.execute("TRUNCATE table volgistics;")
19+
ret = session.execute(volg_table.insert(row_list))
20+
21+
row_count = ret.rowcount
22+
23+
session.commit() # Commit all inserted rows
24+
session.close()
25+
except Exception as e:
26+
row_count = 0
27+
logger.error("Exception inserting volgistics people")
28+
logger.exception(e)
29+
return row_count
30+
31+
32+
def insert_volgistics_shifts(row_list):
33+
34+
row_count = 0
35+
try:
36+
Session = sessionmaker(engine)
37+
session = Session()
38+
metadata = MetaData()
39+
volg_table = Table("volgisticsshifts", metadata, autoload=True, autoload_with=engine)
40+
41+
result = session.execute("TRUNCATE table volgisticsshifts;")
42+
ret = session.execute(volg_table.insert(row_list))
43+
44+
row_count = ret.rowcount
45+
46+
session.commit() # Commit all inserted rows
47+
session.close()
48+
except Exception as e:
49+
row_count = 0
50+
logger.error("Exception inserting volgistics shifts")
51+
logger.exception(e.pgerror)
52+
return row_count

src/server/api/admin_api.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,26 @@ def start_job():
231231
return job_id
232232

233233

234+
@admin_api.route("/api/get_last_runs", methods=["GET"])
235+
#@jwt_ops.admin_required
236+
def get_run_logs():
237+
""" Get the timestamps of the last update runs"""
238+
239+
with engine.connect() as connection:
240+
q = text("""select keycol,valcol from kv_unique where keycol like '%_update'; """)
241+
result = connection.execute(q)
242+
243+
if result.rowcount > 0:
244+
rows = result.fetchall()
245+
246+
row_list = []
247+
248+
for row in rows:
249+
row_dict = row._mapping
250+
row_list.append({row_dict['keycol'] : row_dict['valcol']})
251+
252+
return jsonify(row_list)
253+
234254

235255
def insert_rfm_scores(score_list):
236256
"""Take a list of (matching_id, score) and insert into the

src/server/api/file_uploader.py

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import pandas as pd
21
from config import engine
32
from donations_importer import validate_import_sfd
43
from flask import current_app
54
from models import ManualMatches, SalesForceContacts, ShelterluvPeople, Volgistics
6-
from shifts_importer import validate_import_vs
5+
from pipeline.log_db import log_volgistics_update
6+
from volgistics_importer import open_volgistics, validate_import_vs, volgistics_people_import
77
from werkzeug.utils import secure_filename
8-
98
import structlog
109
logger = structlog.get_logger()
1110

@@ -26,39 +25,17 @@ def determine_upload_type(file, file_extension, conn):
2625
# automatically pulling from vendor APIs directly, in which case we'd know
2726
# what kind of data we had.
2827
if file_extension == "csv":
29-
logger.debug("File extension is CSV")
30-
df = pd.read_csv(file, dtype="string")
31-
32-
if {"salesforcecontacts", "volgistics", "shelterluvpeople"}.issubset(df.columns):
33-
logger.debug("File appears to be salesforcecontacts, volgistics, or shelterluvpeople (manual)")
34-
ManualMatches.insert_from_df(df, conn)
35-
return
36-
elif {"Animal_ids", "Internal-ID"}.issubset(df.columns):
37-
logger.debug("File appears to be shelterluvpeople")
38-
ShelterluvPeople.insert_from_df(df, conn)
39-
return
28+
logger.warn("%s: We no longer support CSV files", file.filename)
29+
return
4030

4131
if file_extension == "xlsx":
42-
excel_file = pd.ExcelFile(file)
43-
if {"Master", "Service"}.issubset(excel_file.sheet_names):
44-
logger.debug("File appears to be Volgistics")
45-
# Volgistics
46-
validate_import_vs(file, conn)
47-
Volgistics.insert_from_file(excel_file, conn)
48-
return
49-
50-
df = pd.read_excel(excel_file)
51-
if "Contact ID 18" in df.columns:
52-
# Salesforce something-or-other
53-
if "Amount" in df.columns:
54-
# Salesforce donations
55-
logger.debug("File appears to be Salesforce donations")
56-
validate_import_sfd(file, conn)
57-
return
58-
else:
59-
# Salesforce contacts
60-
logger.debug("File appears to be Salesforce contacts")
61-
SalesForceContacts.insert_from_file_df(df, conn)
62-
return
32+
# Assume it's Volgistics
33+
workbook = open_volgistics(file)
34+
if workbook:
35+
validate_import_vs(workbook)
36+
volgistics_people_import(workbook)
37+
workbook.close()
38+
log_volgistics_update()
39+
return
6340

6441
logger.error("Don't know how to process file: %s", file.filename)

src/server/api/internal_api.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from api.API_ingest import ingest_sources_from_api, salesforce_contacts
77
from api.api import internal_api
8-
from rfm_funcs.create_scores import create_scores
8+
# from rfm_funcs.create_scores import create_scores
99
from api.API_ingest import updated_data
1010

1111
logger = structlog.get_logger()
@@ -37,12 +37,12 @@ def ingest_raw_data():
3737
return jsonify({'outcome': 'OK'}), 200
3838

3939

40-
@internal_api.route("/api/internal/create_scores", methods=["GET"])
41-
def hit_create_scores():
42-
logger.info("Hitting create_scores() ")
43-
tuple_count = create_scores()
44-
logger.info("create_scores() processed %s scores", str(tuple_count) )
45-
return jsonify(200)
40+
# @internal_api.route("/api/internal/create_scores", methods=["GET"])
41+
# def hit_create_scores():
42+
# logger.info("Hitting create_scores() ")
43+
# tuple_count = create_scores()
44+
# logger.info("create_scores() processed %s scores", str(tuple_count) )
45+
# return jsonify(200)
4646

4747

4848
@internal_api.route("/api/internal/get_updated_data", methods=["GET"])

src/server/models.py

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import re
33
from itertools import combinations
44

5-
import pandas as pd
65
import sqlalchemy as sa
76
from sqlalchemy import (
87
Boolean,
@@ -296,35 +295,35 @@ class Volgistics(Base):
296295
json = Column(JSONB)
297296
created_date = Column(DateTime, default=datetime.datetime.utcnow)
298297

299-
@classmethod
300-
def insert_from_file(cls, xl_file, conn):
301-
df = pd.read_excel(xl_file, sheet_name="Master")
302-
303-
column_translation = get_source_column_translation(cls)
304-
df = df[column_translation.keys()]
305-
df = df.rename(columns=column_translation)
306-
307-
df["home"] = df["home"].apply(normalize_phone_number)
308-
df["work"] = df["work"].apply(normalize_phone_number)
309-
df["cell"] = df["cell"].apply(normalize_phone_number)
310-
311-
dedup_on = [col for col in cls.__table__.columns if col.name in df.columns]
312-
df["created_date"] = datetime.datetime.utcnow()
313-
df.to_sql(
314-
cls.__tablename__,
315-
conn,
316-
if_exists="append",
317-
index=False,
318-
)
319-
conn.execute(
320-
dedup_consecutive(
321-
cls.__table__,
322-
unique_id=cls._id,
323-
id=cls.number,
324-
order_by=cls.created_date,
325-
dedup_on=tuple_(*dedup_on),
326-
)
327-
)
298+
# @classmethod
299+
# def insert_from_file(cls, xl_file, conn):
300+
# df = pd.read_excel(xl_file, sheet_name="Master")
301+
302+
# column_translation = get_source_column_translation(cls)
303+
# df = df[column_translation.keys()]
304+
# df = df.rename(columns=column_translation)
305+
306+
# df["home"] = df["home"].apply(normalize_phone_number)
307+
# df["work"] = df["work"].apply(normalize_phone_number)
308+
# df["cell"] = df["cell"].apply(normalize_phone_number)
309+
310+
# dedup_on = [col for col in cls.__table__.columns if col.name in df.columns]
311+
# df["created_date"] = datetime.datetime.utcnow()
312+
# df.to_sql(
313+
# cls.__tablename__,
314+
# conn,
315+
# if_exists="append",
316+
# index=False,
317+
# )
318+
# conn.execute(
319+
# dedup_consecutive(
320+
# cls.__table__,
321+
# unique_id=cls._id,
322+
# id=cls.number,
323+
# order_by=cls.created_date,
324+
# dedup_on=tuple_(*dedup_on),
325+
# )
326+
# )
328327

329328
@classmethod
330329
def insert_into_pdp_contacts(cls):
@@ -356,29 +355,29 @@ class ManualMatches(Base):
356355
source_type_2 = Column(String, primary_key=True)
357356
source_id_2 = Column(String, primary_key=True)
358357

359-
@classmethod
360-
def insert_from_df(cls, df, conn):
361-
# Our input csv has columns like "salesforcecontacts," "volgistics," and
362-
# "shelterluvpeople," where two columns are non-null if there is an
363-
# association between those two ids. We massage this table into one that
364-
# is easier to join on.
358+
# @classmethod
359+
# def insert_from_df(cls, df, conn):
360+
# # Our input csv has columns like "salesforcecontacts," "volgistics," and
361+
# # "shelterluvpeople," where two columns are non-null if there is an
362+
# # association between those two ids. We massage this table into one that
363+
# # is easier to join on.
365364

366-
match_dicts = df.to_dict(orient="records")
367-
368-
matched_pairs = []
369-
for match in match_dicts:
370-
non_nulls = {k: v for (k, v) in match.items() if not pd.isna(v)}
371-
for ((st1, sid1), (st2, sid2)) in combinations(non_nulls.items(), 2):
372-
matched_pairs.append(
373-
{
374-
"source_type_1": st1,
375-
"source_id_1": sid1,
376-
"source_type_2": st2,
377-
"source_id_2": sid2,
378-
}
379-
)
380-
381-
conn.execute(insert(cls).values(matched_pairs).on_conflict_do_nothing())
365+
# match_dicts = df.to_dict(orient="records")
366+
367+
# matched_pairs = []
368+
# for match in match_dicts:
369+
# non_nulls = {k: v for (k, v) in match.items() if not pd.isna(v)}
370+
# for ((st1, sid1), (st2, sid2)) in combinations(non_nulls.items(), 2):
371+
# matched_pairs.append(
372+
# {
373+
# "source_type_1": st1,
374+
# "source_id_1": sid1,
375+
# "source_type_2": st2,
376+
# "source_id_2": sid2,
377+
# }
378+
# )
379+
380+
# conn.execute(insert(cls).values(matched_pairs).on_conflict_do_nothing())
382381

383382
class SalesforceDonations(Base):
384383
__tablename__ = "salesforcedonations"

0 commit comments

Comments
 (0)