def redis_test():
    rj = Client(host='localhost', port=6379)

    # Set the key `obj` to some object
    obj = {
        'answer': 42,
        'arr': [None, True, 3.14],
        'truth': {
            'coord': 'out there'
        }
    }
    rj.jsonset('obj', Path.rootPath(), obj)

    # Get something
    print ('Is there anybody... {}?'.format(
        rj.jsonget('obj', Path('.truth.coord'))
    ))

    # Delete something (or perhaps nothing), append something and pop it
    rj.jsondel('obj', Path('.arr[0]'))
    rj.jsonarrappend('obj', Path('.arr'), 'something')
    print ('{} popped!'.format(rj.jsonarrpop('obj', Path('.arr'))))

    # Update something else
    rj.jsonset('obj', Path('.answer'), 2.17)

    # And use just like the regular redis-py client
    jp = rj.pipeline()
    jp.set('foo', 'bar')
    jp.jsonset('baz', Path.rootPath(), 'qaz')
    jp.execute()
Exemple #2
0
 def get_answers(self, rj: RedisClient, clear: bool = True) -> List[Answer]:
     """
     Get all answers the frontend has received.
     """
     if not clear:
         raise NotImplementedError
     key = f"alg-{self.ident}-answers"
     if key in rj.keys():
         pipe = rj.pipeline()
         pipe.jsonget(key, Path("."))
         pipe.jsonset(key, Path("."), [])
         answers, success = pipe.execute()
         return answers
     return []
Exemple #3
0
    def testUsageExampleShouldSucceed(self):
        "Test the usage example"

        # Create a new rejson-py client
        rj = Client(host='localhost', port=port, decode_responses=True)

        # Set the key `obj` to some object
        obj = {
            'answer': 42,
            'arr': [None, True, 3.14],
            'truth': {
                'coord': 'out there'
            }
        }
        rj.jsonset('obj', Path.rootPath(), obj)

        # Get something
        rv = rj.jsonget('obj', Path('.truth.coord'))
        self.assertEqual(obj['truth']['coord'], rv)

        # Delete something (or perhaps nothing), append something and pop it
        value = "something"
        rj.jsondel('obj', Path('.arr[0]'))
        rj.jsonarrappend('obj', Path('.arr'), value)
        rv = rj.jsonarrpop('obj', Path('.arr'))
        self.assertEqual(value, rv)

        # Update something else
        value = 2.17
        rj.jsonset('obj', Path('.answer'), value)
        rv = rj.jsonget('obj', Path('.answer'))
        self.assertEqual(value, rv)

        # And use just like the regular redis-py client
        jp = rj.pipeline()
        jp.set('foo', 'bar')
        jp.jsonset('baz', Path.rootPath(), 'qaz')
        jp.execute()
        rv1 = rj.get('foo')
        self.assertEqual('bar', rv1)
        rv2 = rj.jsonget('baz')
        self.assertEqual('qaz', rv2)
class Csv2Redis:
    def __init__(self, filename: str):
        self.rj = Client(host=os.environ.get("REDIS_HOST", "localhost"),
                         decode_responses=True)
        self.filename = filename
        # Hardcoded data we need for 2020 election candidates
        self.candidate_ids = {
            "David Cohen": "3h2g45h3j",
            'Jacob "Jake" Tonkel': "089wegvb7",
            "Dev Davis": "456hjkl2l",
            "Lan Diep": "cf90g8cii",
        }
        self.referendums = {
            "df7g8y6d8": {
                "id":
                "df7g8y6d8",
                "electionDate":
                "2020-11-03",
                "name":
                "Measure G",
                "description":
                "Charter Amendment regarding Independent Police Auditor, Planning Commission, Redistricting",
                "ballotLanguage":
                "Shall the City Charter be amended to: expand the Independent Police Auditor’s oversight, including review of officer-involved shootings and use of force incidents causing death or great bodily injury, review of department-initiated investigations against officers, and other technical amendments; increase the Planning Commission to 11 members with Council appointing one member from each Council District and one “at-large” member; and allow the Council to establish timelines for redistricting when Census results are late?",
            },
            "35j6kh45m": {
                "id":
                "35j6kh45m",
                "electionDate":
                "2020-11-03",
                "name":
                "Measure H",
                "description":
                "Cardroom Tax",
                "ballotLanguage":
                "To fund general San Jose services, including fire protection, disaster preparedness, 911 emergency response, street repair, youth programs, addressing homelessness, and supporting vulnerable residents, shall an ordinance be adopted increasing the cardroom tax rate from 15% to 16.5%, applying the tax to third party providers at these rates: up to $25,000,000 at 5%; $25,000,001 to $30,000,000 at 7.5%; and over $30,000,000 at 10%, increasing card tables by 30, generating approximately $15,000,000 annually, until repealed?",
            },
        }
        self.extra_candidate_data = {
            "David Cohen": {
                "seat": "Councilmember District 4",
                "ballotDesignation":
                "Governing Board Member, Berryessa Union School District",
                "website": "www.electdavidcohen.com",
                "twitter": "electdavidcohen",
                "votersEdge":
                "http://votersedge.org/ca/en/election/2020-11-03/santa-clara-county/city-council-city-of-san-jose-district-4/David-Cohen",
                "profilePhoto": "",
            },
            "Lan Diep": {
                "seat": "Councilmember District 4",
                "ballotDesignation": "City Councilmember",
                "website": "www.lanforsanjose.com",
                "twitter": "ltdiep",
                "votersEdge":
                "http://votersedge.org/ca/en/election/2020-11-03/santa-clara-county/city-council-city-of-san-jose-district-4/Lan-Diep",
                "profilePhoto": "",
            },
            'Jacob "Jake" Tonkel': {
                "seat": "Councilmember District 6",
                "ballotDesignation": "Senior Biomedical Engineer",
                "website": "www.jake4d6.com",
                "twitter": "jake4d6",
                "votersEdge":
                "http://votersedge.org/ca/en/election/2020-11-03/santa-clara-county/city-council-city-of-san-jose-district-6/Jake-Tonkel",
                "profilePhoto": "",
            },
            "Dev Davis": {
                "seat": "Councilmember District 6",
                "ballotDesignation": "Councilwoman/Mother",
                "website": "www.devdavis.com",
                "twitter": "devdavisca",
                "votersEdge":
                'http://votersedge.org/ca/en/election/2020-11-03/santa-clara-county/city-council-city-of-san-jose-district-6/Devora-"Dev"-Davis',
                "profilePhoto": "",
            },
        }

    def read_data_sheet(self):
        # Read the clean csv file from Google sheet. This file won't work on just aggregated csv from scrapper.
        # Skip every other line. If the clean csv changes to every line, we need to update this as well.
        if not os.path.exists(self.filename):
            logger.warning(
                "{} does not exist. Please double check the file path.".format(
                    self.filename))
        if not os.path.isfile(self.filename):
            logger.warning(
                "Only process csv file. Please double check {} is a file.".
                format(self.filename))
        filetype, _ = mimetypes.guess_type(self.filename)
        if "csv" in filetype:
            self.data = pd.read_csv(
                self.filename,
                skiprows=lambda x: x % 2 == 1,
                sep=",",
                quotechar='"',
                encoding="iso-8859-1",
            )
        elif "spreadsheet" in filetype:
            logger.info("Reading plain text csv is faster and is encouraged.")
            self.data = pd.read_excel(
                self.filename,
                skiprows=lambda x: x % 2 == 1,
            )
        else:
            logger.warning("Only read csv and spreadsheet file for now.")
            return
        if self.data.shape[0] < 100:
            logger.info("{} only have {} row.".format(self.filename,
                                                      self.data.shape[0]))
        # Add candidate ID column. candidate ID is <Ballot Item>;<CandidateControlledName>;<Election Date>
        self.data["Ballot Item"] = self.data["Ballot Item"].str.replace(
            "-", " ")
        self.data["ID"] = (
            self.data["Ballot Item"].str.replace(" ", "_") + ";" +
            self.data["CandidateControlledName"].str.replace(" ", "_") + ";" +
            self.data["Election Date"].map(str))
        # Round Amount to decimal 2
        self.data["Amount"] = (self.data["Amount"].map(str).str.replace(
            ",", "").replace("$",
                             "").replace("'",
                                         "").astype(float).round(decimals=2))
        self.metadata = str(
            datetime.fromtimestamp(os.path.getmtime(self.filename)))

    def get_ids(self, ids) -> str:
        """
        :param ids: list of strings in format <Ballot Item>;<CandidateControlledName>;<Election Date>
        :return: unique candidate id from candidate_ids dict
        """
        ret = []
        for id in ids:
            cand_name = id.split(";")[1].replace("_", " ")
            # If there are independent contributions, they won't be associated with a candidate.
            if cand_name in self.candidate_ids:
                ret.append(self.candidate_ids[cand_name])
        return ret

    def set_path_in_redis(self, path_name, data_shape):
        """
        :param path_name: str representing the name of our new path
        :param data_shape: dict/json of data we're inserting
        """
        with self.rj.pipeline() as pipe:
            pipe.jsonset(path_name, Path.rootPath(), data_shape)
            pipe.execute()
        logger.debug("The new shape set in redis is {}".format(
            self.rj.jsonget(path_name)))

    def set_referendums_shape_in_redis(self) -> None:
        """
        Set the referendums key in redis with the appropriate data (currently hardcoded)
        """
        try:
            self.set_path_in_redis(
                "referendums",
                {"Referendums": list(self.referendums.values())})
        except Exception as e:
            logger.debug(e)

    def set_metadata_shape_in_redis(self) -> None:
        """
        Set metadata key in redis with date last processed
        """
        try:
            self.set_path_in_redis("metadata",
                                   {"DateProcessed": self.metadata})
        except Exception as e:
            logger.debug(e)

    def setElectionShapeInRedis(self) -> bool:
        """
        Populate election shape into redis
        """
        data = self.data
        electionShape = {"Elections": {}}
        dataAmount = data[[
            "Ballot Item",
            "CandidateControlledName",
            "Election Date",
            "Entity_City",
            "Entity_ST",
            "Amount",
            "Rec_Type",
            "ID",
        ]]
        # There are 4 types RCPT, EXPN, LOAN, S497.
        elections = {}
        for ed in dataAmount["Election Date"].unique():
            dataPerElectionDate = dataAmount[dataAmount["Election Date"] == ed]
            totalContributions = dataPerElectionDate[
                dataPerElectionDate["Rec_Type"] == "RCPT"]["Amount"].sum(
                ).round(decimals=2) + dataPerElectionDate[
                    dataPerElectionDate["Rec_Type"] ==
                    "LOAN"]["Amount"].sum().round(decimals=2)

            officeElections = []
            # Hardcoded from frontend data
            referendums = list(self.referendums.keys())
            for bi in dataPerElectionDate["Ballot Item"].unique():
                dataPerElectionDateAndBallotItem = dataPerElectionDate[
                    dataPerElectionDate["Ballot Item"] == bi]
                totalContributionsPerBallotItem = (
                    dataPerElectionDateAndBallotItem[
                        dataPerElectionDateAndBallotItem["Rec_Type"] == "RCPT"]
                    ["Amount"].sum().round(
                        decimals=2)) + dataPerElectionDateAndBallotItem[
                            dataPerElectionDateAndBallotItem["Rec_Type"] ==
                            "LOAN"]["Amount"].sum().round(decimals=2)
                if not "measure" in bi:
                    officeElections.append({
                        "Title":
                        bi,
                        "CandidateIDs":
                        self.get_ids(dataPerElectionDateAndBallotItem["ID"].
                                     unique().tolist()),
                        "TotalContributions":
                        totalContributionsPerBallotItem,
                    })
                else:
                    referendums.append({
                        "Title":
                        bi,
                        "Description":
                        bi,
                        "TotalContributions":
                        totalContributionsPerBallotItem,
                    })
            elections = {
                "Title": "{} Election Cycle".format(ed.split("/")[2]),
                "Date": ed,
                "TotalContributions": totalContributions,
                "FundingByGeo": self.getFundingByGeo(dataPerElectionDate),
                "OfficeElections": officeElections,
                "Referendums": referendums,
            }
            electionShape["Elections"][ed] = elections
            electionShape["Metadata"] = self.metadata
        self.set_path_in_redis("elections", electionShape)
        return True

    def setCandidateShapeInRedis(self, electionDate="11/3/2020") -> bool:
        """
        Populate candidate shape into redis
        Redis data spec
        Candidates: [{
            ID: "councilmember-district-6;dev-davis;11-3-2020",
            Name: "Dev Davis",
            TotalRCPT: 300,
            TotalLOAN: 100,
            TotalEXPN: 100,
            FundingByType: {
                IND: 300,
                COM: 100
            },
            FundingByGeo: {
                CA: 300,
                NonSJ: 200,
                SJ: 100
                NonCA: 0
            }
            ExpenditureByType: {
        # TODO: We could have populated candidates for all election date but right now the spec only asks for the current year.
        """
        # TODO: TotalFunding - understand how TotalFunding is calculated and perhaps add TotalFunding
        data = self.data
        candidateShape = {"Candidates": []}
        dataAmount = data[[
            "Ballot Item",
            "CandidateControlledName",
            "Election Date",
            "Amount",
            "Rec_Type",
            "Entity_Cd",
            "Entity_Nam L",
            "Entity_Nam F",
            "Entity_City",
            "Entity_ST",
            "Expn_Code",
            "ID",
        ]]
        candidateIDs = pd.unique(dataAmount["ID"])

        for cid in candidateIDs:
            candidate = dict()
            name = cid.split(";")[1].replace("_", " ")
            # This is for contributions to "independent" committees
            if name not in self.candidate_ids:
                continue
            candidate["ID"] = self.candidate_ids[name]
            candidate["Name"] = name
            candidate.update(self.extra_candidate_data[name])
            dataPerCandidate = dataAmount[
                (dataAmount["CandidateControlledName"] == name)
                & (dataAmount["Election Date"] == electionDate)]

            # Get transaction by type
            totalByRecType = (dataPerCandidate.groupby(
                ["Rec_Type"])[["Amount"]].sum().round(decimals=2).to_dict())
            if "RCPT" in totalByRecType["Amount"]:
                candidate["TotalRCPT"] = totalByRecType["Amount"]["RCPT"]
            if "EXPN" in totalByRecType["Amount"]:
                candidate["TotalEXPN"] = totalByRecType["Amount"]["EXPN"]
            if "LOAN" in totalByRecType["Amount"]:
                candidate["TotalLOAN"] = totalByRecType["Amount"]["LOAN"]
            if "S497" in totalByRecType["Amount"]:
                candidate["TotalS497"] = totalByRecType["Amount"]["S497"]
            candidate["TotalFunding"] = candidate["TotalRCPT"] + candidate[
                "TotalLOAN"]

            # Get funding by committee type
            recpDataPerCandidate = dataPerCandidate[
                dataPerCandidate["Rec_Type"].isin(
                    ["RCPT", "LOAN"]
                )]  # dataPerCandidate[(dataPerCandidate['Rec_Type'] == 'RCPT')
            totalByComType = (recpDataPerCandidate.groupby(
                ["Entity_Cd"])[["Amount"]].sum().round(decimals=2).to_dict())
            candidate["FundingByType"] = totalByComType["Amount"]

            # Get funding by geo
            candidate["FundingByGeo"] = self.getFundingByGeo(
                recpDataPerCandidate)

            # Get expenditure by type
            expnDataPerCandidate = dataPerCandidate[
                dataPerCandidate["Rec_Type"] == "EXPN"]
            totalByExpnType = (expnDataPerCandidate.groupby(
                ["Expn_Code"])[["Amount"]].sum().round(decimals=2).to_dict())
            candidate["ExpenditureByType"] = totalByExpnType["Amount"]

            # Get Committees
            totalByCommittees = (recpDataPerCandidate[
                recpDataPerCandidate["Entity_Cd"] == "COM"].groupby([
                    "Entity_Nam L"
                ])[["Amount"]].sum().round(decimals=2).to_dict())
            totalByCommitteesList = [{
                "Name":
                c,
                "TotalFunding":
                totalByCommittees["Amount"][c]
            } for c in totalByCommittees["Amount"]]
            candidate["Committees"] = totalByCommitteesList

            candidateShape["Candidates"].append(candidate)
            candidateShape["Metadata"] = self.metadata
            logger.debug(candidateShape)
        self.set_path_in_redis("candidates", candidateShape)
        return True

    def getFundingByGeo(self, data):
        """
        Get total funding by GEO.

        :param data: A filtered Pandas table with Entity_City and Entity_ST columns populated.
        :return: Geo funding of the shape:
            {
                CA: 300,
                NonSJ: 200,
                SJ: 100
                NonCA: 0
            }

        """
        totalByGeoSJ = (
            data[data["Entity_City"] == "San Jose"]["Amount"].sum().round(
                decimals=2))
        totalByGeoNonSJ = (
            data[data["Entity_City"] != "San Jose"]["Amount"].sum().round(
                decimals=2))

        totalByGeoCA = data[data["Entity_ST"] == "CA"]["Amount"].sum().round(
            decimals=2)
        totalByGeoNonCA = (
            data[data["Entity_ST"] != "CA"]["Amount"].sum().round(decimals=2))
        return {
            "SJ": totalByGeoSJ,
            "NonSJ": totalByGeoNonSJ,
            "CA": totalByGeoCA,
            "NonCA": totalByGeoNonCA,
        }
Exemple #5
0
    'arr': [None, True, 3.14],
    'truth': {
        'coord': 'out there'
    }
}

jsondata = json.dumps(obj)

rj.jsonset('obj', Path('A2AA'), obj)

# Get something
temp = rj.jsonget('obj', Path('A2AA.truth.coord'))

print (f'Is there anybody... {temp}?')

# Delete something (or perhaps nothing), append something and pop it
rj.jsondel('obj', Path('.arr[0]'))
rj.jsonarrappend('obj', Path('.arr'), 'something')
popped = rj.jsonarrpop('obj', Path('.arr'))
print(f'{popped} popped!')

# Update something else
rj.jsonset('obj', Path('.answer'), 2.17)

# And use just like the regular redis-py client
jp = rj.pipeline()
jp.set('foo', 'bar')
jp.jsonset('baz', Path.rootPath(), 'qaz')
jp.execute()