There are a few points in the existing code that I think could be improved.
###Confusing function name write_new_json
Naming is important. Good names make your code more readable, and easier to understand. Bad names make it less readable. Names are bad when they don't describe what the thing is/does, or conversely, if they seem to describe something that the thing isn't/doesn't do.
Usually, the verbs "read" and "write" in programming refer to fetching data from, and sending data to, some external/system resource. Especially a file. So the name write_new_json() sounds like you're writing some JSON to a file. But that's not what the function is doing at all. It's actually transforming some JSON into some other kind of JSON. Therefore write_new_json() is a misleading name.
I would pick a new name that avoids write and probably even avoids json; describing the input and output formats of a function as "JSON" does nothing to capture the purpose of the function. How about build_clock_punches? build_time_logs?
###Confusing variable name i
for i in data:
Another naming nitpick. In most programming languages, it is conventional to name your loop counter variable i. In Python, this would be something like for i in range(0, 1000):.1 But in your case, i is not a loop counter; it is not even a number. Therefore the name is confusing. Try using a more descriptive name, e.g. for clock_punch in data: or for clock_event in data:.
###Unnecessary use of in operator
if i['clock_type'] in [3]:
You're using in because [3] is a list, but there doesn't seem to be any reason that [3] needs to be a list. You can just compare the value directly using the equality operator, e.g. if clock_event['clock_type'] == 3:.
###Unnecessary variable break_count
As far as I can tell, you only use break_count to keep track of the length of request["break"]. Persistently tracking the length of a list in a separate variable is a bad idea: it is unnecessary, and it opens you up to bugs. Suppose you go in later and add a new line of code that changes the length of request["break"], but forget to add that second line of code that updates break_count? Or vice versa?
To find the length of a list directly, use len().
if clock_event['clock_type'] == 3:
last_break_index = len(request["break"]) - 1
request["break"][last_break_index].update({"end": clock_event["end"]})
But wait! There's an even better approach here. Python offers a handy syntax for getting the last item in a list: my_list[-1].
if clock_event['clock_type'] == 3:
request["break"][-1].update({"end": clock_event["end"]})
And for the if break_count == 0: line, it is better to check for the non-existence of request["break"] directly.
if clock_event['clock_type'] == 4:
if "break" not in request:
request.update({"break": []})
###Too trusting
Your program assumes the input data will always be well-formed. What happens if it's not? Suppose the system that produces your input goes haywire, and you receive a break-start, and then two break-ends? Or receive a break-end without receiving any break-starts first? Or data with a mix of two or more names?2 Depending on the nature of the nonsense input, your program would either crash entirely, or produce a nonsense output of its own.
What your program should do in these cases is detect that something is wrong, and then throw some kind of exception that explains what the problem was. Unfortunately I don't have time to elaborate on every possible case you might encounter, so I'll have to leave that as an exercise for you.
Of course, if this is a school assignment or other non-real-world project where you have a guarantee that the data will be well-formed, you may not want to go through all the extra trouble. But it's something you should keep in mind for future projects.
###Thoughts on using a data class
You mentioned wanting to use a data class to ingest the input data. If all you are planning to do is replace all the dicts in the list data with equivalent data class objects, and then run those objects through the same logic you have now...then you don't gain that much. You do get the type annotations, and there's nothing wrong with having those, but it's not a huge benefit.
1Technically, Python doesn't even have "loop counters" like some other languages do. All Python for-loops are foreach loops rather than "traditional" for-loops. But it's close enough that the conventions around i still apply.
2Actually, it seems perfectly reasonable for the upstream system to send you data for two or more names at once. You may want to enhance your code to support that (or not, I don't know your circumstances).