Computing fluxes accurately

First, it seems like you have a scaling mistake in your code:

What happened to dt for the ds term.

Secondly, your code can be simplified quite alot:

mesh, cell_markers, facet_markers = gmshio.read_from_msh("the_mesh.msh", MPI.COMM_WORLD, gdim=3)
# Define function space
V = FunctionSpace(mesh, ("Lagrange", 2))
# Define the "unknown" variable of the equation.
Temp = Function(V)
  

# Physical parameters.
κ = 49.0 
C_p = 120 
density = 9.7e3 # kg / m^3.
T_amb = 0.0
q_SB = 5.6704e-8
h = 10

T_fixed_surface = 21
convection_surface = 22
T_left = 100


# Define Dirichlet boundary conditions to the facets corresponding to the left end of the rod.
left_facets = facet_markers.find(T_fixed_surface)
left_dofs = locate_dofs_topological(V, mesh.topology.dim-1, left_facets)
bc = dirichletbc(ScalarType(T_left), left_dofs, V)
bcs = [bc]

x = SpatialCoordinate(mesh)
dx = Measure("dx", domain=mesh,subdomain_data=cell_markers)
ds = Measure("ds", domain=mesh, subdomain_data=facet_markers)


# Initial temperature of the rod.
T_0 = 0.0

def temp_init(x):
    values = np.full(x.shape[1], T_0, dtype = ScalarType) 
    return values

Temp.name = "Temp"
Temp.interpolate(temp_init)

# Simulation parameters.
t = 0 # Start time
t_final = 6.0 # Final time
num_steps = 100
dt = t_final / num_steps # time step size

T_n = Function(V)
T_n.name = "T_n"
T_n.interpolate(temp_init)

v = TestFunction(V)

F_T = density * C_p * (Temp - T_n )* v * dx + dt * dot(κ * grad(Temp), grad(v)) * dx + dt * v * h * (Temp - T_amb) * ds(convection_surface)


# Define the problem.
problem = NonlinearProblem(F_T, Temp, bcs=bcs)
solver = NewtonSolver(mesh.comm, problem)
solver.convergence_criterion = "residual"
solver.rtol = 1e-14
solver.report = True


ksp = solver.krylov_solver
opts = Options()
option_prefix = ksp.getOptionsPrefix()
opts[f"{option_prefix}ksp_type"] = "preonly"
opts[f"{option_prefix}pc_type"] = "lu"
opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"
opts[f"{option_prefix}ksp_max_it"]= 10000

ksp.setFromOptions()

times, Qs_ratios, spatial_average_temperatures = [], [], []
n = FacetNormal(mesh)
left_flux = form(dot(-κ * grad(Temp), n)*ds(T_fixed_surface))
right_flux = form(dot(-κ * grad(Temp), n)*ds(convection_surface))

Q = VectorFunctionSpace(mesh, ("DG", 1))
q = Function(Q)
flux_calculator = Expression(-κ * grad(Temp), Q.element.interpolation_points())



out_file = VTXWriter(mesh.comm, "t.bp", [Temp], engine="BP4")
out_flux = VTXWriter(mesh.comm, "flux.bp", [q], engine="BP4")

#set_log_level(LogLevel.INFO)
for time_step in np.linspace(t, t_final, num_steps):    
    times.append(t)  

    # Compute total heat convected away.

    # Heat entering the rod from the left side.
    Q_in = assemble_scalar(left_flux)
            
    # Heat leaving the rod on all other surfaces.
    Q_out = assemble_scalar(right_flux)
            
    if Q_in == 0:
        Qs_ratio = 0
    else:
        Qs_ratio = abs(Q_out) / abs(Q_in)
    print(Q_out, Q_in, time_step)
    Qs_ratios.append(Qs_ratio * 100)
    t += dt
    out_file.write(t)
    n, converged = solver.solve(Temp)
            
    T_n.x.array[:] = Temp.x.array
    q.interpolate(flux_calculator)        
    out_flux.write(time_step)
    #spatial_average_temperature = assemble_scalar(form(Temp * dx(domain = mesh))) / assemble_scalar(form(1 * dx(domain=mesh)))
    #spatial_average_temperatures.append(spatial_average_temperature)

out_flux.close()
out_file.close()
#plt.plot(times, spatial_average_temperatures)
plt.plot(times, Qs_ratios)
plt.savefig("Test.png")
plt.close()

I would also like to note that your mesh is incredibly coarse (and it is scaled such that the cell jacobian is very tiny, which could lead to numerical issues. For instance the fluxes are very large (1e6 magnitude)

You are also not attaching the appropriate null space (the constant space) to your multi-grid solver.

Considering the code above with P1, P2, P3 gives