**Start with a solid foundation**. Even simple scripts like this can benefit by
following a simple rule: put all algorithmic code inside of functions. At the
top level, you can perform imports or define constants, functions, or classes.
Everything else must be in functions. For this program, we could start with the
following sketch:

    jack = {...}
    james = {...}
    dylan = {...}
    jess = {...}
    tom = {...}

    def main():
        student = get_student()
        score = compute_score(student)
        letter = compute_grade(score)
        print(score, letter)

    def get_student():
        return jack

    def compute_score(student):
        return 100

    def compute_grade(score):
        return 'A'

    if __name__ == '__main__':
        main()

**Take advantage of data structures**. You have 5 constants in the form of
student dicts. And you have a name that will be entered by the program user
(e.g., 'jack'). You don't need exotic techniques like `eval()` to transform
that user-entered string into a Python variable. Rather, you need a dict
mapping each student name to its corresponding dict of information about the
student. This simple function will do the trick:

    def collect_students():
        return {
            d['name'].lower() : d
            for d in (jack, james, dylan, jess, tom)
        }

**Validate user input**. Once we have that utility function, we can implement
the behavior to get user input and return the corresponding student dict. Code
to collect user input should normally be written with an awareness that people
make mistakes. A `while True` loop is often the most flexible mechanism for
these situations: get the input, validate it, and return if OK:

    def get_student():
        students = collect_students()
        while True:
            name = input('Enter student name: ')
            try:
                return students[name.lower()]
            except KeyError:
                pass

**Use data structures to simplify algorithms**. Your code to compute the
student's overall score is repetitive (it computes 3 different means) and hard
to read (a long, dense line of code). However, if we define a simple data
structure -- in this case, a dict mapping each type of coursework to its weight
in the overall score -- we can compute the overall score more understandably:

    def compute_score(student):
        weights = {'assignment': 0.1, 'test': 0.7, 'lab': 0.2}
        return sum(
            w * mean(student[k])
            for k, w in weights.items()
        )

    def mean(vals):
        # Better: raise exception if vals is empty.
        # Even better: use statistics.mean().
        return sum(vals) / len(vals)

**Use data structures to simplify algorithms -- yet again**. Your code to
compute the letter grade is algorithmically complex (relying on recursion) and
opaque (with cryptic variable names like `asci` and `a`). None of that is
needed if you define a dict mapping each minimum-score to its corresponding
letter grade. (Note that this relies on the insertion-ordering
property of dicts in modern Python.)

    def compute_grade(score):
        grades = {90: 'A', 80: 'B', 70: 'C', 60: 'D'}
        for min_score, letter in grades.items():
            if score >= min_score:
                return letter
        return 'E'