Monday, September 30, 2019

  • Abdulrashid Sadulaev is a 23yr old Russian wrestler. He is the current world champ. He's from Dagestan, just like Khabib.
    • Awesome pin of the defending champ snyder with some offshoot peterson roll all the way to fall? https://www.youtube.com/watch?v=Ryy19IY9fQo.
  • Supercontest. Worked on async score fetching and result calculation today.
    • Design decision: Score.coverer and Pick.points will update in realtime, as the scores change, rather than waiting until the game completes. This allows you to present mid-game date as "what if the games ended right now?" For views that don't want to bank games until they're done, like the leaderboard, you can filter with an additional check for FINISHED_STATUSES.
    • If the app is down for a game, you have to manually enter the information into the db. This is fine. Based on the xml scoresheet that I pull nfl scores from, the only option is live. In order to get past data, I'd have to hit another API.
  • sqlalchemy difference between default and server_default: default is just what python will insert for the value if you don't provide one, server_default is a statement that you can use to generate the value on the db side.
  • Chubbyemu videos are so interesting and informative.
  • The patriots, the chiefs, and the NINERS are the only teams still undefeated.
  • Ordered pink curing salt to make pastrami beef ribs with the next 2 racks. Curing salt is basically just table salt (sodium chloride) mixed with sodium nitrite. The pink doesn't matter, it's just dyed to make sure people don't use it as table salt (although pink Himalayan  salt can be used as table salt, and it's obviously pink too). Regular sea salt can be used to cure/brine meat as well, obviously, it just can't go as long because the nitrites are what protect the meat from bacteria growth during the curing process.

Friday, September 27, 2019

  • Supercontest.
    • Added tons of stats for the history of the league. https://github.com/brianmahlstedt/supercontest/issues/23. Super interesting.
    • The submit button stays up after someone clicks it. That was the problem. Fixed it and removed a few more duplicates.
    • cache.memoize on get_picks was causing problems if you made multiple pick submissions within the span of 10 seconds, because the client would be told about picks that might have been old, desyncing from the actual picks in the backend db. Fixed (by removing the cache for that query). Pick information should be live on request. Scores are different, because the user doesn't write to the scores table from the frontend.
  • Completely cleaned and reorganized my room. Cleared a bunch of space. Including closet, lights, everything.
  • DigitalOcean.
    • Did some research, you can upgrade any of disk/cpu/ram. It adds a decent amount to the monthly cost. Just going from 1GB memory to 2 makes it $10/mo instead of $5.
    • Allowed password login on the droplet for my user, so that if anything goes wrong remotely, I don't need my laptop and ssh key. I can just ssh from any machine.
      • sudo vim /etc/ssh/sshd_config
      • PasswordAuthentication = yes
      • sudo service ssh reload
      • sudo passwd bmahlstedt
    • Confimed that I have access. You can ssh in like usual or you can go to the DO website in the browser and access a console from there. Password is one of my old SpaceX ones.
    • Upgraded from the legacy metrics agent on my droplet to the current one.
  • tmux shortcut reminders.
    • To detach from your current session, just type your hotkey then d
    • To kill your current session, just type your hotkey then : then kill-session
    • If another client has another session in a different window size, just kick them off when you attach with tmux attach -d

Thursday, September 26, 2019

  • I care much less about the API utilization metrics that FMD provides. The most interesting is the API performance. Graph below. I wish they had multi-version api perf like they do for utilization. The results are mostly expected, but still an awesome feature. https://southbaysupercontest.com/dashboard/api_performance.
    • In checking back, you can see multi-version api perf for specific endpoints! Just not an overview of all routes for the application.
  • South Park is back!
  • FMD
    • Verified that it CANNOT do patchfix version resolution. Only major and minor.
    • A major version (3) was released yesterday, which appears to have broken the overview page. 3.0.3 was released a couple hours ago, looks like they've had a few bugfixes. Upgraded, and confirmed the overview is still broken.
  • Maybe this line of questioning will help the lesser perspectives understand the ignorant racism in the NBA owner concession:
    • Is it ok to compare someone to Saddam Hussein, when they have nothing in common with him except that they're Arab?
    • Is it ok to compare someone to Hitler, when they have nothing in common with him except that they're German?
    • Is it ok to compare someone to slaveowners, when they have nothing in common with them except that they're white?
    • It it ok to compare someone to <anyone bad or good>, when they have nothing in common to do with it except that they share the same race?
    • No. None of these comparisons are ok. They're offensive. Doesn't matter the historical subject/object, victim/aggressor, small/large, green/orange, old/young.
  • All daemon threads will be killed when the last non-daemon thread dies. The non-daemons are the main ones. The daemon threads are subservient - they will not hold the main app.
  • Supercontest.
    • Got a few "AttributeError: 'AnonymousUserMixin' object has no attribute 'id'" errors in the production app, with an IP from singapore. This just means they tried to access a route and weren't logged in. The flask_user.current_user object is AnonymousUserMixin when the user is not logged in, so they can't do anything. The error was caught pre-route in the url_value_preprocessor.
    • Right now, I have 94 compiled requirements (including testreqs) and 26 input requirements (not including testreqs).
    • Remember that while 99% of requests make it to the app and you can check the standard uwsgi/flask logs (make logs-prod), there's a small chance that nginx catches something. just run `docker logs -f nginx-proxy` to check.
    • Added text for harner's banner.
    • Tons more work on the redesign of blueprints, routes, url_prefix, url_defaults, url_value_preprocessor.
      • Used request.url_rule.endpoint and request.url_rule.argument to infer all of the  values (in g) that the current view would have access to. Then I conditioned the url_for() navlink builds in the templates based on those.
      • It's all in a great place now. This was frustrating for a few days, but will be much easier to manage/change in the future without breaking things.
      • Closed https://github.com/brianmahlstedt/supercontest/issues/102.
    • Added apscheduler (advanced python scheduler) for the weekly email pick reminder.
    • Not sure how, but two people submitted 10 picks into the db. Both were pairs of the same 5. I deleted them manually, but I'd like to know how this happened.
    • Split up the utilities module into a separate util package, containing distinct modules for the specific type of functionality they provided. It was getting too big.
    • Originally wrote the email reminder with apscheduler, but there's a specific flask-apscheduler that made it a little easier. Don't need to manage the debug app duplication, atexit, or any of that peripheral wrapping.
    • Tested with my email only and a modified cron. Worked! The apscheduled job shows up in the regular flask logs too.
    • Closed the weekly reminder ticket: https://github.com/brianmahlstedt/supercontest/issues/97.
  • In debug mode, flask loads two instances of the application. If your app does something like schedule an external cron job, it will happen twice.
  • Added Google Keep back to phone and to browser bookmark. I was missing the ability to immediately open something and jot a note down, especially while I was out and about, away from my laptop/blog.
  • The `date` command in bash gives the local datetime with tzinfo.
  • The amazon rewards card gives you 3% back at amazon. There's also a "reload" feature on amazon, where you put money in your account (for use at amazon) and it gives you 2%, plus $10 bonus sometimes. I tried reloading WITH my rewards card today. It won't let you.
  • Electric toothbrush finally died, bought a new one. $50. Arrives tomorrow.
  • Went to ortho in Van Nuys, got an MRI referral. Total round trip: 2.5 hours. 1.5hr driving, 1hr in waiting hour, 5 min visit with doctor.
    • So far, the entire chain has been:
      • spacex doctor -> spacex pt -> general doctor -> bloodwork -> general doctor -> xray -> general doctor -> ortho doctor
    • 8 appointments, 8 visits, 0 conclusions. The referral system in medicine is terrible.
    • There will be more. MRI -> ortho doctor -> whatever is next (hopefully not just PT).
  • Idea: there should be a global medical database. You have to fill out the same new patient forms at every clinic you go to, and 99% of it is the same.
    • Every office would have auth keys to a specific subset of the db: general information, surgical history, family history, ortho, respiratory, psychological, whatever. There would be a ton of different tables and only your office's specialty would be accessible by your office.
    • This is just chrome holding all your passwords. They're collocated in a higher layer, the browser, and then every site uses only the ones it needs (and can only access the ones it needs).
    • This would make the patient experience easier, would make referrals easier, would make everything easier.
    • You could also do some machine learning once all the data was collocated. I guarantee there are correlations we don't see yet because all the variables are never observed together.
  • Jinja can understand **kwargs, but the syntax is a little bit different than python: https://medium.com/python-pandemonium/using-kwargs-in-flask-jinja-465692b37a99.
  • Supercontest.
    • Week 4 did not become available at 10am PT, which would have been the case if I had messed up the timing (because that's 5pm UTC).
    • Restructured almost all of core/results, and its corresponding usage in views, to be sequential. Query for something, pass it to something else, sort it, calculate from it, etc. It's a much clearer, layered approach now. The analytic functions are modular, no queries are being repeated. Establishment of the necessary information happens FIRST, in the route, in the highest level. It's much easier to see duplicates now.
    • This modularity made my query footprint a lot smaller. The all-picks view would query for all users but only one week, whereas the lb/graph would query for all users for all week. They used the same machinery underneath, but the latter called it in a loop. Now it queries all at once and reformats the data as desired after. I remember thinking about this inefficiency when I first wrote it, but didn't have time to optimize. I do now.
    • Commented out all the tests so that I can run test-python to completion as it should. All no tests run, it's still gives more information to add this properly to my dev workflow (it compiles requirements and such).
    • Tested and work. App is a lot snippier, and FDT shows a lot fewer queries (like 10 max, for the worst route that has to query everything).
    • Updated to harner's gif. Played around with the background css styling. There are two important ones: background-repeat and background-size. "space" will keep the original size but fill in gaps with whitespace, "round" will distort the image within a slight margin to fill the gaps (both for repeat). For size, you can basically stretch it or keep it the same, fit in parent, etc. Looks good now. Fits one across on mobile, and repeats on larger viewports.
    • Made the app gracefully handle the time period between 5pm PT (week_start) and when westgate posts the lines, if there's a delay. It conditionally shows messages, and even omits the table.
    • The heaviest (time-wise) route in the main navs is /graph. The heaviest league is the free one, because it's the least filtered. And the heaviest season is any full one. Therefore, the current view that takes the longest to load is https://southbaysupercontest.com/season2018/league0/graph.
    • Removed wednesday from RESULTS_DAYS and just added a single commit_scores call within commit_lines, so it initializes properly.
    • Added queries.is_user_in_league and g.user_in_league.
    • Fixed a few things to make the new "return empty but valid results for new lines and new picks" work.
    • Confirmed that newly committed lines from westgate enter the db with timezone info, and timezone = utc (the rest were manually added to the migration - I wanted to make sure it was consistent.
    • Did the usual wed deployment.
  • If you follow a ctag in vim, don't edit it there. It won't save by default. This is just for looking.

Tuesday, September 24, 2019

  • To remove from a collection that represents a many-many relationship, just delete that list element and commit. You could also wipe it all by doing something like
    • user = db.session.query(User).filter(x).one()
    • user.leagues = []
    • db.session.commit()
  • I docker-compose downed the 3 bmahlstedt.com containers on the droplet so the supercontest can have the full resources of that tiny machine. I'll move bmahlstedt.com elsewhere. I'll have to remember to bring it back up for interviews, etc.
  • Cooked the soaked garbanzo beans. Scalded my hip by splashing boiling water directly on it. Was bad. Blistered up and got all oozy.
  • Bought bulk charcoal on amazon. Wasn't prime shipping, but it was $45 for 4x18lbs. Briquettes for $1/lb is usually a good deal - this is half that.
  • Big fresh order. $75. Should be good until tough mudder tahoe trip.
  • I should tweet this:
    • "[B*tches] aint leavin til 6 in the morning" - Snoop Dogg, Gin and Juice
    • "Parties dont stop til 8 in the morning" - Jermaine Dupri, Welcome to Atlanta
    • what kind of latenights are these rappers getting into
  • SQLAlchemy obviously does a join for all relationships. For many-one, it creates a member and for many-many it creates a collection. You can filter against these directly through the ORM, which is nice.
  • Went to costco to buy beef ribs.
  • Interesting, but not surprising. Finishes get more likely as you go up in weight class: 
  • Post image
  • Supercontest.
    • The league change alone was very simple. I only had to pass the league ID to like 5 places, and then add it as a filter to like 3 queries. This is due to the efficient layering of the query structure.
    • Confirmed in the league deploy that the flask monitoring dashboard data persists across versions now.
    • I could theoretically remove the entire dbsession.joins module now and just query on the backref relationships, but it's slower. The eager joins are much faster than going through the orm for everything.
    • Made it so that if a user is in a non-free league for the requested season, the default is to display that. If you're only in free, it shows free.
    • Adding coverer to the Score table, collocated as closely as possible to the information that it depends upon - this makes more sense than putting it in the Line table. There's also no real reason that it needs its own table, it's not distinct enough. It will update in realtime, not waiting until the game is finished. This is because some views want to update colors before the games are finished, but other views want to hold info until status=F/FO (like the lb). The information is there, you can do either.
      • All of the same logic above for coverer in the score table is repeated for points in the pick table.
    • is_league_in_season should return True if every checked for the free league. Fixed.
    • Did the usual efficiency optimization. Looked at FDT, removed unnecessary queries, consolidated others. Saved a ton of time, benchmarks are back down.
    • Renamed season_week_blueprint to data_blueprint.
    • Today was pretty dumb. Reverted a lot of the changes. Overall, pretty underwhelmed by the inability to nest blueprints, conditionally handle values, set up url defaults, preprocess, etc. Sharing app state to build urls for navlinks should not be this hard.
    • Ended up using flask-caching to just memoize the calls that were repeated, rather than solving the problem directly in application logic.
    • I have it configured to SimpleCache, which is just a python dict with key/values. Not the most thread safe, but should be fine.
    • Checked the FDT profiler to see if not just any sqlalchemy queries were being repeated, but python functions as well. Sorted by cumulative time, then calls, to make sure that nothing from my dist (supercontest.*) was being called a lot and taking a lot of time.

Monday, September 23, 2019

  • The droplet was slogging along.
    • Ran docker system prune --all, reclaimed 7.5G.
    • There is only one core, it's usually railed at almost 100% when deploying, handling a request, etc. It idles at 1-2% otherwise.
    • There's only 1G RAM. There's about 24G disk space. 0 swap.
    • Tried moving the wheelbuild from the intermediate container to a simple install in the final container. Still would hang.
    • sudo reboot. Wanted to restart the docker service but just did a whole system update anyway.
    • (unrelated) cleared the release updater with:
      • sudo truncate -s 0 /var/lib/ubuntu-release-upgrader/release-upgrade-available
    • sudo apt update && sudo apt upgrade
    • sudo apt full-upgrade
    • sudo apt autoremove
    • `sudo update-rc.d docker defaults` to make docker start on boot.
    • After system restart, mem went down to about 200M, 20%. It was at about 80% before, even before building the image.
    • Tried rebuilding the image for supercontest, worked this time. Looks like it was just system overload (I'm straining this tiny machine). A restart/upgrade every now and then won't kill us.
    • docker-compose restart supercontest-app-prod clears a decent amount of mem.
    • docker-compose down before running the new build also clears a lot of space.
    • The flask monitoring dashboard has a db backend, obviously. It's only 4M right now.
  • /proc/sys/vm/swappiness is how aggressively your system will use swap. It's configurable. 60 is the usual.
  • Pruned my laptop's docker system as well. Reclaimed 21GB.
  • Lost both fantasy games, both pretty badly.
  • Supercontest.
    • Added a persistent (named) docker volume for the flask monitoring dashboard. It was getting wiped new on every deploy. Confirmed it stayed.
    • Added ipython to the dev image so that the manage.py shell opens it by default.
    • Did some backlog cleaning and milestone organization. Issue count is in a good place.
    • Centered the headers on the rules markdown. Swapped the picks/points column on the all-picks view so the colored points cells would stand out, not adjacent to the full main table.
    • With Monday, Sunday, and Thursday games combined, there are ~18 hours of active scoring during the week. This is a little over 1000 minutes. If I fetch the nfl scoresheet xml once a minute, that's 1000 requests a week. Totally handle-able. With our current user base of ~50, and each user opening the app (or different views within it) about 10 times a week, we're already at half that traffic. Much better to control this and keep it static around 1000, rather than proportional to our user base as we scale.
    • With a backref or backpopulating relationship in sqlalchemy, graphene will break unless you name your SQLAlchemyObjectType classes distinct from the model name. I.e., Week becomes WeekNode.
    • Relationship changes are just in the ORM. They don't change any of the raw db, no sql, no migration/upgrade required.
    • Substantially cleaned the makefile and admin.md.
    • Added backrefs for all existing FKs in the models. There's a lot more information now!
    • I don't think you can filter on a backref with graphene. Still, it's useful to be able to see all the info (basically a join) in graphiql.
    • The automigration for the table creation of league and league_user_association was easy. The only manual changes to the migration file were the creation of the 2018 and 2019 paid leagues, then adding users to them.
    • To find all the users from 2018:
      • from supercontest.dbsession.joins import join_to_picks
      • all_picks = join_to_picks()
      • results = all_picks.filter(Season.season == 2018).distinct(User.email).all()
      • emails = [user.email for _, user, _, _, _, _ in results]
    • In the migration file, I create the league table and the league_user association, then I find all the users and add them appropriately. This migration was pretty cool.
    • Added full league functionality.
    • league0 is the free league. It's not an actual league row, it's just "don't do any filtering for league". It's the same as the app is today, before the league change. This allows users to play for free, any year they want.
    • The url_for(request.url_rule.endpoint, *args)) calls are layered. The season navs and league navs are lumped. They pass season and league, because they're the top level. The week navs pass season, league, and week, because they're the lowest. The information beneath (like what tab you're on: matchups or lb) is passed through request.url_rule.endpoint. You do not need to provide any lower information; ie league_navs don't have to pass week or main tab, and main navs don't have to pass week.
    • The nav_active js obviously matches the element's id to the route, not the displayed text. This allows you to show whatever you want while doing unique endpoint-nav matching.
    • Use g.<attr> in the templates, that's what it's for. The redefinition to variables in layout.html is for javascript only, not use in html.
    • Rewrote the url_defaults and value_preprocessor to be MUCH clearer about the actions each is taking, why, and when.
    • The hardest part of the league change was handling all the navs and links moving everywhere properly. This is a little easier in a node app with something like react/redux, where you have a full state container.
    • Did some cool conditional behavior on the matchups view. If a league was passed (via route explicitly, which flask must handle), it strips it and redirects to the leagueless matchups endpoint. The python side can parse and handle the values as args/kwargs at will, but if an url is fetched from a server, there MUST be a @route to handle it. You can't just url_defaults or url_value_preprocessor away it, you must add a @route to handle it first. Then you can redirect / add data / remove data as desired.
  • Turned on the activity overview (commits vs code reviews etc) for my github profile.
  • Ansible (even with -vvv and stdout_lines assigned to debug vars and such) does not print the stdout and stderr of the remote commands back on the host machine. If you wanna watch, ssh in and do it.
  • Reorganized and cleaned the kitchen, moving the machines under the shelves and removing the mat. Looks much better now. Cleaned the fridge/freezer too. Made new batch of oat milk. Starting soaking a large batch of garbanzo beans. Bottled the green tea hibiscus kombucha without a second fermentation. Threw away the scoby - I don't plan on making another batch soon.
  • Made another batch of protein bars. Just pecans, oats, and protein powder in this one. Delicious. Better than the tahini base.
    • I didn't think about it until after, but add some oat milk to the next batch. It will make them stick together better, allow you to add more protein powder, and impart a little taste.
  • Gbro didn't finish the 1/2 cup over the course of the day. I'll leave it at the same serving for now (1/2c once a day instead of twice). Online resources say that appetites vary, but a healthy cat weight is one where you can "easily feel the ribs".  We're not close.
  • GitLab.
    • https://www.youtube.com/watch?v=nMAgP4WIcno.
    • Created an account, started to look through the full capabilities.
    • Combines Stash (SCM), JIRA (tickets), and Bamboo (CI/CD).
    • It's not an extension to github, it's a full alternative. It's a competitor.
    • You can edit in browser, merge requests (they call them the correct thing, not pull requests!), create boards, sprint plan, etc.
    • You get a lot of CI configuration right out of the box. It infers your language, creates a container, runs static analysis, checks licenses, runs security tests - all without you doing anything. You can, of course, customize all of this. They have advanced stages for dynamic testing, CSRF, and more.
    • It autocomments a lot of helpful information on the MR. Performance change, code quality deltas, etc.
    • CD has different environments, you can do partial deployments, more.
    • Microsoft bought GitHub last year for 7.5b.
    • I'm going to finish these few big tickets and then I'll migrate all my projects/repos over from github to gitlab.
  • https://www.programcreek.com/python/ is an awesome website. It basically just scrapes open source python projects for whatever you're looking for. In most cases, you'll get a working implementation of the string you're curious about.

Sunday, September 22, 2019

  • Remember to pass python lists through jinja with the |tojson filter for use in javascript. Same with bools. For dicts, dump them python side then pass with |safe.
  • Supercontest.
    • Added row count (rank, I guess, since it's sorted?) to the all-picks view. Makes it easy to compare to 45 (of the lb, which shows all users with season picks instead of week picks).
    • Added header with total pick counts for each team, and colored status of every game. This is the only place that has this info now; previously it would just highlight your picks on the matchups tabs, and then everyone's picks on this tab (which might have excluded teams; an often occurence).
    • App behaved well all day with the sunday rush, even with all the new changes (which were substantial). The live updated of points/possiblepoints for completed games was awesome. It ranked everybody dynamically, and was interesting to watch throughout the day.
    • Rewrote the `joins` module to properly use joins instead of subqueries. This is a lot cleaner. It removes all the `.c.` nonsense, and I don't have to be explicit (manually) about all the columns to forward through the joins. The only slightly annoying thing is that it returns the query results as a tuple divided by source table, so you have to unpack it to get the data you want. With the subquery structure, it was all flat so I could access the data directly with the column name.
    • Made the leaderboard sort with the same 3 keys as the all-picks view: total points, percentage, user id.
    • Added percentage at the top of the lb for each week, showing the performance of the entire league (theoretically should be close to 50).
    • Finished the graphql update. Fully got filters working. Didn't do joins. Uploaded a new client pkg to pypi with examples. Added twine instructions to ADMIN.md.
  • augmented-ui is a css kit you can include via cdn or npm pkg that transforms your site into cyberpunk: https://medium.com/better-programming/augmented-ui-cyberpunk-inspired-web-ui-made-easy-d463c0371144.
  • Foam roll on the legs = crucial for training.
  • Awesome website that does the same thing Ken's script used to do: https://www.fantasy-football-power-rankings.com/espn/1187979/2019. Power rankings, where it lists your record if you were to play every other team every week. Just like total points for, it is a more absolute metric of success without the random luck of head-head matchup order. Yahoo already reports this in your weekly recap, but ESPN doesn't, so this site provides it. https://github.com/JakePartusch/fantasy-football-power-rankings.
  • If joining the same table twice for multiple conditions, use sqlalchemy.orm.aliased. Same as a simple table table2 rename in sql.
  • Gonna feed gbro 1/2c twice a day instead of leaving big bowls out to self feed. This is slightly more than the recommended amount, but he's definitely overweight. I want to see how much he's consuming, then briley can adjust as he wants.
  • If you create a list in python with [x] * n, then they all point to the same object. If you update one of them, every element will mirror the same update.
  • Made another batch of tahini, but instead of using 100% sesame seeds, I used a ratio of about 80% sesame to 20% pecans, yielding pecan tahini. Incredible.
  • Made another batch of protein bars: oats, dates, protein powder, pecan tahini, banana, cinnamon, cacao powder. Incredible.
  • Read through most of the graphql documentation again.
    • Remember, it converts resolver names and such from snake_case to camelCase. I disably this during schema instantiation with auto_camelcase=False.
    • Within an ObjectType class, each resolve_x method corresponding to the field x that you define in the constructor.
    • They intentionally do not allow select *. Graphql is meant to be deliberate: fetch only what you need, not all cols. https://github.com/graphql/graphql-spec/issues/127.
    • SQLAlchemyConnectionField is the magic liason. Should map most of your entire model, all cols, field types, sortability, etc.
    • There is an extension to this that allows filtering: https://pypi.org/project/graphene-sqlalchemy-filter/. graphene-sqlalchemy can't do it right out of the box (simply). You'd have to add custom resolvers that pass args through query.filter() etc.
    • It's worth mentioning: the documentation surrounding this effort is horrendous. I have a very simple use case: take a few sqlalchemy models with foreign keys, allow flask graphql queries with filters on cols, for both direct queries as well as queries on joined tables. This common implementation should be trivial to implement, and it's not even close to that.
    • Filters: eq, ne, like, ilike, regexp, is_null, in, not_in, lt, lte, gt, gte, range.
      • Example: {seasons(filters:{season_start_lt:"2019-06-01"}){edges{node{id season}}}}. This would return just the rows from 2018 (lt = less than), and only the id and year number.
    • Datetimes are of the form "2019-11-01T00:00:00"
  • sqlalchemy has an `add_columns` which you can call after a join to create a more sql-standard select return table. https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.add_columns. This can be used to get around the necessity of unpacking the multiple table results if you query a join. You can even add labels.

Saturday, September 21, 2019

  • TSS stands for Transportation Software Solutions. Let Stacey, Roubik, and Art know that my intentions are to move back to the Bay Area.
  • Remy and Katilin got sick on Monday too! It wasn't the prime rib, because everyone had that and only 3 of us reacted. I've also had it a few times since with no problem. Possibilities: dip, cheese, gravy? The latenight snack after stuff had been sitting out could have been it.
  • There's a paradox about predictability; that once something becomes predictable, entropy naturally makes it move away toward unpredictable again. I half-buy it. I'm a believer in determinism superseding free will in the longggg future.
  • Your body can actually burn an incredible number of calories while sedentary (comparable to rigorous physical exercise), if you are highly stressed/concentrated: https://www.espn.com/espn/story/_/id/27593253/why-grandmasters-magnus-carlsen-fabiano-caruana-lose-weight-playing-chess.
  • Agreed. Personal websites are wonderful tools. https://www.vanschneider.com/a-love-letter-to-personal-websites.
  • GitLab is a company I had seriously considered about 2 years ago: https://www.forbes.com/sites/alexkonrad/2019/09/17/gitlab-doubles-valuation-to-nearly-3-billion. They're doing very well, and my professional experience matches their mission absurdly well. Their IPO is in 1 year.
  • Chrome's devtools has a "device toolbar" that you can toggle on and off by clicking the little button that looks like a phone. You can specify any custom viewport, standard phones, and it will show your how your responsive view is displayed. Great for testing.
  • Hahaha AB released from the Pats. What a roller coaster.
  • Supercontest.
    • Added mx-auto to all nav cols so that they'd center on smaller viewports, tested in devtools. Looks much better.
    • The lb totals and percentages will now show as ints if they're whole numbers (even if source data is X.0). If they're floats, they'll continue to show as floats.
    • Webassets.
      • Moved all third-party cdn js to the toplevel template.
      • Moved all variables (that were passed through jinja from flask to javascript) to the toplevel template.
      • Bundled and minified all supercontest js, added in the toplevel template.
      • Cleaned some of the iteration in jinja, setting variables properly.
      • At this point, all templates are just templates. It's a lot cleaner. All variable injection is collocated, which is easier to understand as well.
      • Made all js in the bundle distinct. Some required more specific ID selectors. Some needed to check on array length. Some needed to condition on typeof <x> !== undefined. This whole exercise was a good practice in forcing explicit javascript.
      • Minifying with jsmin.
    • Package the service.
      • I don't just want to copy an ambiguous directory over to be able to use the app. It should have explicit boundaries. This will keep it slim, also.
      • Therefore, I made the service a self-sufficient package. It can't run without an accompanying database already existing, but the app layer itself is self-sufficient.
      • This was a pretty lengthy, iterative process to get to work.
      • MANIFEST only tells it what to build the dist with; not necessarily what to install. You have to be explicit about those as data files in setup.py.
    • Overall, working with bundled assets and wheel pkgs should make the app non-noticeably faster.
    • Closed https://github.com/brianmahlstedt/supercontest/issues/81.
  • Docker.
    • If you built an image successfully but it failed to run the start command or entrypoint for some reason or another, perform the following to test.
      • docker ps -a
      • docker commit <ID> test_image
      • docker run -ti --entrypoint=sh test_image
    • To force a rebuild, even if nothing has changed:
      • docker-compose build --no-cache <service>
      • Then you can start the service at will.
  • tar -xzvf (I can never remember anyway - this is a worthless comment).
  • Remember to build wheels in the same environment that you'll install them in. Not the same instance, but the same system. There are many underlying system libs that are baked into bdist wheels, and the build/deploy envs should be identical to prevent clashes for this.
  • find_packages() returns a list like this: ['supercontest', 'supercontest.core', 'supercontest.views', 'supercontest.dbsession', 'supercontest.commands', 'supercontest.models', 'supercontest.graphql']

Thursday, September 19, 2019

  • Supercontest.
    • Datetime, picks, leaderboard:
      • Updated all the datetimes in the database to pacific. Updated the database timezone to pacific.
      • Updated the url defaults. Season is current or max. Week is current or 17.
      • Added g.current_season next to the existing g.season (which is the requested season) and g.is_current_season. Same for weeks.
      • Switched everything to be perfectly clear: all datetimes in the database are utc, all westgate lines are written after conversion from pacific time, and all sqlalchemy reads convert to local.
      • Debug toolbar is helping a lot. Noticed that on RESULTS_DAYS, it was bloated by about 500ms due to excessive queries.score_exists/get_score calls. Consolidated all of them. You could even go further by conditioning commit_scores not just on RESULTS_DAYS, but on time as well to tighten the range in which the extra calls are made. Call fetch_scores protected by results_days, then only call commit if any of the statuses aren't P/F/FO.
        • Reduced total db queries for the endpoint by >30 and profile reduced by ~400ms.
      • Completed redefined the pick view and how it's sorted, including the core/results backend to calculate as such. Now returns number of picks, current total points, current possible points, etc. Looks very good now.
      • LB and graph only show completed games in the total now. I added a col for in-progress games so you call still check "what if it all ended right now" status.
      • Percentages are now based on the picks that you have submitted, not the total (5 per week).
      • Combined a lot of the route logic, keeping the endpoints as thin as possible. Most of them outsource to functions in the core.results module now.
      • Added tooltips all over leaderboard for submitted picks, help, more.
      • Universal color for a lot of things.
      • Renamed the matchups tab to your picks. Renamed the picks tab to all picks.
      • Graph and leaderboard are now bulletproof for completed games.
      • Added req compilation back as a requirement of the test step, and ensured that compreqs were installed into the testenv.
    • My make test-python recipe is heavy-handed; it will compile all reqs and recreate the testenv. On subsequent runs, just use tox.
    • Got the bandit tests running again. Pytest is still obsolete.
    • App behaved well over TNF.
    • Closed https://github.com/brianmahlstedt/supercontest/issues/84.
    • Closed https://github.com/brianmahlstedt/supercontest/issues/94.
    • Dashboard.
      • Added a conditional (on my account) navtab for the flask monitor.
  • Bools have to be filtered |tojson to pass through jinja from flask to js. They'll be properly typeof() = boolean in js.
  • Forcing NBA owners to change their titles to governors due to historical implications is the same false equivalence as telling a German that they can't use ovens. It's offensive. It's irrelevant. Race is being used as glue to compare situations which have absolutely no relation.  It's not just a "doesn't affect you" situation - it's discriminatory, and should not be ok to concede. Finding these parallels with your lens of virtuous sensitivity is not progressive; it's an overshot of true equality, which slows resolution (if it doesn't destabilize entirely).
  • Nested dicts are fine to pass through jinja's |tojson filter for use in js.
  • Fed mouse. First time she's ever been snappy when I opened the terrarium. Used a broomstick to move her to the feeding tank.
  • My digital ocean droplet is in Santa Clara.
  • Supercontest.
  • Followup appointment with PCP. Got the referrals approved for both ortho and for PT.
    • The x-ray results:
      • Soft tissues: Unremarkable.
      • Bones: No acute fracture or subluxation. No lytic or blastic lesion.
      • Joints: The joint spaces are preserved. Articular surfaces are unremarkable.
    • So x-ray is fine. I'll probably need to do an MRI, since this would imply that the muscle is the source of injury.
    • Called the ortho place, made an appointment in Van Nuys for next wednesday.
    • I'm going to hold off on scheduling the PT until after the ortho evaluation is done. Stim, stretching, strengthening, etc - I can do that all on my own. The referral won't expired for 3 months, so I can schedule PT after ortho if need be.
  • Got adjusted at chiro (bought the 3/$90), then went to the new Chick-Fil-A.
  • For the last time: snake_case and camelCase and PascalCase.
  • Reminder for ISO8601: https://www.cl.cam.ac.uk/~mgk25/iso-time.html. All dates should be in the format YYYY-MM-DD.
  • A pipe | in a makefile is for order-only prerequisites. If you change any of those files, the parent recipe does not deem them necessary to rerun.
  • Postgres.
    • To get datatypes:
      • select column_name, data_type from information_schema.columns where table_name = '<mytable>';
      • select pg_typeof(<mycol>) from <mytable> limit 1;
    • I was very pleasantly surprised with postgresql's ability to internally parse timestamps. It correctly converted the type 'text' to 'timestamp with time zone' for every single datetime string from westgate. I thought I would have to use the python logic I had written to do this, but it was dead easy using sql in the migration instead.
    • lag() is a window function that allows you to update a row based on a previous row.
  • You can use direct sqlalchemy inside of an alembic migration if you want. This might be easier to (add, modify, etc) your data in python rather than doing it in sql. https://stackoverflow.com/questions/13676744/using-the-sqlalchemy-orm-inside-an-alembic-migration-how-do-i/
  • KFC is rolling out a chicken sandwich that uses glazed donuts instead of buns: https://www.cnn.com/2019/09/17/us/kfc-donut-chicken-sandwich-trnd/index.html.
  • Remember https://www.pngtosvg.com/ is the right place for free online vector conversion with color.
  • Just print(locals()) to show all args/kwargs passed to a function.
  • Changed my default text editor for git to vim: git config --global core.editor "vim"

Tuesday, September 17, 2019

  • Rob Zombie was on JRE a couple days ago. Cool dude. Just directed another movie, 3 from Hell.
  • Stomach was torn up all yesterday, couldn't do much. Worse than the ER trip in the bay, but stuck it out. Didn't sleep, lasted about 24 hrs, but feel better now. Ordered imodium, pedialyte, and pepto bismol on amazon fresh and it arrived within 8 hrs!
  • Lost both FF games. Went 1/5 in supercontest.
  • Supercontest.
    • Went through my giant migration and verified it with pure sql incrementally. Made sure the season/week change were good, user_season_association, then on to picks and scores etc. Made it a lot easier to chunk on the pure db side rather than debugging through the application layer.
    • The problem ended up being the FKs back to week and season for the lines and picks tables. They were matching those two numbers, but not joining lines/weeks/seasons on their id conditions so lines and picks were going to multiple places. Simply added a couple join conditions to restrict this properly.
    • Removed some of the smaller tasks of 68 to a followup ticket 86: https://github.com/brianmahlstedt/supercontest/issues/86.
    • Since the leaderboard iterates over picks, it only shows players who have made picks at all that season. The same is now true for the picks tab.
    • Closed an old ticket: https://github.com/brianmahlstedt/supercontest/issues/33.
    • User registration defaults to add you to the most recent season, but we'll need a way to do this efficiently at each season rollover. I guess for the 2020-2021 season, I'll just run quick sql to add everyone who was in 2019-2020 over, then we can prune after week 2-3 like usual.
    • Made determine_current_pick_points() modular to not just require a Pick row. Now, you can provide a Matchup row (line/scores) and a team string, simulating a pick.
    • Picks can be made by any user at any time, no matter if they're "active" for that year or not. That's just for our records, and requires the script be run to sync that data based on the existence of picks that year.
    • Finished and closed all the gigantic model changes. https://github.com/brianmahlstedt/supercontest/issues/68. On to the fun stuff.
    • Fixed datetime and improved the lb: https://github.com/brianmahlstedt/supercontest/issues/86.
    • Added a request tracking dashboard: https://github.com/brianmahlstedt/supercontest/issues/77. Useful for timing and profiling over time, and by version. This will be used for production.
    • Added a debug toolbar: https://github.com/brianmahlstedt/supercontest/issues/78. This does a huge superset of the dashboard. You can see sqlalchemy queries, http headers, profiling, total times, etc; a plethora of information for a single request (every time).
    • Made the all-picks view show the team name abbreviations throughout the table so that when you scroll down you're not lost. Added tooltips for the lines as well. https://github.com/brianmahlstedt/supercontest/issues/83.
  • lol: https://www.youtube.com/watch?v=4sqYEmAy9Dg.
  • "If you go to jail for tax evasion, you're living off of taxes as a result of not paying taxes." - Joe Rogan
  • For more complicated boolean queries in sqlalchemy, use not_ and_ etc.
  • Remember, with csrf enabled globally for an app, a lot of plugins might get tangled. You can specifically call csrf.exempt(view_or_blueprint), but this often means going into the source code for an extension to find out how to import the view/blueprint for exemption.
  • For rgb colors, if all 3 numbers match, then it will produce a color in grayscale. All zeros is black. all 255s is white.

Monday, September 16, 2019

  • Supercontest:
    • Backed up the supercontest db, made sure everything worked (with old db models still today).
    • If a user honestly misses lockdown, but wants to pick before kickoff, you can manually add their picks (if you deem their case ok). Just `python manage.py shell` then import and run `commit_picks(..., verify=False)` for the user id and week id and teams, etc.
  • NFL Sunday. Remilins all day.
  • Smoked the 16lb ribeye roast.
  • Made gravy out of the trimmings:
    • Stock: Salt the trimmings in a pan, sear them with olive oil until very brown, deglaze with wine, add herbs/onions/whatever and water, simmer for hours (reduce) then filter.
    • Gravy: Whisk in flour (like 2tbsp per cup of liquid), boil, then turn off.
  • Moved bbq_log from docs to sheets. Lot cleaner now. Froze top row and left two columns during scrolling to make everything a lot easier to see. This was my 32nd smoke on the WSM today!
  • Stack is always lifo, way faster, every thread gets a stack, etc. Heap has much less restriction- it's bigger and slower. It's usually across multiple threads (usually one for whole app), data can be written/read in different orders, etc.

Saturday, September 14, 2019

  • Quadruple-filtered the batch of oat milk today. Made an ok difference. Each time removes a bit of the suspension. Also added 6 pitted dates. Not the biggest change for half a gallon of milk. Gonna save the dates for the next round of protein bars: pecan butter, tahini, oats, dates, protein powder.
  • Really interesting story about Nick Diaz' life: https://www.mmafighting.com/2015/9/14/9327767/nick-diaz-opens-old-wounds-on-a-dark-day-in-his-career. Didn't graduate middle school, been fighting all his life. Biggest motivation was his girlfriend killing herself in high school. Now suspended for 5 years for having marijuana in his system. Frustrated that he can't be there for his brother.
  • Bought round-trip flight to the bay for the tough mudder, oct 4-7. 15k miles and $11 in fees.
  • Significant rework on a lot of vim shortcuts, mostly.
    • Use on full tmux terminal for vim, and have vim handle the grid between panes for editing multiple files at once. This allows you to copy between files easily. Then keep other grids in tmux for different shell contexts, like python, bash, psql, git, logs, etc.
    • tmux:
      • ctrl-a then c to create tab
      • ctrl-a then n to move to next tab
      • ctrl-a then p to move to previous tab
      • ctrl-a then - to split window horizontally
      • ctrl-a then \ to split window vertically
      • ctrl-a then arrow to move windows
      • ctrl-a then x to close window, or tab if no more windows (confirm with y)
    • vim:
      • ctrl-w then c to create tab (with filetree explorer)
      • ctrl-w then n to move to next tab
      • ctrl-w then p to move to previous tab
      • ctrl-w then - to split window horizontally (with filetree explorer)
      • ctrl-w then \ to split window vertically (with filetree explorer)
      • ctrl-w then arrow to move windows
      • ctrl-w then x to close window, or tab if no more windows
    • ctags
      • ctrl-] to jump to def
      • ctrl-t to jump back
      • ctrl-\ to open def in new vim tab
    • There's no great way to maximize the current window in vim, like ctrl-a then z does in tmux.
  • Good UFC card. Pereira is one of the craziest fighters I have ever seen. So much capoeira. He did a straight backflip on a downed opponent.
  • Supercontest.
    • Finished updating the core modules for the new db and model structure. Now just need to test with the live app.
    • Removed the read/commit from excel from source. This was a one-team use to pull from petty's old data. If I need to reference it again, I can scrape the vcs archives. Nominally it won't be needed ever again.

Friday, September 13, 2019

  • Pay transparency! https://onezero.medium.com/leak-of-microsoft-salaries-shows-fight-for-higher-compensation-3010c589b41e. Leaving your job and coming back into the same role to get a raise...should never be a thing.
  • Cool device that closes your windows when a coworker walks in lol: https://github.com/dekuNukem/daytripper.
  • Confirmed my plan covers 2 chiro visits per month at all facilities, not each facility. Called Pendergraft, going to buy a 3-visit pack for $90. First appt monday.
  • Round trip to the border of hermosa/manhattan, where the street numbers restart, along the strand, is 3.6 miles.
  • Ran a 5k (3.1miles). Felt pretty good. A little over 20min.
  • Met with Roubik, Stacey, and an engineer from 5m. I liked them, and understand the product uniqueness. I don't know enough about the market to assess their viability and future.
  • Got quest bloodwork report back. Everything good except creatinine, slightly higher than the upper limit. Range is 0.60-1.35, I had 1.44. I think this was the same as last time? Electrolytes were good though, I think renal function is fine. False positive due to creatine supplementation, likely.
    • Lipid panel was fantastic. Cholesterol numbers solid, even with my weekly meat smoking.
  • Import from the distribution name at the toplevel (absolute), but then within each package (__init__, modules) feel free to import relatively. When referencing another package (within the same distribution), go back to absolute imports.
  • Supercontest.
    • Made all the get_*_picks() queries combine into one. Can query on season, week, or user, modularly. Returns full pick rows.
    • Finished going through views, cores, and all other modules to change the db interactions around. Major re-org.
  • Always a cool video about encryption and diffie-helman: https://www.youtube.com/watch?v=3QnD2c4Xovk. You just need a function that's easy in one direction and hard in another. Think paint. Easy to determine what two color mix to make, hard to determine what two colors went in a mixture.
    • Start with one color. Server and client each have a private color that they mix with the public color. Then send it over. Then mix private color again. Each then has a 3 color mixture of their private, the other's private, and the public color. A third party sniffer knows the combo of each private+public, but doesn't know all 3 because it has never seen the privates without being mixed with the public.
    • Instead of paint, a discrete logarithm is usually used. Example: 3^Xmod17. Each party's private is an X. Easy to calculate what that would yield, very hard to figure out what X is if only the answer is known (especially as the numbers get large).
  • The n+1 query problem in databases is for one-many relationships (or many-many, as well). You query once for the parent and then n times for the children, which can be very overloading if there are a lot of queries. This is called lazy loading. It's the default for most ORMs. It's faster if the relationships are small.
    • The opposite is called eager loading. It's simply a join on the data. For large datasets, it's much faster.
  • Flask extension research, again:
  • New manhattan softball league started last night.
  • France eliminated USA from the FIBA world cup in the quarterfinal lol.
  • Supercontest.
    • My migration was trying to move scores from lines with UPDATE, but scores is a new table. Needed INSERT INTO. Changed.
    • Query for the full leaderboard (without totals) is:
      • old models: select season, week, email, sum(points) from picks, users where picks.user_id = users.id group by season, week, email order by season asc, week asc, sum(points) desc;
      • new models: 
    • Lb totals:
      • select row_number() over (order by sum(points) desc) as row_number, email, sum(points) from picks, users where picks.user_id = users.id and season = 2019 group by email order by sum(points) desc;
    • Without rankings:
      • select sum(points) from picks, users where picks.user_id = users.id and season = 2019 group by email order by sum(points) desc;
      • Change year as necessary.
    • SUM in postgres returns null if the sum is zero, so this only shows people who scored a point over the whole season. You can use max(coalesce(col, 0)) to avoid that.
    • Cycled this to test db: restore backup, upgrade, query.
    • Quick interim ticket with UI clarifications: https://github.com/brianmahlstedt/supercontest/issues/79
      • Highlighting pick capability on the matchups tab.
      • Sorting second tier after datetime on matchups. Sorting team abbvs on picks tab.
      • LB shows percentage now.
      • Niners->Warriors in adspace.
    • Made the graph endpoint respect "don't show this week's results until complete" just like the leaderboard does for its colorization. Abstracted that logic out into an initializer for both views.
    • Added the INSERTs to the user-season association table in the migration. Everyone goes to 19, a list is crosschecked against 18.
    • Made a ticket to remove the src volume in the production app, and bundle all the static content with flask-assets: https://github.com/brianmahlstedt/supercontest/issues/81.
    • TNF game was delayed due to lightning. The nfl's xml scoresheet reported back "Suspended" as the status. The app handled it just fine.
    • Changed manage.py's shell context to inject the symbols for the model names explicitly, not just the models object. Now you can copy-paste exact queries from the app (db.session.query(User) instead of db.session.query(models.User)).
    • Abstracted all interactions with the db to a package called dbsession, with modules `queries` and `commits`. This will make the application side of migrations much easier to update.
    • You can incrementally build tables for further querying in sqlalchemy, like you would naturally do with nested SELECT and JOIN statements in SQL. This is super useful for layered relationships, like Pick -> Line -> Week -> Season. The trick is calling .subquery() at the end of your query. Then, you can query it (or join it) later. To access the labelled columns, you use c.
      • See supercontest.dbsession.queries for examples.
      • You can rename columns with .label('new_name') if you want to change name, deduplicate, or do math and create a new col, etc.
    • I'm starting to agree that ORM usage can be way more complicated and abstract than SQL for complex queries.
    • All of this model and query redesign made the views module much slimmer. Almost all logic lives outside the routes, which is the way it should be.
    • To query on a relationship column, use .has().
  • Westgate Supercontest.
    • Entry fee is $1500.
    • Winner last year was at 70.1%, 59.5 total out of 85 (17*5). He won $1,422, 214.20.
    • There were 3,123 contestants for a total of $4,309,740.
  • Generated a personal access token for octotree (it uses the free auth for the first X requests).
  • In sqlalchemy, use .one() if you expect one and only one result. Returns the object, not a list like .all(). If there might be multiple, use .first(). This won't raise exceptions like .one() will.
  • In python dir(var) will give private and dunder attributes, whereas var.__dict__ will just give the main ones (better).

Wednesday, September 11, 2019

  • Uber laid off about 8% of its workforce.
  • Tax-gain harvesting. Just balancing tax brackets for the year. If your income or investments are lower than usual at some point, then you can sell some of your better stocks (and usually buy them back immediately). Do this to maximize your current income tax bracket (10-12-22-24-32-35-37) and cap gains tax bracket (0-15-20). This allows you to pay a portion of the tax at a time that is cheapest for you.
    • The opposite is tax-loss harvesting. If you have stocks that have suffered, and your year had particularly high capital gains, then you can sell them at a loss to offset. Then you buy them back immediately, effectively shifting your basis in that holding to a lower amount.
  • Did a little fantasy research to set lineups for this week. Forgot how annoying it was lol. Watching the games is more fun without that stress.
  • Bought and smoked a whole octopus.
    • $13/lb, 4.7lbs. This was from redondo pier - I'll try an asian market next time, probably a little cheaper.
    • Frozen is fine, unlike most poultry/game/cattle. They'll usually have the beak/eyes/guts removed, but do so if the market didn't.
    • Let it sit out at room temp for about an hour, with a fan, before cooking. This creates a pellicle - a dried outer layer which smoke can cling to much better than the natural underwater skin.
    • Pepper dry rub with maple syrup binder. Mesquite chunks.
    • Take to ~140. About 2hrs with the smoker @150.
    • Octopus is super low in fats and carbs. Super high in protein and cholesterol.
    • They're in the cephalapod class, but above that they're in the mollusk phylum!
  • Chopped and prepared all veggies for smoothies and juices.
  • SQL.
    • Today was almost all SQL.
    • You can query distinct on multiple columns, and it will return rows with all the permutations.
    • Remember kids, don't hardcode static values into your migrations. If it's based on the current data (moving rows/cols, checking max, etc), but that update infer from the existing data. Also: write and test your downgrades!!
    • Foreign keys need definition in the database, and therefore need to be in the alembic migrations. Relationships, however, do not! They're only on the sqlalchemy side, the portion that decides how to write to the db. But the db structure itself, the sql side, does not have knowledge of a "relationship". You still need to define the association tables, foreign keys, etc in the migrations.
      • Along the same lines - whether or not the many-many relationship here is unidirectional or bidirectional is purely in the python side as well. The raw db has an association table. That's it. The ORM can choose to reference in both directions on both original tables, or just one.
    • Remember, SELECT statements (and subqueries) just return rows. That's why you can pass them to an INSERT statement or likewise. That's also why you can nest them.
    • Example join for common supercontest use case:
      • select email, team from users inner join picks on users.id = picks.user_id where season = 2019;
      • You can be specific about what you want back if the tables share column names:
        • select users.id, picks.points from users inner join picks on users.id = picks.user_id;
    • Outer join does an inner join first, then checks a condition on one of the tables. Left checks the condition for the first table in the join. Right checks the condition on the second A full outer join does both.
    • Cross join does permutations, returning a table with all the columns from both tables in the join.
    • Remember, can't add a non-nullable col to existing rows. Needs a default, or add it regular and execute whatever inserts you want, then add the nullable=False constraint last.
    • Listing all fk constraints for a table is a long query: https://stackoverflow.com/a/1152321.
    • If you do `FROM table1, table2` postgres does an implicit CROSS JOIN, for all permutations. You usually don't want this naked, but often the query comes in the form `FROM table1, table2 WHERE table1.col1 = table2.col2`. This implicitly makes it an INNER JOIN. It's the same syntax as `FROM table1 INNER JOIN table2 on col1 = col2`.
    • You can join more than two, as well. Example syntax: `FROM table1, table2, table3`.
    • For postgresql and alembic, you have to manually create the sequence in the migration before creating the table:
      • from sqlalchemy.schema import Sequence, CreateSequence
      • op.execute(CreateSequence(Sequence('groups_field_seq')))
    • INSERT INTO requires parentheses around the columns that follow. SELECT should not have parentheses around the multiple values. If you put parentheses, it tries to combine the contents into a compound value.
  • Supercontest.
    • Small bug in the prod app. sorted_user_ids wasn't defined in the picks view when it's in "only-me" mode wed-sat. It's just your id, one line change.
    • Added the week 2 lines. Backed up before and after.
    • Verified that "last week" colorizes on the leaderboard now. Verified all the new picking UI functionalities work. The light blue is a great change. The /picks tab shows yours on wed-sat also (only yours).
    • Made my picks for this week.
    • Overall db change in hierarchy, following foreign keys and relationships:
      • Score -> Line -> Week -> Season
      • Pick -> Line -> Week -> Season
      • Pick -> User
      • User -> Season
    • Manually created the extensive migration to the new table structure. Some changes were easier: table creation, rename, not nullable, fk, etc. Some changes were harder: conditional updates, moving cols, etc. Practiced a lot of SQL.
    • The final migration (just upgrade) was over 100 lines. Not great.
    • While you should write downgrades whenever possible, some migrations are not reversible. This is one of them. Data was lost (Pick.points, Matchup.winner, etc). It's not worth recalculating those and programmatically allowing a downgrade. You have backups from before then.
    • Got the db to upgrade. Now going to test with sql (manually) and update the app to use the new structure.
    • Rebased on the master prod changes.

Tuesday, September 10, 2019

  • Supercontest.
    • Made the leaderboard current_user row highlight with border like the picks current_user row.
    • Standardized all the flask_user templates. Made them extend my bases, so the headers are consistent throughout the whole site.
    • Changed all the container/row/cols to be more responsive, and optimized for different viewports.
    • Changed the matchups table to white-space:nowrap. This was specifically for the datetime column, but applies to all cols and all rows. Nothing should span two lines. As the viewport shrinks, it should scroll overflow.
    • Added make enter-dev/prod-app for ease.
    • A recent alembic migration had iteritems(), had to upgrade to items() now that the app is py3.
    • Deleted all remote branches and pruned refs.
    • Deployed the cosmetic and responsive changes from the first #68 branch.
    • Backed up before all prod changes to be sure.
    • Migration:
      • Pluralized table names for easy querying.
      • Dropped default values for user cols: email, is_active, password.
      • Made user cols nullable: first_name, last_name.
      • Set the val/nextval in the sequences for all 3 tables to match max(id).
    • Merged and deployed the changes to the existing db first. Adding new season table and all the others next.
    • Made logout tab dynamically change to login based on auth state.
    • Gigantic table redesign (full source changes to the model captured here https://github.com/brianmahlstedt/supercontest/commit/0684fdc731d975bbdb80a8a961cc2d83cda1aea2):
      • New Season table, with sequence on id. year col, int, not nullable.
      • Add seasons col to User table, many-many relationship with Season. Unidirectional.
      • New Week table, with seq on id. Two col, season_id (fk), and week. Both ints, both non nullable.
      • Rename Matchup to Line table. Include sequence. id, season_id (fk), week_id (fk), favored_team, underdog_team, datetime, line, and home_team moved. The favored_team_score, underdog_team_score, and status cols moved to the new Score table. Winner moved to a new Coverer table. home_team became nullable=False.
      • The Pick table lost its season and week cols. It now uses a foreign key to the line table for the id of the matchup that contained the team it picked. Also dropped the points col (did not move). The cols user and line became not nullable.
      • New Score table. Id pk with seq. Line_id, fk, int, not nullable. Moved favored_team_score and underdog_team_score and status from the old matchups table here, and made them not nullable.
    • I had initially created PickPoints, WeekPoints, and SeasonPoints tables, but I think it's better to recalc than to aggregate and store. This is a design decision based on application usage. Scores can change 3 days out of the 7. If ever a score changes, the points for that pick changes, which requires a recalc of PickPoints, WeekPoints, and SeasonPoints. So storing the calc output doesn't really save much. If you data is more static, then it's better to store the calcs.
  • You can customize the graphiql template, but it's not easy. Flask-graphql allows you to pass a template string, but it's a full react app, so you can break it quite easily. I would have to copy over the jumbotron and settings_navs manually, since jinja can't integrate them, so I'll leave it as-is.
  • Watched the rob lowe and bruce willis roasts in the background while working. The new alec baldwin roast is coming up this sunday.
  • Upgraded the mint link to BoA.
  • col, col-12, and col-xs-12 are all the same thing. xs and 12 are the defaults. The breakpoint setting (the viewport size) is, remember, a minimum, so it applies to all screens that are larger. Therefore they all mean one column, across the full width, on all screens.
  • Flask-sqlalchemy will autoincrement the first primary key with integer type.
  • Coordinated with Roubik (Nel's dad), going to meet Thursday in Glendale.
  • A tablespoon of my homemade tahini in a big cup of coffee = delicious. Adds some good fats and a nutty flavor.
  • Placed Fresh order.
  • Useful psql:
    • List all sequences:
      • select c.relname from pg_class c where c.relkind = 'S';
    • List all defaults:
      • select column_name, column_default from information_schema.columns where (table_schema, table_name) = ('public', '<mytable>') order by ordinal position;
    • Drop a default:
      • alter table public.<table> alter column <column> drop default;
    • List cols as nullable or not:
      • select table_name, column_name, is_nullable from information_schema.columns where table_name = '<mytable>';
    • List indexes:
      • select * from pg_index where tablename not like 'pg%';
  • Remember, don't execute crucial sql in the database without including it in the alembic upgrades/downgrades. You can put arbitrary op.execute(<sql>) to make sure that all you manual actions are captured programmatically.
  • It is understandable, but still kinda sucks how manual db migrations still are. Alembic can't auto-understand every change to your model, especially when abstracted away through an ORM like sqlalchemy, and flask-sqlalchemy.
  • Basic relationship patterns: many-one, one-many, one-one, many-many.
    • For many-one or one-many, you simply specify a foreign key to another table (usually pulling in the id). If you want actually attribute the whole row (like make all the children available in the parent object), then specify the FK as well as a relationship with back_populates. You can go both directions.
    • One-one is easy. It's just many-one or one-many with uselist=False, where you simply attribute a scalar for the parent/child instead of many.
    • Many-many isn't that bad. You require an extra table called an association table which connects the column in each of the left and right tables that you want to map. Then you simply reference the association table in each of the two tables you want to associate. You put in both if you want bidirectional (ie child object has parents col and parent obj has a children col), or you can only put it in one if you want.
  • I want to smoke a big rib roast next time. This is basically the same meat as the ribeye (the best steak), but in a much larger roast cut instead of a steak cut. Slices of a big rib roast are what prime rib is.
    • It is very expensive, obviously. Even a good cheap distributor is gonna charge $15-20/lb for a prime rib roast. Wagyu ribeye can be over $100/lb.
    • Costco sells a boneless 7lb for $130.
  • Art mentioned biologics and biosimilars: https://www.phrma.org/advocacy/research-development/biologics-biosimilars.
    • Did another domain scrape. curebench and pillemporium are both available. I love em.
  • When designing the models, you can just put the foreign key IDs without backpopulation/backref. This keeps your tables leaner, but it makes your queries heavier (because you have to join). This is obvious, but is a pretty important decision when designing a system.

Monday, September 9, 2019

  • FIBA world cup has finally entered the bracket stage, with only 8 teams left. We play France.
  • Filtered my oat milk twice instead of once for the first time day. Made a huge difference.
  • Ekeler did a fantastic job yesterday while Gordon continues to cry. Love it.
  • Passer rating is basically (touchdowns + yards + completions - interceptions), weighted and normalized by attempts.
    • Ranges from 0 to 158.3 (perfect).
    • To get perfect, it's basically 12 yd/throw, 80% completion, a touchdown every 10 throws, 0 interceptions.
    • Aaran rodgers has the best career passer rating (103.1) and best season (122.5).
  • The Equifax data breach settlement continues to disappoint. They now require an additional step from everyone - you must provide proof of an active credit monitoring service to get the <$125 cash, otherwise you only get the free credit monitoring service in the settlement.
  • Lost both fantasy matchups this week.
  • Remember, a semicolon in js is used to terminate a single statement. Curly braces are blocks that contain statements. If you have an if (condition) {statements} you don't need to terminate with semicolon. If you define a function() {} you don't need to terminate with a semicolon. If you assign and define a function, like const foo = function() {}; then you need to terminate with a semicolon.
  • Supercontest.
    • Changed it so that calc_lb only does it for the current week, not all in the season. Much faster.
    • All current winners and points are now calculated serverside only. It checks the current values and does the math once, then passes matchup.winner and pick.points back to templates as needed. This simplified much of the javascript, since I don't do the duplicated calculation anymore.
    • Went back through a lot of the js and terminated statements properly, shifted to 4-sp indentation, etc.
    • The /picks tab now respects games that haven't started, coloring them blue.
    • All tabs that require current scores (picks, matchups, lb, graph) are much cleaner on the backend, calculating only what's necessary and passing as little as possible through the templates. This required that the lb and graph endpoints got smarter, bc they didn't have the value preprocessor and defaults for week/season.
    • The lb is only colorized once a week is completed now.
    • Sorted the matchups table by datetime and the picks table alphabetically.
    • Deleted the 2018 week 18 fake data.
    • Changed the doc onload behavior of coloring picks to a function, called still once onload and then also every time after pick submission. This separation allows submitted picks to be dark blue and unsubmitted to be light blue, which I implemented.
    • Tried to make the column headers vertical in the picks table (for team names), but this is unnecessarily difficult in css. Leaving team name abbrvs for now (changed pats to NE).
    • Closed https://github.com/brianmahlstedt/supercontest/issues/71.
  • My laptop can't find the tv via chromecast half the time. Turning wifi on/off (from my laptop) works, but this is still annoying that chromecast can't flush. I've confirmed they're always on the same network.
  • Because of the docker cache, deployments are significantly shorter for smaller changes. To not need to rebuild the image is very timesaving.
  • Remember, Python and Javascript have different representations for key/value pairs (dict, hash table). Jinja can handle your python dict just fine, but in order to pass it through a template to js, it needs an interim format that's useful. The most common is json. Dump it in your python app, then have js able to read it.
    • data=json.dumps(my_dict) in the route, then myData = {{ data|safe }} in the template.

Sunday, September 8, 2019

  • Great day/night with Colin/AK yesterday. Smoked beef ribs and played games at the new apartment then went out on Artesia for shuffleboard/pool.
  • In true Cheick Kongo fashion, Cheick Kongo forfeited his title fight in the first round due to an eye poke lol. Ruling was NC, Bader keeps the Bellator HW belt. I have never seen a fighter give up voluntarily like this; they're usually screaming at the doctor and the ref to let them continue. Not doing well for French combat stereotypes.
  • First NFL sunday of the season. The best.
    • Went 4-1.
    • 49ers wons.
    • Supercontest behaved perfectly. Picks autopublished at midnight, scores updated in realtime, no issues.
  • IT Chapter 2!

Friday, September 6, 2019

  • Researched a pattern for automatic injection of season and week into all db calls. Flask's g object already has season and week from my default value params, but I was hoping there would be a clean pattern where I didn't have to pass them explicitly into every db call. There are some precompile event ways in sqlalchmey, and there's a "BakedQuery" but none do exactly what I wanted. There's no harm in being explicit in every query for now. Once the user table adopts season as well, I might be able to just have a wrapper around db.session.query which inserts season and week everywhere, but it's not important for now. I was surprised a normative solution for this wasn't more common/apparent.
  • Sell orders completed in Robinhood. Transferred to BoA.
  • Chiro. Feel much better.
  • I don't agree with this article: https://abe-winter.github.io/2019/09/03/orms-backwards.html. ORMs are of good convenience to the majority of users, like any high-level abstraction. Most database interactions of not of the complexity/difference that would require raw SQL for integrity.
  • Blog.
    • In the spirit of retention, I thought it would be a good idea to start a morning habit of rereading my previous blog posts. If I go back a week, this will take about 5 minutes. Every item will be ingrained 7 times, which should be enough to remember most details.
    • Changed the main page to only show 7 days, so I just read the whole site every day.
    • Redesigned. Simply black now. Better font sizes. Cleaner organization. Removed some gadgets.
  • The pgadmin getting started docs are some of the worst I've ever seen. Very verbose, and none of the content I want. How do you install? Give me one sentence with options.
    • The web interface allows you some conveniences. You can run the pgadmin server in a container and localhost:5050 gets a web application. You can create users, tables, modify data, etc.
    • Any package that requires you to wget a wheel and install it  manually is...not going to get any attention. Upload your modules to pypi like a normal human being pls.
    • I've been satisfied with psql so far; I'm not going to use pgadmin because of these unnecessary deterrents. PSQL forces me to practice my sql anyway.
  • Supercontest.
    • Added a repeat-x niners svg to the jumbotron.
    • Parsed and put the rules on a new site tab. Used webcomponents and zero-md to convert the md in source to html.
    • Closed https://github.com/brianmahlstedt/supercontest/issues/66.
    • Started working on the Season table, ticket 68.
    • Verified that the remote backup to local, and then restore local from local, correctly syncs the production db to my dev machine.
  • Where sqlalchemy would usually have you declare a base, manage the session, etc - flask_sqlalchemy is what I use and it abstracts a lot of that away, simply giving you a db object to do the most common tasks with.
  • Met with Art about pharma:
    • Recap from my old notes:
      • utm params can identify the majority of traffic we generate.
      • Universal application to be approved as pharmacy. Standardizing this API would be valuable for everyone, marketplace or not.
      • Kayak model: redirect them, don't buy/sell directly. Pros: Simpler. Easier.
      • Amazon/Orbitz model: allow buy/sell through our website. Pros: You can generate aggregate purchase orders. Paperwork is a big aspect. You can also hold the money and do a billing cycle.
      • Could offer a small client to serve smaller vendor's prices, putting them online.
      • Exit strategy is not to monopolize, it's to sell to Amazon (like PillPack for 753m).
    • Now actions:
      • Build the mvp site.
        • Get domain name and host.
        • Populate with fake data from multiple vendors. You might be able to get some public data for generics. Some sites even list their rx, like https://auromedics.com/products/ampicillin-and-sulbactam/.
        • Write template API that we'll encourage vendors to use. Write client from excel sheet.
        • Add basic user login which protects the price comp tool endpoint.
        • Add basic landing page which describes the benefits for (a) pharmacies and (b) vendors.
      • We are going with the orbitz model, the buy-through-us model. This gives a few benefits:
        • One stop shop. One invoice. 
        • Can include generics, prescriptions, and med supplies in the same order.
        • We can basically give credit, where pharmacies can go on a monthly billing cycle, and we pay in the meantime.
      • Go speak with vendors. Get them onboard and get their data.
        • Here is where you can search for some vendors: https://search.dca.ca.gov/
        • Med supply vendors are huge.
        • Collect what percentage? Do research, seek financial expertise here.
      • Standardized universal application.
        • Vendors have to be cool with us giving their data out. This should be fine; it's free marketing.
      • Go speak with pharmacies once you have a good selection of vendors and products. Get them to start using it.
      • Exit sell to Amazon like PillPack for 753m.
  • Met with Nel's dad about the pathplanning app. Notes coming tomorrow after I do more research.

Thursday, September 5, 2019

  • Elbow.
    • Bloodwork.
      • Walk-in, 8am when they opened. Took 45 minutes in the waiting room and 5 minutes of action.
      • Made a quest account and downloaded the app, can now check results on my phone when they're ready.
    • X-ray.
      • Walk-in, 915am a little after they opened. Took 90 minutes in the waiting room and 5 minutes of action.
    • Picked up both prescriptions from yesterday.
      • Applied the topical painkiller before pull day at the gym. Felt 1000x better. It's incredible how much of a difference it makes. It's incredible how muted my exercise has been for almost 2 years now. Cheers, looking forward to the permanent solution.
  • Pinged and finally settled all Hawaii expenses.
  • Refilled all powders and pills. Threw a lot out; all that's left is turmeric and glucosamine. Once those are gone, I won't reorder. I eat turmeric root directly and I didn't find much benefit from glucosamine.
  • Lamborghini makes an SUV called the Urus. Its MSRP is 200k. Crazy. But, given that 50% of vehicles sales are SUVs nowadays, I get it.
  • Homemade protein bars.
    • Required ingredients. Just 3. My homemade nut butter, oats, and protein powder. Mix in bowl with rubber spoon then chill in fridge/freezer and cut, or just leave in bowl.
    • Optional: Honey, maple, cinnamon, cacao, coconut oil, the usual suspects.
    • Now I don't have to buy clif bars for post-gym anymore! That's the very last processed item in my whole diet, now everything is homemade!
  • Django crash course (traversy media: https://www.youtube.com/watch?v=e1IyzVyrLSU).
    • python manage.py [runserver|migrate|etc] of flask-scripts comes from django.
    • Default port 8000.
    • Can use jinja, but defaults to django's own template engine (which is really similar anyway).9
    • Comes with a lot by default. An admin interface. Gives a skeleton structure for views, models, settings, and more. Creates all the files for you, just leaving you to edit them.
    • You can do a lot with django, but I still prefer flask for the most part.
  • pgadmin (latest version: 4) is a much better tool for messing with your db than psql. Use it next time.
  • Checked mail for the first time in >1mo. Got the replacement amazon card and activated. Also deposited the $14 treasury disbursement (I think it was a settlement from that credit leak?) using mobile checking which was nice.
  • The pecans at costco are definitely the cheapest, at ~$8/lb. Bought organic pecans for ~$10/lb (from a company called I'm A Nut) on Amazon, which is worth the convenience of online + organic. This is on regular Amazon, not Fresh.
  • Homemade tahini. Just like nut butter, but with seeds. Toast them, then process them. Add a little oil or salt if you want. So good.
  • Bought new tupperware. Tired of lidless, wrong-sized options.
  • Homemade kava. About 3 tablespoons of root and about half a liter (2 cups) of hot water makes a decent strength individual serving. Mix a little coconut oil in (the fat helps with extraction, just like others), then steep for 10 minutes. Then knead the tea bag.
  • Supercontest.
    • TNF verifications, first of the season. App behaved very well. Closed https://github.com/brianmahlstedt/supercontest/issues/64.
    • There was only one issue. If thursday was picked, it would reject all 5 subsequent picks if you wanted to change one of the other four (which is valid). Fixed it to verify against the current picks.
    • Closed the "before 2019 season" milestone.
  • Chrome on mobile allows you to create desktop shortcuts from your current page, but that doesn't work when you want the url pre-redirect. The trick to get around it? Disable wifi and mobile data, then the browser can't follow the redirect :) Then create the shortcut from the origin site.