def find_cable(
*,
meters: Union[int, str, None] = None, # Required
amps: Union[int, str] = "40",
volts: Union[int, str] = "220",
material: Literal["cu", "al", "?"] = "cu",
max_lines: Union[int, str] = "1",
phases: Literal["1", "3", 1, 3] = "2",
conduit: Optional[Union[str, bool]] = None,
ground: Union[int, str] = "1"
):
"""
Calculate the type of cable needed for an electrical system.
Args:
meters: Cable length (one line) in meters. Required keyword.
amps: Load in Amperes. Default: 40 A.
volts: System voltage. Default: 220 V.
material: 'cu' (copper) or 'al' (aluminum). Default: cu.
max_lines: Maximum number of line conductors allowed. Default: 1
phases: Number of phases for AC (1, 2 or 3). Default: 2
conduit: Conduit type or None.
ground: Number of ground wires.
Returns:
dict with cable selection and voltage drop info, or {'awg': 'n/a'} if not possible.
"""
gw.info(f"Calculating AWG for {meters=} {amps=} {volts=} {material=}")
# Convert and validate inputs
amps = int(amps)
meters = int(meters)
volts = int(volts)
max_lines = int(max_lines)
phases = int(phases)
ground = int(ground)
assert amps >= 10, f"Minimum load for this calculator is 15 Amps. Yours: {amps=}."
assert (amps <= 546) if material == "cu" else (amps <= 430), f"Max. load allowed is 546 A (cu) or 430 A (al). Yours: {amps=} {material=}"
assert meters >= 1, "Consider at least 1 meter of cable."
assert 110 <= volts <= 460, f"Volt range supported must be between 110-460. Yours: {volts=}"
assert material in ("cu", "al"), "Material must be 'cu' (copper) or 'al' (aluminum)."
assert phases in (1, 2, 3), "AC phases 1, 2 or 3 to calculate for. DC not supported."
with gw.sql.open_connection(autoload=True) as cursor:
# Use correct voltage drop formula: includes current (amps)
if phases in (2, 3):
expr = "sqrt(3) * :meters * :amps * k_ohm_km / 1000"
else:
expr = "2 * :meters * :amps * k_ohm_km / 1000"
sql = f"""
SELECT awg_size, line_num, {expr} AS vdrop
FROM awg_cable_size
WHERE (material = :material OR :material = '?')
AND ((amps_75c >= :amps AND :amps > 100)
OR (amps_60c >= :amps AND :amps <= 100))
AND line_num <= :max_lines
ORDER BY awg_size DESC
"""
params = {
"amps": amps,
"meters": meters,
"material": material,
"volts": volts,
"max_lines": max_lines,
}
gw.debug(f"AWG find-cable SQL candidates: {sql.strip()}, params: {params}")
cursor.execute(sql, params)
candidates = cursor.fetchall()
gw.debug(f"AWG find-cable candidates fetched: {candidates}")
# Iterate and pick first cable within voltage drop threshold (3%)
for awg_size, line_num, vdrop in candidates:
perc = vdrop / volts
gw.debug(f"Evaluating AWG={awg_size}, lines={line_num}, vdrop={vdrop:.6f}, vdperc={perc*100:.4f}%")
if perc <= 0.03:
awg_res = AWG(awg_size)
cables = line_num * (phases + ground)
result = {
"awg": str(awg_res),
"meters": meters,
"amps": amps,
"volts": volts,
"lines": line_num,
"vdrop": vdrop,
"vend": volts - vdrop,
"vdperc": perc * 100,
"cables": f"{cables - 1}+{ground}",
"total_meters": f"{(cables - 1) * meters}+{meters*ground}",
}
if conduit:
if conduit is True:
conduit = "emt"
fill = find_conduit(awg_res, cables, conduit=conduit)
result["conduit"] = conduit
result["pipe_inch"] = fill["size_inch"]
gw.debug(f"Selected cable result: {result}")
return result
# If no suitable cable found, return 'n/a'
gw.debug("No cable found within voltage drop limit (3%). Returning 'n/a'.")
return {"awg": "n/a"}