Overview
The Safety module manages work permits, risk assessments, safety inspections, incidents, and PPE (Personal Protective Equipment) tracking to ensure regulatory compliance and workplace safety.
Key Features
Work Permits Hot work, confined space, heights, and LOTO permits
Risk Analysis (JSA/AST) Job Safety Analysis and risk assessments
Safety Inspections Scheduled safety audits and checklists
Incident Management Report and investigate safety incidents
PPE Tracking Assign and track protective equipment
Compliance OSHA and regulatory compliance tracking
Data Model
PermisoTrabajo (Work Permit)
class PermisoTrabajo ( models . Model ):
ESTADOS = [
( 'BORRADOR' , 'Draft' ),
( 'PENDIENTE' , 'Pending Approval' ),
( 'APROBADO' , 'Approved' ),
( 'EN_EJECUCION' , 'In Progress' ),
( 'CERRADO' , 'Closed' ),
( 'CANCELADO' , 'Cancelled' ),
]
numero = models.CharField( max_length = 50 , unique = True )
tipo_permiso = models.ForeignKey( 'TipoPermiso' , on_delete = models. PROTECT )
# Work details
descripcion_trabajo = models.TextField()
ubicacion = models.ForeignKey( 'activos.Ubicacion' , on_delete = models. PROTECT )
activo = models.ForeignKey( 'activos.Activo' , on_delete = models. SET_NULL )
# Timing
fecha_inicio = models.DateTimeField()
fecha_fin = models.DateTimeField()
# Personnel
solicitante = models.ForeignKey(User, on_delete = models. SET_NULL )
supervisor = models.ForeignKey(User, on_delete = models. SET_NULL )
# Status
estado = models.CharField( max_length = 20 , choices = ESTADOS )
# Link to JSA
analisis_riesgo = models.ForeignKey( 'AnalisisRiesgo' , on_delete = models. SET_NULL )
TipoPermiso (Permit Type)
class TipoPermiso ( models . Model ):
nombre = models.CharField( max_length = 100 )
descripcion = models.TextField()
color = ColorField( default = '#FF0000' )
requiere_aprobacion_supervisor = models.BooleanField( default = True )
requiere_aprobacion_seguridad = models.BooleanField( default = True )
Common permit types:
Hot Work (welding, cutting, grinding)
Confined Space Entry
Work at Heights
Lockout/Tagout (LOTO)
Excavation
Electrical Work
AnalisisRiesgo (Risk Analysis / JSA)
class AnalisisRiesgo ( models . Model ):
nombre = models.CharField( max_length = 200 )
descripcion = models.TextField()
fecha = models.DateField( auto_now_add = True )
responsable = models.ForeignKey(User, on_delete = models. SET_NULL )
class PasoTrabajo ( models . Model ):
analisis = models.ForeignKey( 'AnalisisRiesgo' , on_delete = models. CASCADE )
numero = models.IntegerField()
descripcion = models.TextField()
class Riesgo ( models . Model ):
SEVERIDAD_CHOICES = [
( 'BAJA' , 'Baja' ),
( 'MEDIA' , 'Media' ),
( 'ALTA' , 'Alta' ),
( 'CRITICA' , 'CrÃtica' ),
]
paso_trabajo = models.ForeignKey( 'PasoTrabajo' , on_delete = models. CASCADE )
descripcion = models.TextField()
severidad = models.CharField( max_length = 10 , choices = SEVERIDAD_CHOICES )
probabilidad = models.IntegerField() # 1-5
class Control ( models . Model ):
riesgo = models.ForeignKey( 'Riesgo' , on_delete = models. CASCADE )
descripcion = models.TextField()
tipo = models.CharField( max_length = 50 ) # Engineering, Administrative, PPE
Work Permit Workflow
Request Permit
Worker creates permit request with work details permiso = PermisoTrabajo.objects.create(
tipo_permiso = tipo,
descripcion_trabajo = "Welding on cooling tower" ,
ubicacion = ubicacion,
fecha_inicio = start_time,
estado = 'BORRADOR'
)
Complete JSA
Identify hazards and control measures jsa = AnalisisRiesgo.objects.create(
nombre = "Welding JSA" ,
responsable = request.user
)
permiso.analisis_riesgo = jsa
permiso.save()
Verify Requirements
Check all permit requirements are met for req in permiso.tipo_permiso.requisitos.all():
VerificacionRequisito.objects.create(
permiso = permiso,
requisito = req,
verificado = True ,
verificado_por = supervisor
)
Approval
Supervisor and safety officer approve permiso.estado = 'APROBADO'
permiso.save()
Execute Work
Work proceeds under permit conditions permiso.estado = 'EN_EJECUCION'
permiso.save()
Close Permit
Complete work and close permit permiso.estado = 'CERRADO'
permiso.fecha_cierre = timezone.now()
permiso.save()
Incident Management
Incidente (Incident)
class Incidente ( models . Model ):
TIPOS = [
( 'ACCIDENTE' , 'Accident' ),
( 'CASI_ACCIDENTE' , 'Near Miss' ),
( 'CONDICION_INSEGURA' , 'Unsafe Condition' ),
]
numero = models.CharField( max_length = 50 , unique = True )
tipo = models.ForeignKey( 'TipoIncidente' , on_delete = models. PROTECT )
fecha = models.DateTimeField()
# Location
ubicacion = models.ForeignKey( 'activos.Ubicacion' , on_delete = models. PROTECT )
activo = models.ForeignKey( 'activos.Activo' , on_delete = models. SET_NULL )
# Description
descripcion = models.TextField()
causa_raiz = models.TextField( blank = True )
acciones_correctivas = models.TextField( blank = True )
# Personnel
reportado_por = models.ForeignKey(User, on_delete = models. SET_NULL )
personas_involucradas = models.ManyToManyField(User)
# Severity
severidad = models.CharField( max_length = 20 )
dias_perdidos = models.IntegerField( default = 0 )
Incident Investigation
Root cause analysis and corrective actions:
def investigar_incidente ( request , incidente_id ):
incidente = Incidente.objects.get( id = incidente_id)
if request.method == 'POST' :
# Record investigation findings
incidente.causa_raiz = request. POST .get( 'causa_raiz' )
incidente.acciones_correctivas = request. POST .get( 'acciones' )
incidente.estado = 'INVESTIGADO'
incidente.save()
# Create follow-up tasks
for accion in request. POST .getlist( 'acciones[]' ):
# Create maintenance order or action item
pass
return render(request, 'investigacion.html' , { 'incidente' : incidente})
Safety Inspections
Inspeccion (Inspection)
class Inspeccion ( models . Model ):
tipo = models.ForeignKey( 'TipoInspeccion' , on_delete = models. PROTECT )
fecha = models.DateField()
ubicacion = models.ForeignKey( 'activos.Ubicacion' , on_delete = models. PROTECT )
inspector = models.ForeignKey(User, on_delete = models. SET_NULL )
class ResultadoInspeccion ( models . Model ):
inspeccion = models.ForeignKey( 'Inspeccion' , on_delete = models. CASCADE )
item = models.ForeignKey( 'ItemInspeccion' , on_delete = models. PROTECT )
conforme = models.BooleanField()
observaciones = models.TextField( blank = True )
foto = models.ImageField( upload_to = 'inspecciones/' , blank = True )
PPE Management
AsignacionEPP (PPE Assignment)
class AsignacionEPP ( models . Model ):
usuario = models.ForeignKey(User, on_delete = models. CASCADE )
tipo_epp = models.CharField( max_length = 100 )
fecha_asignacion = models.DateField( auto_now_add = True )
fecha_vencimiento = models.DateField()
numero_serie = models.CharField( max_length = 50 )
estado = models.CharField( max_length = 20 ) # Activo, Vencido, Retirado
Compliance Reporting
OSHA Recordkeeping
Track recordable incidents:
def reporte_osha ( request , year ):
"""Generate OSHA 300 log"""
incidentes_registrables = Incidente.objects.filter(
fecha__year = year,
tipo__registrable_osha = True
).order_by( 'fecha' )
# Calculate injury rates
total_horas = calcular_horas_trabajadas(year)
tasa_incidentes = ( len (incidentes_registrables) * 200000 ) / total_horas
return render(request, 'osha_300.html' , {
'incidentes' : incidentes_registrables,
'tasa_incidentes' : tasa_incidentes,
'year' : year
})
Best Practices
Permit Duration : Set realistic time windows for permits to avoid expired permits during work
Critical Requirements : Mark life-critical requirements (e.g., gas testing for confined spaces) as mandatory
Near Miss Reporting : Encourage near miss reporting to prevent future incidents
Maintenance Link permits to work orders
Assets Equipment-specific hazards
User Guide Detailed permit workflows