Giter VIP home page Giter VIP logo

hsvdotbeer's People

Contributors

austincgomez avatar danroberts728 avatar dependabot[bot] avatar drewbrew avatar mjcarroll avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

hsvdotbeer's Issues

Make docs less terrible

  1. import fixtures
  2. missing models
  3. parser design pattern (for writing new ones)
  4. moderation (when designed)
  5. Instructions for adding venues for the different parsers.

BeerAdvocate URL for Ace Pineapple points to RateBeer

From digitalpour data for WYWB805:

{
  "Id": "5c6dd6d235272612e83ae095",
  "MenuItemDisplayDetail": {
    "$type": "BeerDashboard.Common.Models.Tap, BeerDashboard.Common",
    "BeverageType": null,
    "TapType": "CO2",
    "NonRotating": false,
    "ParentTapId": null,
    "LineLength": null,
    "LineDiameter": null,
    "CO2Mix": null,
    "NitroMix": null,
    "PSI": null,
    "Id": "58882be35e002c105cb7e4bc",
    "CompanyId": "57b130dd5e002c0388f8b686",
    "CompanyName": "Wish You Were Beer",
    "LocationId": "2",
    "LocationName": "Campus 802",
    "ItemType": "Tap",
    "DisplayName": "37",
    "DisplayOrder": 37,
    "DisplayLogoUrl": null,
    "DisplayGroup": null,
    "CostCenter": null,
    "NotInUse": false,
    "StorageLocation": {
      "CompanyId": "57b130dd5e002c0388f8b686",
      "CompanyName": null,
      "LocationId": "2",
      "LocationName": "Campus 802",
      "IsUsable": true,
      "DefaultKegLocation": true,
      "TemperatureSensorId": null,
      "Temperature": 0.0,
      "Id": "58882a955e002c105cb7e4ad",
      "StorageLocationName": "Cooler"
    },
    "ProductId": "H9V0TYD4QY5PT",
    "MeasuringSystemMappingId": null,
    "EventTableId": null,
    "TemperatureSensorId": null,
    "Temperature": 0.0,
    "IsDirty": false
  },
  "MenuItemProductDetail": {
    "$type": "BeerDashboard.Common.Models.Keg, BeerDashboard.Common",
    "KegSize": 661.0,
    "KegType": "CO2",
    "Coupler": "US Sankey (D)",
    "BatchId": null,
    "DateKegged": null,
    "KegId": null,
    "InitialOuncesConsumed": 0.0,
    "SamplesPoured": 0,
    "SampleSize": 0.0,
    "OuncesConsumed": 46.5,
    "PercentFull": 0.92965204236006049,
    "UseMeasuredValues": false,
    "PosReportedOuncesConsumed": 46.5,
    "PosReportedPercentFull": 0.92965204236006049,
    "MeasuredOuncesConsumed": 0.0,
    "MeasuredPercentFull": 0.0,
    "DaysOn": 1,
    "TimeOn": 188.01611686833334,
    "AllowedTaps": null,
    "PercentConsumedBySamples": 0.0,
    "EstimatedOzLeft": 614.5,
    "HasRestrictions": false,
    "RestrictedReplacementsList": null,
    "EstimatedKegLeftDuration": "1.17:25:00",
    "AvailableInBottles": false,
    "MoreKegsAvailable": false,
    "Id": "2cb7119e-0ba8-479d-9af2-dcdb6a23bb39",
    "BeverageType": "Cider",
    "Beverage": {
      "$type": "BeerDashboard.Common.Models.CiderModels.Cider, BeerDashboard.Common",
      "BeverageName": "Pineapple",
      "Cidery": {
        "CideryName": "Ace",
        "CideryUrl": "http://www.acecider.com/",
        "CultureSpecificCideryNames": {},
        "CultureSpecificLocationNames": {},
        "ProducerName": "Ace",
        "SimplifiedProducerName": "ace",
        "CultureAwareCideryName": "Ace",
        "CultureAwareLocationName": "Sebastopol, CA",
        "Id": "51d75e05df752b2124c127d1",
        "FullProducerName": null,
        "Location": "Sebastopol, CA",
        "ProducersUrl": null,
        "LogoImageUrl": "https://s3.amazonaws.com/digitalpourproducerlogos/51d75e05df752b2124c127d1.png",
        "TwitterName": "@AceCider",
        "Latitude": 0.0,
        "Longitude": 0.0,
        "DefaultKegSize": 1984.0,
        "DefaultKegCoupler": "US Sankey (D)",
        "CompanyId": null,
        "IsAmateur": false,
        "AlternateLocations": []
      },
      "Collaborators": [],
      "CiderName": "Pineapple",
      "CiderStyle": {
        "Id": "54ab0cc8b3b6f60eccac28a0",
        "StyleName": "Pineapple Cider",
        "ParentId": null,
        "ParentIds": ["52a4d173fb890c0f541fc9c5"],
        "Color": 15921786,
        "RecommendedCO2Mix": null,
        "RecommendedNitroMix": null,
        "RecommendedPSI": null,
        "CultureSpecificStyleNames": {},
        "CultureAwareStyleName": "Pineapple Cider"
      },
      "StyleVariation": "",
      "StyleVariationPrefix": null,
      "Dryness": "Semi-Sweet",
      "BaseFruit": {
        "Apple": 1.0
      },
      "BarrelAging": null,
      "HopsUsed": null,
      "YeastUsed": "Champagne",
      "Abv": 5.0,
      "Ibu": 0.0,
      "OriginalGravity": null,
      "FinalGravity": null,
      "pH": null,
      "Attributes": null,
      "CiderUrl": "acecider.com/our-ciders/ace-pineapple/",
      "CultureSpecificCiderNames": {},
      "RateBeerUrl": null,
      "BeerAdvocateUrl": "http://www.ratebeer.com/beer/ace-pear-cider/3004/",
      "UntappdUrl": null,
      "CollaboratorList": "",
      "BeverageProducer": {
        "$type": "BeerDashboard.Common.Models.CiderModels.Cidery, BeerDashboard.Common",
        "CideryName": "Ace",
        "CideryUrl": "http://www.acecider.com/",
        "CultureSpecificCideryNames": {},
        "CultureSpecificLocationNames": {},
        "ProducerName": "Ace",
        "SimplifiedProducerName": "ace",
        "CultureAwareCideryName": "Ace",
        "CultureAwareLocationName": "Sebastopol, CA",
        "Id": "51d75e05df752b2124c127d1",
        "FullProducerName": null,
        "Location": "Sebastopol, CA",
        "ProducersUrl": null,
        "LogoImageUrl": "https://s3.amazonaws.com/digitalpourproducerlogos/51d75e05df752b2124c127d1.png",
        "TwitterName": "@AceCider",
        "Latitude": 0.0,
        "Longitude": 0.0,
        "DefaultKegSize": 1984.0,
        "DefaultKegCoupler": "US Sankey (D)",
        "CompanyId": null,
        "IsAmateur": false,
        "AlternateLocations": []
      },
      "BeverageStyle": {
        "$type": "BeerDashboard.Common.Models.CiderModels.CiderStyle, BeerDashboard.Common",
        "Id": "54ab0cc8b3b6f60eccac28a0",
        "StyleName": "Pineapple Cider",
        "ParentId": null,
        "ParentIds": ["52a4d173fb890c0f541fc9c5"],
        "Color": 15921786,
        "RecommendedCO2Mix": null,
        "RecommendedNitroMix": null,
        "RecommendedPSI": null,
        "CultureSpecificStyleNames": {},
        "CultureAwareStyleName": "Pineapple Cider"
      },
      "FullCideryList": "Ace",
      "ResolvedLogoImageUrl": "https://s3.amazonaws.com/digitalpourproducerlogos/51d75e05df752b2124c127d1.png",
      "FullStyleName": "Pineapple Cider ",
      "ExpandedStyleName": "Pineapple Cider ",
      "CultureAwareBeverageName": "Pineapple",
      "FullProducerList": "Ace",
      "StyleColor": 15921786,
      "Id": "52e05208fb890c0980933935",
      "LogoImageUrl": null,
      "CO2Content": null,
      "CaloriesPerOz": null,
      "CustomDescription": null,
      "CustomStyle": null
    },
    "DateProduced": "0001-01-01T00:00:00Z",
    "Year": 2019,
    "BeverageCategory": "Craft",
    "BeverageCategoryLogoUrl": null,
    "Attributes": [],
    "CustomBeverageIcon": null,
    "HasVintage": false,
    "Prices": [{
      "Id": "T",
      "Size": 5.0,
      "Price": 2.25,
      "DisplayName": "5oz",
      "DisplaySize": 5.0,
      "PosModifier": "AZZAQR9A21E0Y",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Taster",
      "DisplayOnMenu": true,
      "Deactivated": false
    }, {
      "Id": "A",
      "Size": 9.5,
      "Price": 4.5,
      "DisplayName": "10oz",
      "DisplaySize": 10.0,
      "PosModifier": "F56XWN3E24HKM",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Half Snifter",
      "DisplayOnMenu": false,
      "Deactivated": false
    }, {
      "Id": "B",
      "Size": 15.5,
      "Price": 7.0,
      "DisplayName": "16oz",
      "DisplaySize": 16.0,
      "PosModifier": "7ZQ9HTJTT9S6T",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Pint 2",
      "DisplayOnMenu": true,
      "Deactivated": false
    }, {
      "Id": "C",
      "Size": 35.2,
      "Price": 10.0,
      "DisplayName": "32oz",
      "DisplaySize": 32.0,
      "PosModifier": "7WPW4D5J60PAY",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Growler (Small)",
      "DisplayOnMenu": false,
      "Deactivated": false
    }, {
      "Id": "D",
      "Size": 70.4,
      "Price": 20.0,
      "DisplayName": "64oz",
      "DisplaySize": 64.0,
      "PosModifier": "E62P5QRBE47GW",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Growler",
      "DisplayOnMenu": true,
      "Deactivated": false
    }],
    "EventPrices": [{
      "Id": "T",
      "Size": 5.0,
      "Price": 2.25,
      "DisplayName": "5oz",
      "DisplaySize": 5.0,
      "PosModifier": "AZZAQR9A21E0Y",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Taster",
      "DisplayOnMenu": true,
      "Deactivated": false
    }, {
      "Id": "A",
      "Size": 9.5,
      "Price": 4.5,
      "DisplayName": "10oz",
      "DisplaySize": 10.0,
      "PosModifier": "F56XWN3E24HKM",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Half Snifter",
      "DisplayOnMenu": false,
      "Deactivated": false
    }, {
      "Id": "B",
      "Size": 15.5,
      "Price": 6.0,
      "DisplayName": "16oz",
      "DisplaySize": 16.0,
      "PosModifier": "7ZQ9HTJTT9S6T",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Pint 2",
      "DisplayOnMenu": true,
      "Deactivated": false
    }, {
      "Id": "C",
      "Size": 35.2,
      "Price": 8.5,
      "DisplayName": "32oz",
      "DisplaySize": 32.0,
      "PosModifier": "7WPW4D5J60PAY",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Growler (Small)",
      "DisplayOnMenu": false,
      "Deactivated": false
    }, {
      "Id": "D",
      "Size": 70.4,
      "Price": 17.0,
      "DisplayName": "64oz",
      "DisplaySize": 64.0,
      "PosModifier": "E62P5QRBE47GW",
      "SizeInPos": null,
      "UPCCode": null,
      "CostCenter": null,
      "Glassware": "Growler",
      "DisplayOnMenu": true,
      "Deactivated": false
    }],
    "EventPricesActive": false,
    "EventId": null,
    "EventName": null,
    "DateAdded": "2019-02-20T06:54:09.68Z",
    "DoNotUse": false,
    "ProductFinished": false,
    "ReplacesBeverageIds": [],
    "BeverageNameWithVintage": "Pineapple",
    "FullBeverageName": "Ace Pineapple",
    "FullProducerList": "Ace",
    "FullStyleName": "Pineapple Cider ",
    "OverrideableFullStyleName": "Pineapple Cider ",
    "EventItem": false,
    "ReplaceableItem": false
  },
  "Active": true,
  "EstimatedDatePutOn": null,
  "DatePutOn": "2019-02-20T22:38:12.893Z",
  "DatePulledOff": null,
  "QuantityOnTap": 0
}

Also, LOL@ Campus 802

Implement scraper for The Nook

As far as we can tell, The Nook uses a static HTML page to host its beer list. Create a scraper (hello, beautifulsoup) that parses the HTML data, tries to make a guess based on style/name/ABV, and saves the data to taps for the Nook

Create a moderation queue for new beers

If we get a beer coming in that we can't cleanly parse (likely because its style doesn't map to BJCP), use a moderation queue to make our lives way easier.

Also consider adding a StyleMapping model:

  • BeerStyle (FK)
  • provider (CharField, same choices as Venue.taplist_provider)
  • provider_style_name

That way we can auto-resolve future styles with the same name.

Implement Venue app & model

Fields:

  • Name (unique, required)
  • Address
  • City
  • State/Province
  • Postal Code
  • Country
  • URL
  • Facebook link
  • Twitter handle
  • Instagram handle
  • Time Zone (default configurable in settings)

Drop rooms

The only venue where room matters doesn't expose the room via API, so kill it with fire.

Open up auth on views

  • Almost all views (except users) should be read-only to anybody who isn't authorized.
  • Most views should be writable by staff
  • The BeerStyle viewset should be writable by nobody

Drop asterisks in Nook beers

The Nook uses an asterisk on some beers to denote... something (super expensive beers?).

We need to strip those out.

Implement Event app/model

Fields:

  • Venue (required)
  • Start time (required)
  • End time (required, set to end of day in venue time zone if specified, otherwise 1 hour after start)
  • Description

NOTE: In the future we may want to adopt a more robust calendar model to handle repeating events/all day events, but this is good for the basics.

Add pricing

  1. Create a ServingSize model within the beer app:
    • volume_oz (nullable unique decimal field)
    • name (unique)
    • Constraints:
  2. Create a BeerPrice model:
    • beer (FK to Beer)
    • size (FK to ServingSize)
    • venue (FK to Venue)
    • price (Decimal)
    • Constraint: beer, venue, and size are unique together
  3. When merging beers, copy over pricing
  4. Expose pricing info in beer serializer
  5. Update parsers to save pricing if available in API (and create sizes if not known)
  6. Write fixture for common sizes
    • 4 oz taster
    • half pint
    • 10 oz
    • 14.5 oz
    • pint
    • 32 oz crowler/half growler
    • 64 oz growler

Implement Beer model in the beers app

Fields:

  • Name

  • Manufacturer (foreign key; depends on #15 -- merged)

  • Style (foreign key; depends on #10 -- merged)

  • In production (boolean, default True)

  • ABV (optional)

  • SRM (optional)

  • IBU (optional)

  • Untappd ID (optional, indexed)

  • BeerAdvocate URL (optional, indexed)

  • RateBeer URL (optional, indexed)

  • Unique index: manufacturer + name

Remaining venues

Venues we should theoretically be able to support

  • The Bar at 805 (DigitalPour, but no site that I know of)
  • Pints and Pixels (Untappd, but doesn't expose the tap list on their site) (2019-03-28: @mjcarroll emailed them about it)
  • Below the Radar (Untappd, but doesn't have a functioning site to expose the tap list on)

Venues we'll never support

  • Green Bus
  • Salty Nut
  • OBB
  • Church Street Wine Shoppe

Places to consider but may not fit in with our target audience

Untappd

  • Cajun Steamer (Twickenham, no beer menu on site)
  • Anaheim Chili (they have a beer menu on their site, but it's down they let their domain get hijacked and their email address bounced)
  • End Zone (no beer menu on their site)
  • Stand Up Live (no beer menu on their site)
  • West End Outdoors (Athens, no website)

Add filtering on venues

Things I can think of:

  • name
  • beer name
  • beer style name
  • beer style category name

Does filtering by manufacturer make sense? I can't say I've ever wondered what beers by Straight to Ale are on tap at Wish You Were Beer.

Jump from Travis to... something

Since Idera fired all the Travis devs and we need to leave travis-ci.org anyway, look at other options and use one of those.

Ideas:

  • CircleCI
  • Azure devops

I need this to fix #4.

Add route: /venues/<pk>/styles

List styles available at a venue with beers expanded from that, e.g.

{
  "style": {
    <style serializer>,
    "beers": [<list of beer serializers>]
  }, ...
}

Make brewery and manufacturer names unique without regard to case

  1. Create a data migration to merge manufacturers whose names differ only in case
    from collections import Counter
    names = [i.name.casefold() for i in Manufacturer.objects.all()]
    dupes = [key for key, val in Counter(names) if val > 1]
    needing_merge = Manufacturer.objects.filter(name__iregex=f'({"|".join(dupes)})')
    # then copy the merge method and go through each one
  2. Create an empty migration to enable the PostgreSQL case-insensitive extension.
  3. Convert Manufacturer.name into a CITextField (PostgreSQL won't enforce length anyway)
  4. Repeat steps 1 and 3 for Beer

Bogus RateBeer and BeerAdvocate URLs from OTBX

https://dev.hsv.beer/api/v1/beers/788/

{
    "id": 788,
    "manufacturer": {
        "id": 284,
        "name": "Franziskaner",
        "url": "",
        "location": "Munich, Germany",
        "logo_url": "https://s3.amazonaws.com/digitalpourproducerlogos/525853c6fb890c252c69df77.png",
        "facebook_url": "",
        "twitter_handle": "",
        "instagram_handle": "",
        "untappd_url": null,
        "automatic_updates_blocked": false,
        "taphunter_url": null
    },
    "abv": "5.00",
    "color_srm": null,
    "color_srm_html": "#efef07",
    "style": null,
    "venues": [
        {
            "id": 4,
            "time_zone": "America/Chicago",
            "tap_list_provider_display": "DigitalPour",
            "name": "Old Town Beer Exchange",
            "address": "301 Holmes Avenue",
            "city": "Huntsville",
            "state": "Alabama",
            "postal_code": "35801",
            "country": "US",
            "website": "http://otbxhsv.com",
            "facebook_page": "https://www.facebook.com/OTBXHSV",
            "twitter_handle": "OTBXHSV",
            "instagram_handle": "otbxhsv",
            "tap_list_provider": "digitalpour",
            "untappd_url": null
        }
    ],
    "prices": [
        {
            "venue": "Old Town Beer Exchange",
            "serving_size": {
                "name": "Growler",
                "volume_oz": "64.0"
            },
            "price": "14.00"
        },
        {
            "venue": "Old Town Beer Exchange",
            "serving_size": {
                "name": "Crowler/Half Growler",
                "volume_oz": "32.0"
            },
            "price": "7.00"
        },
        {
            "venue": "Old Town Beer Exchange",
            "serving_size": {
                "name": "Pint",
                "volume_oz": "16.0"
            },
            "price": "5.50"
        },
        {
            "venue": "Old Town Beer Exchange",
            "serving_size": {
                "name": "10 oz",
                "volume_oz": "10.0"
            },
            "price": "4.50"
        },
        {
            "venue": "Old Town Beer Exchange",
            "serving_size": {
                "name": "4 oz Taster",
                "volume_oz": "4.0"
            },
            "price": "2.50"
        }
    ],
    "untappd_metadata": null,
    "name": "Hefe-Weisse",
    "in_production": true,
    "ibu": 4,
    "untappd_url": null,
    "beer_advocate_url": "http%3A%2F%2Fbeeradvocate.com%2Fbeer%2Fprofile%2F142%2F924",
    "rate_beer_url": null,
    "logo_url": "https://s3.amazonaws.com/digitalpourbeveragelogos/50941bb49294c325343962b6.png",
    "manufacturer_url": null,
    "automatic_updates_blocked": false,
    "taphunter_url": null,
    "stem_and_stein_pk": null
}

Note that the BA and RB URLs are URL-quoted. This came straight from the OTBX JSON data.

Expand manufacturer lookup to handle common endings

When looking up manufacturers, ignore common endings:

  1. Brewing Company
  2. Brewery
  3. Brewing
  4. Brewing Co.
  5. Brewing
  6. Beer Company
  7. Beer
  8. Beer Co.

In other words, if a manufacturer given from the API provider ends in one of those, strip it off before searching for the manufacturer.

Also create a data migration to auto-merge breweries with those endings and standardize on the ending-less version for consistency (e.g. Stone over Stone Brewing Company)

Setup fuzzy search

The search field will search across multiple datasets: beer name, manufacturer name, and beer style. The logic for this will need to be created.

Weekly notification for new beers

As a moderation helper, send a periodic email (weekly?) listing all new beers added over the past week so we can moderate them if needed.

Take the most naive approach possible to start:

  • Create a model for logging highest beer PK seen
  • Set up a task that looks for all beers higher than that PK and email that list, then update the highest PK

Fix Stem and Stein brewery guessing logic

  • Picked the wrong stone brewing company (solution: try istartswith before icontains)
    image
  • Picked Goodwood instead of Good People (solution: work from longest name first rather than shortest)
    image
  • Picked Horny Goat Brewing instead of Goat Island (same solution as previous bullet)
    image

Implement Tap model and app

Fields:

  • Room (required, depends on #6 -- merged)
  • Tap number (required)
  • Beer (optional, foreign key, depends on #16)
  • Gas type (choices: CO2, nitro, unknown/null)
  • Estimated percent remaining (float, values 0-100, nullable)
    • Some providers give an estimate of how much is left. Let's take advantage of that

Unique indexes:

  • Room + tap number

Write Parser for Stem & Stein

This one is going to be a bit of a challenge:

  • On the main page, there's a <div class="beerlist">
  • Find all the <a>s in there, and follow each link.
  • On each page:
<div class="jumbotron">
    <div class="container">
        <div style="display:table-row">
            <div style="display:table-cell;vertical-align:top;width:17px;"><img style="width:17px;padding-top:2px" src="/Display/GlassWare?color=%23261716&amp;glassware=Tulip" /></div>
            <div style="display: table-cell; padding: 3px; font-size: 22px; vertical-align: top; width:42px">$9 </div>
            <div style="display:table-cell">
                <div class="fill" style="color:white; font-size:30px"><span>Prairie Deconstructed Bomb! Vanilla Imperial Stout</span></div>
                <div style="color:slategray; font-size:18px;padding-left:20px">ABV 13% &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tulsa, OK</div>
            </div>
        </div>
    </div>
</div>
  • Look for a <div style="display:table-row">.
  • Within there:
    • There'll be an img tag pointing to the size and color: /Display/GlassWare?color=%23261716&amp;glassware=Tulip (will have to un-urlencode to color=#261716&glassware=Tulip). Color is a straight HTML color. Glassware gives us our size: 10 oz or 16 oz.
    • There will be a <div> whose text starts with $. That's your price.
    • The other <div> with text is the brewery and beer on one line. That makes things hard.
    • The <span> gives us ABV and location

Cache untappd metadata

  1. Create an untappd_cache table
    • OneToOneField with Beer
    • last_updated
    • metadata (type JSONField)
  2. Create celery task to prune stale metadata entries (have it run at something like 5 AM CDT)
  3. Have the beer details view hit untappd if the beer is not in metadata
  4. When listing beers, asynchronously fetch metadata for the beer if not present (so it can show up for the next person)
  5. Embed untappd data in beer serializer

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.