Эх сурвалжийг харах

Instant update on changes and all fields working

Daniel Sheffield 3 жил өмнө
parent
commit
671c1ce68b
3 өөрчлөгдсөн 146 нэмэгдсэн , 81 устгасан
  1. 10 5
      db_utils.py
  2. 133 73
      price_check.py
  3. 3 3
      price_view.py

+ 10 - 5
db_utils.py

@@ -103,19 +103,24 @@ class QueryManager(object):
         self.cursor = cursor
         self.activity_manager = activity_manager
     
-    def get_historic_prices(self, sort, product, unit, organic=None, limit='90 days'):
+    def get_historic_prices(self, rating_cb, sort, product, unit, organic=None, limit='90 days'):
         statement = get_historic_prices_statement(sort, product, unit, organic=organic, limit=limit)
         #print(self.cursor.mogrify(statement).decode('utf-8'))
         #input()
+        
         df = pd.DataFrame(get_data(self.cursor, statement, self.display))
         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',
+            'id', 'avg', 'min', 'max'
         ], axis=1).to_string(header=[
-            'Date', 'Store', '$/unit', 'Avg.', 'Min', 'Max',
-            'Group', 'Category', 'Product', 'Organic',
-        ], justify='justify-all', max_colwidth=60, index=False)
+            'Date', 'Store', '$/unit', 'Org',
+        ], justify='justify-all', max_colwidth=16, index=False)
     
     def get_session_transactions(self, date, store):
         return get_session_transactions(

+ 133 - 73
price_check.py

@@ -10,6 +10,7 @@ import itertools
 import sys
 import urwid
 import pandas as pd
+import numpy as np
 from widgets import COPYRIGHT
 from db_utils import QueryManager
 from dateutil.parser import parse as parse_time
@@ -21,7 +22,7 @@ from widgets import (
     ActivityManager,
     #_set_focus_path,
 )
-
+from decimal import Decimal, InvalidOperation
 from collections import (
     OrderedDict,
 )
@@ -48,8 +49,8 @@ except:
 
 palette = [
     ('banner', 'light gray', 'dark red'),
-    ('streak', 'light red', 'dark gray'),
-    ('bg', 'light red', 'black'),
+    ('streak', 'light gray', 'black'),
+    ('bg', 'dark red', 'black'),
 ]
 
 top_pane = [
@@ -61,7 +62,9 @@ top_pane = [
 left_pane = [
     'product',
     'organic',
-    None,
+    'div',
+    'spread',
+    'rating',
 ]
 right_pane = [
     'unit',
@@ -69,21 +72,15 @@ right_pane = [
     'price',
 ]
 bottom_pane = [
-    #'rating',
     'dbview',
 ]
 
 inputs = filter(
-    lambda x: x is not None,
+    lambda x: x is not None and x not in ('rating','spread', 'div'),
     itertools.chain(
         left_pane, right_pane
     )
 )
-inputs_layout = [
-    ['product', 'unit', ],
-    ['organic', 'quantity', ],
-    ['price', None, ],
-]
 
 display_map = {
     'price': lambda x: f"{x:.4f}",
@@ -191,102 +188,147 @@ class PriceCheck(urwid.WidgetPlaceholder):
         else:
             return super().keypress(size, key)
 
-    def _init_data(self, fields):
-        self.data = OrderedDict([
-            (k, '') for k in fields
-        ])
-        self.clear()
-
     def apply_choice(self, name, widget, value):
-        self._apply_changes(name, value)
+        self.apply_changes(name, widget, value)
         for k,v in self.data.items():
             if k == name or v:
                 continue
             options = self.query_manager.unique_suggestions(k, **self.data)
             if len(options) == 1:
-                self._apply_changes(k, list(options)[0])
+                self.apply_changes(k, widget, list(options)[0])
 
-    def _apply_changes(self, name, value):
-        self.data.update({
-            name: value,
-        })
-    
-    def apply_changes(self, name):
-        return lambda w,x: self._apply_changes(name, x)
-
-    def apply_organic_state(self, w, state):
-        self.data['organic'] = repr(state).lower()
+    def apply_changes(self, name, widget, value):
+        if name in self.data:
+            
+            self.data.update({
+                name: value,
+            })
+        self.update_historic_prices()
 
     def clear(self):
         for k in self.data:
-            if k in ('ts', 'store',):
+            if k == 'organic':
+                self.data[k] = 'mixed'
                 continue
             self.data[k] = ''
-        self.organic_checkbox.set_state('mixed')
+        self.update()
+        return self
     
     def update(self):
         for k in self.edit_fields:
-            self.edit_fields[k].set_edit_text(self.data[k])
+            if self.data[k] != self.edit_fields[k].get_edit_text():
+                self.edit_fields[k].set_edit_text(self.data[k])
+        
+        if self.data['organic'] != self.organic_checkbox.state:
+            self.organic_checkbox.set_state(self.data['organic'])
+        
+        self.update_historic_prices()
         
-        sort = '$/unit' if self.buttons['sort_price'].state else 'ts'
-        #print(self.organic_checkbox.state)
-        #input()
-        organic = None if self.organic_checkbox.state == "mixed" else self.organic_checkbox.state
-        #print(organic)
-        #input()
-        self.text_fields['dbview'].set_text(
-            self.query_manager.get_historic_prices(sort, self.data['product'], self.data['unit'], organic=organic)
-        )
-        self.organic_checkbox.set_state({
-            "'mixed'": 'mixed',
-            'true': True,
-            'false': False,
-        }[self.data['organic']])
         return self
+    
+    def update_rating(self, _avg, _min, _max, price=None, quantity=None):
+        if None in (_avg, _min, _max):
+            return
+        current = None if None in (price, quantity) else float(price/quantity)
+        size = 16
+        chars = ['|', *['-']*(size - 2), '|' ]
+        rating = [' ']*len(chars)
+        _min, _max = min(_min, current or _min), max(_max, current or _max)
+        ls = np.linspace(_min, _max, len(chars))
+
+        for idx, (e, a) in enumerate(zip(ls, ls[1:])):
+            if e <= _avg < a:
+                chars[max(idx-4,0)] = '['
+                chars[min(idx+4,len(chars)-1)] = ']'
+                for _idx, c in enumerate(f'{_avg:>7.2f}'):
+                    chars[max(idx-3+_idx,0)] = c
+            
+            if current is not None and e <= current < a:
+                rating[idx] = '^'
+        
+        self.text_fields['spread'].set_text(f"{_min:>7.2f}{''.join(chars)}{_max:<7.2f}")
+        self.text_fields['rating'].set_text(f"{' '*7}{''.join(rating)}{' '*7}")
+        
+    
+    def update_historic_prices(self):
+        organic = None if self.data['organic'] == 'mixed' else self.data['organic']
+        sort = '$/unit' if self.buttons['sort_price'].state else 'ts'
+        product, unit = self.data['product'] or None, self.data['unit'] or None
+        try: 
+            price = Decimal(self.data['price']) or None
+        except InvalidOperation:
+            price = None
+        
+        try:
+            quantity = Decimal(self.data['quantity']) or None
+        except InvalidOperation:
+            price = None
+        
+        if None not in (sort, product, unit):
+            self.text_fields['dbview'].set_text(
+                self.query_manager.get_historic_prices(
+                    lambda *args: self.update_rating(*args, price=price, quantity=quantity),
+                    sort, product, unit, organic=organic) 
+            )
 
     def __init__(self, query_manager, fields,
         top_pane, left_pane, right_pane, bottom_pane,
         autocomplete_cb):
         super().__init__(urwid.SolidFill(u'/'))
-        self.organic_checkbox = NoTabCheckBox(
-            u"Organic",
-            on_state_change=self.apply_organic_state
-        )
         self.query_manager = query_manager
-        self._init_data(fields)
         self.top_pane = top_pane
         self.left_pane = left_pane
         self.right_pane = right_pane
         self.bottom_pane = bottom_pane
         self.edit_fields = OrderedDict()
         self.text_fields = OrderedDict()
-        for k in self.data:
+        self.data = OrderedDict()
+        self.organic_checkbox = NoTabCheckBox(
+            u"Organic",
+            state='mixed',
+        )
+        urwid.connect_signal(self.organic_checkbox, 'change', lambda w,v: self.apply_changes('organic', w, v))
+        
+        fields = [f for f in fields ]
+        for k in fields:
             if k in self.right_pane and k != 'unit':
                 ef = AutoCompleteFloatEdit(('bg', k), apply_change_func=lambda name: autocomplete_cb(name, self.data))
             elif k != 'organic':
                 ef = AutoCompleteEdit(('bg', k), apply_change_func=lambda name: autocomplete_cb(name, self.data))
-            else:
+            elif k == 'organic':
+                self.data[k] = 'mixed'
                 continue
             
-            ef.set_edit_text(self.data[k])
-            urwid.connect_signal(ef, 'change', self.apply_changes(k))
+            self.data[k] = ''
+            urwid.connect_signal(ef, 'change', lambda w,v,name=k: self.apply_changes(name, w, v))
             self.edit_fields[k] = ef
-    
-        header = urwid.Text(u'Price Check', 'center')
-        _copyright = urwid.Text(COPYRIGHT, 'center')
+        
         self.buttons = OrderedDict()
         group = []
         for k in top_pane:
             if k != 'sort':
-                self.buttons.update({ k:  urwid.Button(('streak', f'{k}')) })
+                self.buttons.update({ k:  urwid.Button(('streak', f'{k.title()}')) })
                 continue
-            self.buttons.update({ f'{k}_price':  urwid.RadioButton(group, ('streak', u'Best Price'), state="first True",
-                on_state_change=None, user_data=[ self.buttons[b] for b in self.buttons if 'sort' in b ] )})
-            self.buttons.update({ f'{k}_date':  urwid.RadioButton(group, ('streak', u'Last Price'), state="first True",
-                on_state_change=None, user_data=[ self.buttons[b] for b in self.buttons if 'sort' in b ] )})
+            self.buttons.update({ f'{k}_price':  urwid.RadioButton(group, ('streak', u'Best'), state="first True") })
+            self.buttons.update({ f'{k}_date':  urwid.RadioButton(group, ('streak', u'Last'), state="first True") })
+            for button in group:
+                urwid.connect_signal(button, 'postchange', lambda *args: self.update_historic_prices())
+        
+        urwid.connect_signal(self.buttons['clear'], 'click', lambda x: self.clear().update())
+        urwid.connect_signal(self.buttons['exit'], 'click', lambda x: show_or_exit('esc'))
         
-        urwid.connect_signal(self.buttons['clear'], 'click', lambda: None)
-        urwid.connect_signal(self.buttons['exit'], 'click', lambda: None)
+        dbview, spread, rating = [ urwid.Text('') for i in range(0,3) ]
+        
+        self.text_fields.update({
+            'dbview': dbview,
+            'spread': spread,
+            'rating': rating,
+        })
+        
+        self.clear()
+        
+        header = urwid.Text(u'Price Check', 'center')
+        _copyright = urwid.Text(COPYRIGHT, 'center')
         
         banner = urwid.Pile([
             urwid.Padding(header, 'center', width=('relative', 100)),
@@ -297,29 +339,47 @@ class PriceCheck(urwid.WidgetPlaceholder):
             (k, urwid.LineBox(urwid.AttrMap(self.edit_fields[k], 'streak'), title=k.title(), title_align='left')) for k in self.edit_fields
         ])
         fields['organic'] = urwid.LineBox(urwid.AttrMap(self.organic_checkbox, 'bg'))
-        dbview = urwid.Text('')
-        self.text_fields.update({'dbview': dbview})
         fields.update({
             'dbview': urwid.LineBox(
                 urwid.AttrMap(dbview, 'streak'),
                 title="Historic Prices",
                 title_align='center',
-            )
+            ),
+            'div': urwid.Divider(),
+            'spread': spread,
+            'rating': rating,
         })
-        
-        right_pane_widget = (12, urwid.Pile(map(
+
+        right_pane_widget = (16, urwid.Pile(map(
             lambda x: fields[x] if x is not None else urwid.Divider(),
             self.right_pane
         )))
-        left_pane_widget = urwid.Pile(map(
+        left_pane_widget = (urwid.Pile(map(
             lambda x: fields[x] if x is not None else urwid.Divider(),
             self.left_pane
-        ))
+        )))
         
         widget = urwid.Pile([
             banner,
             urwid.Divider(),
-            urwid.Columns([v for v in self.buttons.values()],
+            urwid.Columns(
+                [
+                    (9, urwid.Pile([
+                        urwid.Divider(),
+                        urwid.AttrMap(self.buttons['clear'], 'streak'),
+                        urwid.Divider(),
+                    ])),
+                    urwid.LineBox(
+                        urwid.Columns([ v for k,v in self.buttons.items() if 'sort' in k]),
+                        title="Sort price by",
+                        title_align='left',
+                    ),
+                    (9, urwid.Pile([
+                        urwid.Divider(),
+                        urwid.AttrMap(self.buttons['exit'], 'streak'),
+                        urwid.Divider(),
+                    ]))
+                ],
                 dividechars=2,
             ),
             urwid.Columns((left_pane_widget, right_pane_widget),

+ 3 - 3
price_view.py

@@ -65,9 +65,9 @@ def get_historic_prices_statement(sort, product, unit, organic=None, limit='90 d
         ('min', SQL(f"""TRUNC(min(price/quantity) OVER {partition}, 4)""")),
         ('max', SQL(f"""TRUNC(max(price/quantity) OVER {partition}, 4)""")),
         #('total', SQL("""sum(transactions.price) OVER (PARTITION BY transactions.ts::date ORDER BY transactions.id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)""")),
-        ('group', Identifier('groups','name')),
-        ('category', Identifier('categories','name')),
-        ('product', Identifier('products','name')),
+        #('group', Identifier('groups','name')),
+        #('category', Identifier('categories','name')),
+        #('product', Identifier('products','name')),
         ('organic', Identifier('organic')),
         
         #('price_sum', SQL(f"""sum(price) OVER {partition}""")),