RIPLEY
This commit is contained in:
parent
ebebe4715f
commit
b19870501a
Binary file not shown.
30
core/migrations/0003_identityprofile_entity_profile.py
Normal file
30
core/migrations/0003_identityprofile_entity_profile.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-03-22 22:49
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_relationship'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IdentityProfile',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('full_name', models.CharField(max_length=255)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('profile_image_url', models.URLField(blank=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='entity',
|
||||||
|
name='profile',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='entities', to='core.identityprofile'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -8,6 +8,19 @@ class Source(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class IdentityProfile(models.Model):
|
||||||
|
"""
|
||||||
|
Groups various entities (Person, Email, Username, etc.) into a single identity profile.
|
||||||
|
"""
|
||||||
|
full_name = models.CharField(max_length=255)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
profile_image_url = models.URLField(blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.full_name
|
||||||
|
|
||||||
class Entity(models.Model):
|
class Entity(models.Model):
|
||||||
ENTITY_TYPES = (
|
ENTITY_TYPES = (
|
||||||
('PERSON', 'Person'),
|
('PERSON', 'Person'),
|
||||||
@ -18,6 +31,7 @@ class Entity(models.Model):
|
|||||||
entity_type = models.CharField(max_length=20, choices=ENTITY_TYPES)
|
entity_type = models.CharField(max_length=20, choices=ENTITY_TYPES)
|
||||||
value = models.CharField(max_length=255, db_index=True)
|
value = models.CharField(max_length=255, db_index=True)
|
||||||
source = models.ForeignKey(Source, on_delete=models.CASCADE, related_name='entities')
|
source = models.ForeignKey(Source, on_delete=models.CASCADE, related_name='entities')
|
||||||
|
profile = models.ForeignKey(IdentityProfile, on_delete=models.SET_NULL, null=True, blank=True, related_name='entities')
|
||||||
confidence_score = models.FloatField(default=1.0)
|
confidence_score = models.FloatField(default=1.0)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
@ -45,4 +59,4 @@ class Relationship(models.Model):
|
|||||||
unique_together = ('source_entity', 'target_entity', 'relationship_type')
|
unique_together = ('source_entity', 'target_entity', 'relationship_type')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.source_entity} -[{self.relationship_type}]-> {self.target_entity}"
|
return f"{self.source_entity} -[{self.relationship_type}]-> {self.target_entity}"
|
||||||
Binary file not shown.
@ -1,30 +1,31 @@
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from core.models import Source, Entity
|
from core.models import Source, Entity, IdentityProfile
|
||||||
|
|
||||||
class IngestionService:
|
class IngestionService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ingest_data(source_name, entity_type, raw_data):
|
def ingest_data(source_name, entity_type, value, profile_data=None):
|
||||||
"""
|
"""
|
||||||
Aggregates and normalizes raw data into the system.
|
Aggregates and normalizes data into the system, optionally linking it to an IdentityProfile.
|
||||||
"""
|
"""
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
source, _ = Source.objects.get_or_create(name=source_name)
|
source, _ = Source.objects.get_or_create(name=source_name)
|
||||||
|
|
||||||
# Simple normalization logic: assume raw_data is a dict
|
profile = None
|
||||||
# In a real scenario, this would be more complex depending on source schema
|
if profile_data:
|
||||||
identifier = raw_data.get('identifier')
|
profile, _ = IdentityProfile.objects.get_or_create(
|
||||||
if not identifier:
|
full_name=profile_data.get('full_name', 'Unknown'),
|
||||||
raise ValueError("Missing identifier in raw_data")
|
defaults={'description': profile_data.get('description', '')}
|
||||||
|
)
|
||||||
|
|
||||||
entity, created = Entity.objects.get_or_create(
|
entity, created = Entity.objects.get_or_create(
|
||||||
identifier=identifier,
|
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
defaults={'source': source, 'metadata': raw_data.get('metadata', {})}
|
value=value,
|
||||||
|
source=source,
|
||||||
|
defaults={'profile': profile}
|
||||||
)
|
)
|
||||||
|
|
||||||
if not created:
|
if not created and profile:
|
||||||
# Update existing entity metadata
|
entity.profile = profile
|
||||||
entity.metadata.update(raw_data.get('metadata', {}))
|
|
||||||
entity.save()
|
entity.save()
|
||||||
|
|
||||||
return entity
|
return entity
|
||||||
@ -2,17 +2,63 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
|
<h1 class="mb-4">System Dashboard</h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12 mb-4">
|
||||||
<h1 class="mb-4">System Dashboard</h1>
|
<div class="card shadow-sm">
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Welcome to the Management System</h5>
|
<h5 class="card-title">Quick Search</h5>
|
||||||
<p class="card-text">You are logged in as <strong>{{ user.username }}</strong>.</p>
|
<form id="searchForm" class="input-group">
|
||||||
<a href="{% url 'core:logout' %}" class="btn btn-danger">Logout</a>
|
<input type="text" id="searchInput" class="form-control" placeholder="Search for a person, email, or username...">
|
||||||
|
<button class="btn btn-primary" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">People & Identities</h5>
|
||||||
|
<div id="resultsArea">
|
||||||
|
<p class="text-muted">Enter a search term to find entities.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<a href="{% url 'core:logout' %}" class="btn btn-outline-danger">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('searchForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const query = document.getElementById('searchInput').value;
|
||||||
|
const resultsArea = document.getElementById('resultsArea');
|
||||||
|
resultsArea.innerHTML = '<p>Searching...</p>';
|
||||||
|
|
||||||
|
// Placeholder: In a real scenario, this would call your Django backend
|
||||||
|
fetch(`/api/search/?q=${encodeURIComponent(query)}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
resultsArea.innerHTML = data.map(item => `
|
||||||
|
<div class="d-flex align-items-center mb-3 p-3 border rounded">
|
||||||
|
<div class="me-3" style="width: 50px; height: 50px; background: #ddd; border-radius: 50%;"></div>
|
||||||
|
<div>
|
||||||
|
<strong>${item.name}</strong><br>
|
||||||
|
<small class="text-muted">${item.type}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user