Refinement types can statically capture range checks and other similar assertions. There is a project to add them to Haskell (Liquid Haskell) which is already quite useable.
Of course, static types are machine checked prior to running the program. None of your Python "types" exist until runtime, by which point it is too late. The program crashing due to an assertion failed error is logically still a crash.
We will have to agree to disagree on the roll of static types when building large systems. I have also spent over a decade working on very large codebases. The more "dynamic" a system was (late binding, reflection, string-based lookup, variants), the harder it was to statically reason about the code and avoid introducing errors. In a fast-paced business where hundreds of people are changing the same codebase daily, I do not understand how one could stay sane without static types. Python was never designed with such use-cases in mind.