Przeglądaj źródła

add tool to plot historic prices

Daniel Sheffield 2 lat temu
rodzic
commit
c42d63e0b7
5 zmienionych plików z 90 dodań i 23 usunięć
  1. 50 0
      app/activities/Plot.py
  2. 1 2
      app/activities/PriceCheck.py
  3. 11 10
      app/db_utils.py
  4. 27 11
      app/price_view.py
  5. 1 0
      requirements.txt

+ 50 - 0
app/activities/Plot.py

@@ -0,0 +1,50 @@
+from app.db_utils import QueryManager
+from app.db_utils import QueryManager, display_mapper
+import seaborn as sns
+import seaborn as sns
+import matplotlib.pyplot as plt
+import os
+from sqlite3 import Cursor
+import psycopg
+from db_credentials import HOST, PASSWORD
+host = f'host={HOST}'
+password = f'password={PASSWORD}'
+user = os.getenv('USER')
+conn = psycopg.connect(f"{host} dbname=grocery user={user} {password}")
+cur: Cursor = conn.cursor()
+cur.execute("BEGIN")
+
+query_manager = QueryManager(cur, display_mapper)
+fields = dict()
+for k in ('group', 'category', 'product', 'unit'):
+    options = query_manager.unique_suggestions(k, **fields)
+    names = [ o for o in options ]
+    matches = '\t'.join(names)
+    print(f'{k.title()} names: {matches}')
+    fields[k] = input(f'{k.title()}: ')
+    if (fields[k] == ''):
+        continue
+    options = query_manager.unique_suggestions(k, **fields)
+    while (len(options) != 1):
+        options = query_manager.unique_suggestions(k, **fields)
+        matches = '\t'.join(options)
+        print(f'Matches ({k}): {matches}')
+        fields[k] += input(f'{k.title()}: {fields[k]}')
+    fields[k] = options[0] if options[0] in names else ''
+    if fields[k] == '':
+        print(f'Ignoring {k} {options[0]} as it does not exist')
+    else:
+        print(f'Selected {k}: {fields[k]}')
+    print()
+
+fields = dict((k,v or None) for k,v in fields.items())
+
+print(fields)
+d = query_manager.get_historic_prices_data(fields['unit'] or 'kg', **dict((k,v) for k,v in fields.items() if k != 'unit'))
+sns.set_theme()
+d = d.pivot_table(index=['ts_raw',], columns=['product',], values=['$/unit'], aggfunc='mean')
+d.columns = d.columns.droplevel()
+print(d.info())
+print(d)
+sns.lineplot(data=d, markers=True)
+plt.show()

+ 1 - 2
app/activities/PriceCheck.py

@@ -17,7 +17,6 @@ from ..widgets import (AutoCompleteEdit, AutoCompleteFloatEdit,
                        AutoCompletePopUp, FocusWidget, NoTabCheckBox)
 from . import ActivityManager, show_or_exit
 
-
 class PriceCheck(FocusWidget):
 
     def keypress(self, size, key):
@@ -56,7 +55,7 @@ class PriceCheck(FocusWidget):
             self.data.update({
                 name: value,
             })
-        self.update_historic_prices()
+        #self.update_historic_prices()
 
     def clear(self):
         for k in self.data:

+ 11 - 10
app/db_utils.py

@@ -119,32 +119,33 @@ def get_insert_product_statement(product, category, group):
     return f'CALL insert_product($prod${product}$prod$, $category${category}$category$, $group${group}$group$)'
 
 class QueryManager(object):
-    
+
     def __init__(self, cursor: Cursor, display: Callable[
         [Any, str], str
     ]):
         self.display = display
         self.cursor = cursor
-    
-    def get_historic_prices(self, rating_cb, sort, product, unit, organic=None, limit=None):
-        statement = get_historic_prices_statement(sort, product, unit, organic=organic, limit=limit)
+
+    def get_historic_prices_data(self, unit, sort=None, product=None, category=None, group=None, organic=None, limit=None):
+        statement = get_historic_prices_statement(unit, sort=None, product=product, category=category, group=group, organic=organic, limit=limit)
         #print(self.cursor.mogrify(statement).decode('utf-8'))
         #input()
-        
-        df = pd.DataFrame(get_data(self.cursor, statement, self.display))
+        return pd.DataFrame(get_data(self.cursor, statement, self.display))
+
+    def get_historic_prices(self, rating_cb, sort, product, unit, organic=None, limit=None):
+        df = self.get_historic_prices_data(unit, sort=sort, product=product, organic=organic, limit=limit)
         if df.empty:
             rating_cb(None, None, None)
             return ''
-        
         _avg, _min, _max = [ x for x in df[['avg','min','max']].iloc[0].apply(float) ]
         rating_cb(_avg, _min, _max)
-        
+
         return df.drop(labels=[
-            'id', 'avg', 'min', 'max'
+            'id', 'avg', 'min', 'max', 'ts_raw', 'product', 'category', 'group'
         ], axis=1).to_string(header=[
             'Date', 'Store', '$/unit', 'Org',
         ], justify='justify-all', max_colwidth=16, index=False)
-    
+
     def get_session_transactions(self, date, store):
         statement = get_session_transactions_statement(
             parse_time(date), store, full_name=True, exact_time=True

+ 27 - 11
app/price_view.py

@@ -15,13 +15,26 @@ from collections import (
     OrderedDict,
 )
 
-def get_where(product, unit, organic=None, limit='90 days'):
+def get_where(unit, product=None, category=None, group=None, organic=None, limit='90 days'):
     where = [ ]
-    where.append(SQL(' ').join([
-        Identifier('products', 'name'),
-        SQL('='),
-        Literal(product)
-    ]))
+    if product is not None:
+        where.append(SQL(' ').join([
+            Identifier('products', 'name'),
+            SQL('='),
+            Literal(product)
+        ]))
+    if category is not None:
+        where.append(SQL(' ').join([
+            Identifier('categories', 'name'),
+            SQL('='),
+            Literal(category)
+        ]))
+    if group is not None:
+        where.append(SQL(' ').join([
+            Identifier('groups', 'name'),
+            SQL('='),
+            Literal(group)
+        ]))
     if organic is not None:
         where.append(SQL(' ').join([
             Identifier('organic'),
@@ -46,7 +59,7 @@ def get_where(product, unit, organic=None, limit='90 days'):
         SQL("\n  AND ").join(where),
     ])
 
-def get_historic_prices_statement(sort, product, unit, organic=None, limit='90 days'):
+def get_historic_prices_statement(unit, sort=None, product=None, category=None, group=None, organic=None, limit='90 days'):
     partition = f"(PARTITION BY {'organic,' if organic is not None else ''} product_id, unit_id)"
     organic_sort = f"{'organic,' if organic is not None else ''}"
     _with = SQL("""WITH conv AS (
@@ -61,12 +74,16 @@ def get_historic_prices_statement(sort, product, unit, organic=None, limit='90 d
 )""")
     select = OrderedDict([
         ('id', Identifier('transactions','id')),
+        ('ts_raw', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
         ('%d/%m/%y %_I%P', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
         ('code', Identifier('stores', 'code')),
         ('$/unit', SQL("""TRUNC(price / (quantity * factor), 4)""")),
         ('avg', SQL(f"""TRUNC(sum(price) OVER {partition} / sum(quantity * factor) OVER {partition}, 4)""")),
         ('min', SQL(f"""TRUNC(min(price / (quantity * factor)) OVER {partition}, 4)""")),
         ('max', SQL(f"""TRUNC(max(price / (quantity * factor)) OVER {partition}, 4)""")),
+        ('product', Identifier('products','name')),
+        ('category', Identifier('categories', 'name')),
+        ('group', Identifier('groups', 'name')),
         ('organic', Identifier('organic')),
     ])
     statement = SQL('\n').join([
@@ -114,12 +131,11 @@ def get_historic_prices_statement(sort, product, unit, organic=None, limit='90 d
                     key=Identifier('id'),
                     index=Identifier('group_id')
                 ),
-                
             ]),
         ]),
-        get_where(product, unit, organic=organic, limit=limit),
-        SQL('ORDER BY {organic_sort} {sort} {direction}, code, "$/unit" ASC, ts DESC').format(
-            sort=Identifier(sort),
+        get_where(unit, product=product, category=category, group=group, organic=organic, limit=limit),
+        SQL('ORDER BY {organic_sort} {sort} code, product, category, "group", "$/unit" ASC, ts DESC').format(
+            sort=SQL(f'{Identifier(sort)} {direction},' if sort is not None else ''),
             direction=SQL('DESC' if sort == 'ts' else 'ASC'),
             organic_sort=SQL(organic_sort),
         ),

+ 1 - 0
requirements.txt

@@ -6,3 +6,4 @@ psycopg
 faker
 pandas
 numpy
+seaborn