ZahirJS commited on
Commit
505ff60
·
verified ·
1 Parent(s): 59cb64e

Update entity_relationship_generator.py

Browse files
Files changed (1) hide show
  1. entity_relationship_generator.py +199 -141
entity_relationship_generator.py CHANGED
@@ -319,107 +319,70 @@ def generate_entity_relationship_diagram(json_input: str, output_format: str) ->
319
  "name": "EMPLEADO",
320
  "type": "strong",
321
  "attributes": [
322
- {"name": "RUT", "type": "primary_key"},
323
- {"name": "Nombre", "type": "regular"},
324
- {"name": "Apellido1", "type": "regular"},
325
- {"name": "Apellido2", "type": "regular"},
326
- {"name": "Dirección", "type": "regular"},
327
- {"name": "Sexo", "type": "regular"},
328
- {"name": "Salario", "type": "regular"},
329
- {"name": "FechaN", "type": "regular"}
330
  ]
331
  },
332
  {
333
- "name": "DEPARTAMENTO",
334
  "type": "strong",
335
  "attributes": [
336
- {"name": "Número", "type": "primary_key"},
337
- {"name": "Nombre", "type": "regular"},
338
- {"name": "Localizaciones", "type": "multivalued"}
339
  ]
340
  },
341
  {
342
- "name": "PROYECTO",
343
  "type": "strong",
344
  "attributes": [
345
- {"name": "Número", "type": "primary_key"},
346
- {"name": "Nombre", "type": "regular"},
347
- {"name": "Localización", "type": "regular"}
348
  ]
349
  },
350
  {
351
- "name": "DEPENDIENTE",
352
- "type": "weak",
 
 
 
 
 
353
  "attributes": [
354
- {"name": "Nombre", "type": "partial_key"},
355
- {"name": "Sexo", "type": "regular"},
356
- {"name": "FechaN", "type": "regular"},
357
- {"name": "Relación", "type": "regular"}
 
358
  ]
359
  }
360
  ],
361
  "relationships": [
362
  {
363
- "name": "TRABAJA_PARA",
364
  "type": "regular",
365
  "entities": ["EMPLEADO", "DEPARTAMENTO"],
366
  "cardinalities": {
367
  "EMPLEADO": "N",
368
  "DEPARTAMENTO": "1"
369
- }
370
- },
371
- {
372
- "name": "ADMINISTRA",
373
- "type": "regular",
374
- "entities": ["EMPLEADO", "DEPARTAMENTO"],
375
- "cardinalities": {
376
- "EMPLEADO": "1",
377
- "DEPARTAMENTO": "1"
378
  },
379
  "attributes": [
380
- {"name": "FechaInicio", "type": "regular"}
 
381
  ]
382
  },
383
  {
384
- "name": "CONTROLA",
385
- "type": "regular",
386
- "entities": ["DEPARTAMENTO", "PROYECTO"],
387
- "cardinalities": {
388
- "DEPARTAMENTO": "1",
389
- "PROYECTO": "N"
390
- }
391
  },
392
  {
393
- "name": "SUPERVISIÓN",
394
  "type": "regular",
395
- "entities": ["EMPLEADO", "EMPLEADO"],
396
  "cardinalities": {
397
- "EMPLEADO": "1",
398
- "EMPLEADO": "N"
399
- },
400
- "roles": {
401
- "EMPLEADO": ["supervisor", "supervisado"]
402
- }
403
- },
404
- {
405
- "name": "TRABAJA_PARA_PROYECTO",
406
- "type": "ternary",
407
- "entities": ["EMPLEADO", "PROYECTO"],
408
- "cardinalities": {
409
- "EMPLEADO": "M",
410
  "PROYECTO": "N"
411
- },
412
- "attributes": [
413
- {"name": "Horas", "type": "regular"}
414
- ]
415
- },
416
- {
417
- "name": "DEPENDIENTES",
418
- "type": "identifying",
419
- "entities": ["EMPLEADO", "DEPENDIENTE"],
420
- "cardinalities": {
421
- "EMPLEADO": "1",
422
- "DEPENDIENTE": "N"
423
  }
424
  }
425
  ]
@@ -437,15 +400,25 @@ def generate_entity_relationship_diagram(json_input: str, output_format: str) ->
437
  if 'entities' not in data:
438
  raise ValueError("Missing required field: entities")
439
 
 
440
  dot = graphviz.Graph(comment='ER Diagram', engine='dot')
441
- dot.attr(rankdir='TB', bgcolor='white', pad='0.5', nodesep='1.0', ranksep='1.5')
442
- dot.attr('node', fontname='Arial', fontsize='10', color='black')
443
- dot.attr('edge', fontname='Arial', fontsize='9', color='black')
 
 
 
 
 
 
 
 
 
444
 
445
  entities = data.get('entities', [])
446
  relationships = data.get('relationships', [])
447
 
448
- # Crear entidades
449
  for entity in entities:
450
  entity_name = entity.get('name')
451
  entity_type = entity.get('type', 'strong')
@@ -453,110 +426,195 @@ def generate_entity_relationship_diagram(json_input: str, output_format: str) ->
453
  if not entity_name:
454
  continue
455
 
456
- # Entidades fuertes: rectángulo simple
457
- # Entidades débiles: rectángulo doble (simulado con penwidth)
458
  if entity_type == 'weak':
459
- dot.node(entity_name, entity_name, shape='box', style='filled',
460
- fillcolor='white', color='black', penwidth='3')
 
461
  else:
462
- dot.node(entity_name, entity_name, shape='box', style='filled',
463
- fillcolor='white', color='black', penwidth='1')
 
 
 
 
 
 
 
 
 
 
464
 
465
  # Crear atributos para cada entidad
466
  for entity in entities:
467
  entity_name = entity.get('name')
468
  attributes = entity.get('attributes', [])
469
 
470
- for attr in attributes:
471
  attr_name = attr.get('name', '')
472
  attr_type = attr.get('type', 'regular')
473
 
474
- attr_id = f"{entity_name}_{attr_name}"
475
 
476
  if attr_type == 'primary_key':
477
- # Atributo clave: elipse con texto subrayado
478
- dot.node(attr_id, f"<U>{attr_name}</U>", shape='ellipse',
479
- style='filled', fillcolor='white', color='black')
 
 
 
 
 
 
 
480
  elif attr_type == 'partial_key':
481
  # Clave parcial: elipse con línea punteada
482
- dot.node(attr_id, f"<U>{attr_name}</U>", shape='ellipse',
483
- style='filled,dashed', fillcolor='white', color='black')
 
 
 
 
 
 
 
484
  elif attr_type == 'multivalued':
485
- # Atributo multivaluado: doble elipse (simulado con penwidth)
486
- dot.node(attr_id, attr_name, shape='ellipse',
487
- style='filled', fillcolor='white', color='black', penwidth='3')
488
  elif attr_type == 'derived':
489
  # Atributo derivado: elipse con línea punteada
490
- dot.node(attr_id, attr_name, shape='ellipse',
491
- style='filled,dashed', fillcolor='white', color='black')
 
 
 
 
 
 
492
  elif attr_type == 'composite':
493
- # Atributo compuesto: elipse normal (los componentes se conectarían por separado)
494
- dot.node(attr_id, attr_name, shape='ellipse',
495
- style='filled', fillcolor='white', color='black')
 
 
 
 
 
 
496
  else:
497
- # Atributo regular: elipse simple
498
- dot.node(attr_id, attr_name, shape='ellipse',
499
- style='filled', fillcolor='white', color='black')
 
 
 
 
 
 
500
 
501
  # Conectar atributo con entidad
502
- dot.edge(entity_name, attr_id, color='black')
503
 
504
  # Crear relaciones
505
  for relationship in relationships:
506
  rel_name = relationship.get('name')
507
  rel_type = relationship.get('type', 'regular')
508
- entities_involved = relationship.get('entities', [])
509
- cardinalities = relationship.get('cardinalities', {})
510
- rel_attributes = relationship.get('attributes', [])
511
- roles = relationship.get('roles', {})
512
 
513
- if not rel_name or len(entities_involved) < 2:
514
- continue
515
-
516
- # Relaciones identificadoras: rombo doble
517
- # Relaciones regulares: rombo simple
518
- if rel_type == 'identifying':
519
- dot.node(rel_name, rel_name, shape='diamond', style='filled',
520
- fillcolor='white', color='black', penwidth='3')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  else:
522
- dot.node(rel_name, rel_name, shape='diamond', style='filled',
523
- fillcolor='white', color='black', penwidth='1')
524
-
525
- # Crear atributos de la relación
526
- for attr in rel_attributes:
527
- attr_name = attr.get('name', '')
528
- attr_type = attr.get('type', 'regular')
529
- attr_id = f"{rel_name}_{attr_name}"
530
 
531
- dot.node(attr_id, attr_name, shape='ellipse',
532
- style='filled', fillcolor='white', color='black')
533
- dot.edge(rel_name, attr_id, color='black')
534
-
535
- # Conectar entidades con la relación
536
- if len(entities_involved) == 2 and entities_involved[0] == entities_involved[1]:
537
- # Relación reflexiva (como supervisión)
538
- entity = entities_involved[0]
539
- cardinality1 = cardinalities.get(entity, '1')
540
 
541
- # Para relaciones reflexivas, necesitamos crear dos conexiones
542
- dot.edge(entity, rel_name, label=cardinality1, color='black')
543
- dot.edge(entity, rel_name, label='N', color='black')
544
 
545
- # Agregar roles si están definidos
546
- if roles and entity in roles:
547
- role_list = roles[entity]
548
- if len(role_list) >= 2:
549
- # Crear nodos de texto para los roles
550
- role1_id = f"{rel_name}_role1"
551
- role2_id = f"{rel_name}_role2"
552
- dot.node(role1_id, role_list[0], shape='plaintext', fontsize='8')
553
- dot.node(role2_id, role_list[1], shape='plaintext', fontsize='8')
554
- else:
555
- # Relación normal entre diferentes entidades
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  for entity in entities_involved:
557
  cardinality = cardinalities.get(entity, '1')
558
- dot.edge(entity, rel_name, label=cardinality, color='black')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
 
 
560
  with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
561
  dot.render(tmp.name, format=output_format, cleanup=True)
562
  return f"{tmp.name}.{output_format}"
@@ -564,4 +622,4 @@ def generate_entity_relationship_diagram(json_input: str, output_format: str) ->
564
  except json.JSONDecodeError:
565
  return "Error: Invalid JSON format"
566
  except Exception as e:
567
- return f"Error: {str(e)}"
 
319
  "name": "EMPLEADO",
320
  "type": "strong",
321
  "attributes": [
322
+ {"name": "num_matricula", "type": "primary_key"},
323
+ {"name": "nombre", "type": "regular"},
324
+ {"name": "direccion", "type": "regular"}
 
 
 
 
 
325
  ]
326
  },
327
  {
328
+ "name": "DEPARTAMENTO",
329
  "type": "strong",
330
  "attributes": [
331
+ {"name": "cod_depto", "type": "primary_key"},
332
+ {"name": "nombre_depto", "type": "regular"}
 
333
  ]
334
  },
335
  {
336
+ "name": "TECNICO",
337
  "type": "strong",
338
  "attributes": [
339
+ {"name": "nivel", "type": "regular"}
 
 
340
  ]
341
  },
342
  {
343
+ "name": "ADMINISTRATIVO",
344
+ "type": "strong",
345
+ "attributes": []
346
+ },
347
+ {
348
+ "name": "PROYECTO",
349
+ "type": "strong",
350
  "attributes": [
351
+ {"name": "cod_proyecto", "type": "primary_key"},
352
+ {"name": "nombre_proyecto", "type": "regular"},
353
+ {"name": "cliente", "type": "regular"},
354
+ {"name": "fecha_inicio", "type": "regular"},
355
+ {"name": "fecha_fin", "type": "regular"}
356
  ]
357
  }
358
  ],
359
  "relationships": [
360
  {
361
+ "name": "Pertenece",
362
  "type": "regular",
363
  "entities": ["EMPLEADO", "DEPARTAMENTO"],
364
  "cardinalities": {
365
  "EMPLEADO": "N",
366
  "DEPARTAMENTO": "1"
 
 
 
 
 
 
 
 
 
367
  },
368
  "attributes": [
369
+ {"name": "fecha_asignacion", "type": "regular"},
370
+ {"name": "fecha_cese", "type": "regular"}
371
  ]
372
  },
373
  {
374
+ "name": "Es_un",
375
+ "type": "isa",
376
+ "parent": "EMPLEADO",
377
+ "children": ["TECNICO", "ADMINISTRATIVO"]
 
 
 
378
  },
379
  {
380
+ "name": "Trabaja_en",
381
  "type": "regular",
382
+ "entities": ["TECNICO", "PROYECTO"],
383
  "cardinalities": {
384
+ "TECNICO": "M",
 
 
 
 
 
 
 
 
 
 
 
 
385
  "PROYECTO": "N"
 
 
 
 
 
 
 
 
 
 
 
 
386
  }
387
  }
388
  ]
 
400
  if 'entities' not in data:
401
  raise ValueError("Missing required field: entities")
402
 
403
+ # Crear grafo con estilo mejorado
404
  dot = graphviz.Graph(comment='ER Diagram', engine='dot')
405
+ dot.attr(
406
+ rankdir='TB',
407
+ bgcolor='white',
408
+ pad='0.8',
409
+ nodesep='1.2',
410
+ ranksep='1.8',
411
+ fontname='Arial'
412
+ )
413
+
414
+ # Configuración base de nodos y aristas
415
+ dot.attr('node', fontname='Arial', fontsize='11', color='#404040')
416
+ dot.attr('edge', fontname='Arial', fontsize='10', color='#404040')
417
 
418
  entities = data.get('entities', [])
419
  relationships = data.get('relationships', [])
420
 
421
+ # Crear entidades con estilo profesional
422
  for entity in entities:
423
  entity_name = entity.get('name')
424
  entity_type = entity.get('type', 'strong')
 
426
  if not entity_name:
427
  continue
428
 
 
 
429
  if entity_type == 'weak':
430
+ # Entidad débil: doble rectángulo (simulado con tabla HTML)
431
+ label = f'<<TABLE BORDER="3" CELLBORDER="1" CELLSPACING="0" CELLPADDING="8" BGCOLOR="#f0f0f0"><TR><TD><B>{entity_name}</B></TD></TR></TABLE>>'
432
+ dot.node(entity_name, label, shape='plaintext')
433
  else:
434
+ # Entidad fuerte: rectángulo simple con estilo
435
+ dot.node(
436
+ entity_name,
437
+ entity_name,
438
+ shape='box',
439
+ style='filled,rounded',
440
+ fillcolor='#e8e8e8',
441
+ color='#404040',
442
+ penwidth='2',
443
+ fontsize='12',
444
+ fontcolor='#000000'
445
+ )
446
 
447
  # Crear atributos para cada entidad
448
  for entity in entities:
449
  entity_name = entity.get('name')
450
  attributes = entity.get('attributes', [])
451
 
452
+ for i, attr in enumerate(attributes):
453
  attr_name = attr.get('name', '')
454
  attr_type = attr.get('type', 'regular')
455
 
456
+ attr_id = f"{entity_name}_attr_{i}"
457
 
458
  if attr_type == 'primary_key':
459
+ # Clave primaria: elipse con texto subrayado
460
+ dot.node(
461
+ attr_id,
462
+ f'<U><B>{attr_name}</B></U>',
463
+ shape='ellipse',
464
+ style='filled',
465
+ fillcolor='#d0d0d0',
466
+ color='#404040',
467
+ penwidth='2'
468
+ )
469
  elif attr_type == 'partial_key':
470
  # Clave parcial: elipse con línea punteada
471
+ dot.node(
472
+ attr_id,
473
+ f'<U>{attr_name}</U>',
474
+ shape='ellipse',
475
+ style='filled,dashed',
476
+ fillcolor='#d0d0d0',
477
+ color='#404040',
478
+ penwidth='2'
479
+ )
480
  elif attr_type == 'multivalued':
481
+ # Atributo multivaluado: doble elipse (tabla HTML)
482
+ label = f'<<TABLE BORDER="2" CELLBORDER="0" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#d8d8d8"><TR><TD>{attr_name}</TD></TR></TABLE>>'
483
+ dot.node(attr_id, label, shape='plaintext')
484
  elif attr_type == 'derived':
485
  # Atributo derivado: elipse con línea punteada
486
+ dot.node(
487
+ attr_id,
488
+ f'/{attr_name}/',
489
+ shape='ellipse',
490
+ style='filled,dashed',
491
+ fillcolor='#d8d8d8',
492
+ color='#404040'
493
+ )
494
  elif attr_type == 'composite':
495
+ # Atributo compuesto
496
+ dot.node(
497
+ attr_id,
498
+ attr_name,
499
+ shape='ellipse',
500
+ style='filled',
501
+ fillcolor='#d8d8d8',
502
+ color='#404040'
503
+ )
504
  else:
505
+ # Atributo regular
506
+ dot.node(
507
+ attr_id,
508
+ attr_name,
509
+ shape='ellipse',
510
+ style='filled',
511
+ fillcolor='#d8d8d8',
512
+ color='#404040'
513
+ )
514
 
515
  # Conectar atributo con entidad
516
+ dot.edge(entity_name, attr_id, color='#404040', penwidth='1')
517
 
518
  # Crear relaciones
519
  for relationship in relationships:
520
  rel_name = relationship.get('name')
521
  rel_type = relationship.get('type', 'regular')
 
 
 
 
522
 
523
+ if rel_type == 'isa':
524
+ # Relación ISA (jerarquía)
525
+ parent = relationship.get('parent')
526
+ children = relationship.get('children', [])
527
+
528
+ if parent and children:
529
+ # Crear nodo ISA como triángulo
530
+ isa_id = f"isa_{rel_name}"
531
+ dot.node(
532
+ isa_id,
533
+ 'ISA',
534
+ shape='triangle',
535
+ style='filled',
536
+ fillcolor='#c0c0c0',
537
+ color='#404040',
538
+ penwidth='2'
539
+ )
540
+
541
+ # Conectar padre con ISA
542
+ dot.edge(parent, isa_id, color='#404040', penwidth='2')
543
+
544
+ # Conectar ISA con hijos
545
+ for child in children:
546
+ dot.edge(isa_id, child, color='#404040', penwidth='2')
547
+
548
  else:
549
+ # Relación regular
550
+ entities_involved = relationship.get('entities', [])
551
+ cardinalities = relationship.get('cardinalities', {})
552
+ rel_attributes = relationship.get('attributes', [])
 
 
 
 
553
 
554
+ if not rel_name or len(entities_involved) < 2:
555
+ continue
 
 
 
 
 
 
 
556
 
557
+ rel_type_style = relationship.get('type', 'regular')
 
 
558
 
559
+ if rel_type_style == 'identifying':
560
+ # Relación identificadora: doble rombo
561
+ label = f'<<TABLE BORDER="3" CELLBORDER="0" CELLSPACING="0" CELLPADDING="6" BGCOLOR="#c8c8c8"><TR><TD><B>{rel_name}</B></TD></TR></TABLE>>'
562
+ dot.node(rel_name, label, shape='plaintext')
563
+ else:
564
+ # Relación regular: rombo simple
565
+ dot.node(
566
+ rel_name,
567
+ rel_name,
568
+ shape='diamond',
569
+ style='filled',
570
+ fillcolor='#c8c8c8',
571
+ color='#404040',
572
+ penwidth='2'
573
+ )
574
+
575
+ # Crear atributos de la relación
576
+ for i, attr in enumerate(rel_attributes):
577
+ attr_name = attr.get('name', '')
578
+ attr_id = f"{rel_name}_attr_{i}"
579
+
580
+ dot.node(
581
+ attr_id,
582
+ attr_name,
583
+ shape='ellipse',
584
+ style='filled',
585
+ fillcolor='#d8d8d8',
586
+ color='#404040'
587
+ )
588
+ dot.edge(rel_name, attr_id, color='#404040')
589
+
590
+ # Conectar entidades con la relación
591
  for entity in entities_involved:
592
  cardinality = cardinalities.get(entity, '1')
593
+
594
+ if len(entities_involved) == 2 and entities_involved[0] == entities_involved[1]:
595
+ # Relación reflexiva
596
+ dot.edge(
597
+ entity,
598
+ rel_name,
599
+ label=cardinality,
600
+ color='#404040',
601
+ penwidth='2',
602
+ fontsize='10',
603
+ fontcolor='#000000'
604
+ )
605
+ else:
606
+ # Relación normal
607
+ dot.edge(
608
+ entity,
609
+ rel_name,
610
+ label=cardinality,
611
+ color='#404040',
612
+ penwidth='2',
613
+ fontsize='10',
614
+ fontcolor='#000000'
615
+ )
616
 
617
+ # Renderizar el diagrama
618
  with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
619
  dot.render(tmp.name, format=output_format, cleanup=True)
620
  return f"{tmp.name}.{output_format}"
 
622
  except json.JSONDecodeError:
623
  return "Error: Invalid JSON format"
624
  except Exception as e:
625
+ return f"Error: {str(e)}"