feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,16 @@ from atocore.interactions.service import (
|
||||
list_interactions,
|
||||
record_interaction,
|
||||
)
|
||||
from atocore.engineering.service import (
|
||||
ENTITY_TYPES,
|
||||
RELATIONSHIP_TYPES,
|
||||
create_entity,
|
||||
create_relationship,
|
||||
get_entities,
|
||||
get_entity,
|
||||
get_entity_with_context,
|
||||
get_relationships,
|
||||
)
|
||||
from atocore.memory.extractor import (
|
||||
EXTRACTOR_VERSION,
|
||||
MemoryCandidate,
|
||||
@@ -926,6 +936,144 @@ def api_dashboard() -> dict:
|
||||
}
|
||||
|
||||
|
||||
# --- Engineering Knowledge Layer (Layer 2) ---
|
||||
|
||||
|
||||
class EntityCreateRequest(BaseModel):
|
||||
entity_type: str
|
||||
name: str
|
||||
project: str = ""
|
||||
description: str = ""
|
||||
properties: dict | None = None
|
||||
status: str = "active"
|
||||
confidence: float = 1.0
|
||||
source_refs: list[str] | None = None
|
||||
|
||||
|
||||
class RelationshipCreateRequest(BaseModel):
|
||||
source_entity_id: str
|
||||
target_entity_id: str
|
||||
relationship_type: str
|
||||
confidence: float = 1.0
|
||||
source_refs: list[str] | None = None
|
||||
|
||||
|
||||
@router.post("/entities")
|
||||
def api_create_entity(req: EntityCreateRequest) -> dict:
|
||||
"""Create a new engineering entity."""
|
||||
try:
|
||||
entity = create_entity(
|
||||
entity_type=req.entity_type,
|
||||
name=req.name,
|
||||
project=req.project,
|
||||
description=req.description,
|
||||
properties=req.properties,
|
||||
status=req.status,
|
||||
confidence=req.confidence,
|
||||
source_refs=req.source_refs,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
return {"status": "ok", "id": entity.id, "entity_type": entity.entity_type, "name": entity.name}
|
||||
|
||||
|
||||
@router.get("/entities")
|
||||
def api_list_entities(
|
||||
entity_type: str | None = None,
|
||||
project: str | None = None,
|
||||
status: str = "active",
|
||||
name_contains: str | None = None,
|
||||
limit: int = 100,
|
||||
) -> dict:
|
||||
"""List engineering entities with optional filters."""
|
||||
entities = get_entities(
|
||||
entity_type=entity_type,
|
||||
project=project,
|
||||
status=status,
|
||||
name_contains=name_contains,
|
||||
limit=limit,
|
||||
)
|
||||
return {
|
||||
"entities": [
|
||||
{
|
||||
"id": e.id,
|
||||
"entity_type": e.entity_type,
|
||||
"name": e.name,
|
||||
"project": e.project,
|
||||
"description": e.description,
|
||||
"properties": e.properties,
|
||||
"status": e.status,
|
||||
"confidence": e.confidence,
|
||||
}
|
||||
for e in entities
|
||||
],
|
||||
"count": len(entities),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/entities/{entity_id}")
|
||||
def api_get_entity(entity_id: str) -> dict:
|
||||
"""Get an entity with its relationships and related entities."""
|
||||
result = get_entity_with_context(entity_id)
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail=f"Entity not found: {entity_id}")
|
||||
entity = result["entity"]
|
||||
return {
|
||||
"entity": {
|
||||
"id": entity.id,
|
||||
"entity_type": entity.entity_type,
|
||||
"name": entity.name,
|
||||
"project": entity.project,
|
||||
"description": entity.description,
|
||||
"properties": entity.properties,
|
||||
"status": entity.status,
|
||||
"confidence": entity.confidence,
|
||||
"source_refs": entity.source_refs,
|
||||
"created_at": entity.created_at,
|
||||
"updated_at": entity.updated_at,
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": r.id,
|
||||
"source_entity_id": r.source_entity_id,
|
||||
"target_entity_id": r.target_entity_id,
|
||||
"relationship_type": r.relationship_type,
|
||||
"confidence": r.confidence,
|
||||
}
|
||||
for r in result["relationships"]
|
||||
],
|
||||
"related_entities": {
|
||||
eid: {
|
||||
"entity_type": e.entity_type,
|
||||
"name": e.name,
|
||||
"project": e.project,
|
||||
"description": e.description[:200],
|
||||
}
|
||||
for eid, e in result["related_entities"].items()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.post("/relationships")
|
||||
def api_create_relationship(req: RelationshipCreateRequest) -> dict:
|
||||
"""Create a relationship between two entities."""
|
||||
try:
|
||||
rel = create_relationship(
|
||||
source_entity_id=req.source_entity_id,
|
||||
target_entity_id=req.target_entity_id,
|
||||
relationship_type=req.relationship_type,
|
||||
confidence=req.confidence,
|
||||
source_refs=req.source_refs,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
return {
|
||||
"status": "ok",
|
||||
"id": rel.id,
|
||||
"relationship_type": rel.relationship_type,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/admin/backup/{stamp}/validate")
|
||||
def api_validate_backup(stamp: str) -> dict:
|
||||
"""Validate that a previously created backup is structurally usable."""
|
||||
|
||||
Reference in New Issue
Block a user