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):
|
||||
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):
|
||||
ENTITY_TYPES = (
|
||||
('PERSON', 'Person'),
|
||||
@ -18,6 +31,7 @@ class Entity(models.Model):
|
||||
entity_type = models.CharField(max_length=20, choices=ENTITY_TYPES)
|
||||
value = models.CharField(max_length=255, db_index=True)
|
||||
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)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
@ -45,4 +59,4 @@ class Relationship(models.Model):
|
||||
unique_together = ('source_entity', 'target_entity', 'relationship_type')
|
||||
|
||||
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 core.models import Source, Entity
|
||||
from core.models import Source, Entity, IdentityProfile
|
||||
|
||||
class IngestionService:
|
||||
@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():
|
||||
source, _ = Source.objects.get_or_create(name=source_name)
|
||||
|
||||
# Simple normalization logic: assume raw_data is a dict
|
||||
# In a real scenario, this would be more complex depending on source schema
|
||||
identifier = raw_data.get('identifier')
|
||||
if not identifier:
|
||||
raise ValueError("Missing identifier in raw_data")
|
||||
profile = None
|
||||
if profile_data:
|
||||
profile, _ = IdentityProfile.objects.get_or_create(
|
||||
full_name=profile_data.get('full_name', 'Unknown'),
|
||||
defaults={'description': profile_data.get('description', '')}
|
||||
)
|
||||
|
||||
entity, created = Entity.objects.get_or_create(
|
||||
identifier=identifier,
|
||||
entity_type=entity_type,
|
||||
defaults={'source': source, 'metadata': raw_data.get('metadata', {})}
|
||||
value=value,
|
||||
source=source,
|
||||
defaults={'profile': profile}
|
||||
)
|
||||
|
||||
if not created:
|
||||
# Update existing entity metadata
|
||||
entity.metadata.update(raw_data.get('metadata', {}))
|
||||
if not created and profile:
|
||||
entity.profile = profile
|
||||
entity.save()
|
||||
|
||||
return entity
|
||||
return entity
|
||||
@ -2,17 +2,63 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-5">
|
||||
<h1 class="mb-4">System Dashboard</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="mb-4">System Dashboard</h1>
|
||||
<div class="card">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Welcome to the Management System</h5>
|
||||
<p class="card-text">You are logged in as <strong>{{ user.username }}</strong>.</p>
|
||||
<a href="{% url 'core:logout' %}" class="btn btn-danger">Logout</a>
|
||||
<h5 class="card-title">Quick Search</h5>
|
||||
<form id="searchForm" class="input-group">
|
||||
<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 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>
|
||||
{% 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