Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/app/routes/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ProjectionConceptResponse(BaseModel):
ontology: Optional[str] = None # Source ontology (for cross-ontology mode)
item_type: Optional[str] = None # Item type (for combined mode)
cluster_id: Optional[int] = None # DBSCAN cluster assignment (None = noise)
degree: Optional[int] = None # Total edge count (in + out)


class ProjectionParametersResponse(BaseModel):
Expand Down Expand Up @@ -142,6 +143,10 @@ class RegenerateRequest(BaseModel):
default=False,
description="Include diversity scores (slower)"
)
include_degree: bool = Field(
default=True,
description="Include concept degree (edge count)"
)
embedding_source: Literal["concepts", "sources", "vocabulary", "combined"] = Field(
default="concepts",
description="Which embeddings to project: concepts (default), sources, vocabulary, or combined"
Expand Down Expand Up @@ -287,6 +292,7 @@ async def regenerate_projection(
include_grounding=body.include_grounding,
refresh_grounding=body.refresh_grounding,
include_diversity=body.include_diversity,
include_degree=body.include_degree,
embedding_source=body.embedding_source
)

Expand Down Expand Up @@ -316,6 +322,7 @@ async def regenerate_projection(
"center": body.center,
"include_grounding": body.include_grounding,
"include_diversity": body.include_diversity,
"include_degree": body.include_degree,
"embedding_source": body.embedding_source
},
payload=dataset,
Expand Down Expand Up @@ -348,6 +355,7 @@ async def regenerate_projection(
"include_grounding": body.include_grounding,
"refresh_grounding": body.refresh_grounding,
"include_diversity": body.include_diversity,
"include_degree": body.include_degree,
"embedding_source": body.embedding_source,
"create_artifact": body.create_artifact,
"description": f"Regenerate {body.embedding_source} projection for '{ontology}'",
Expand Down
47 changes: 47 additions & 0 deletions api/app/services/embedding_projection_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,45 @@ def _add_diversity_scores(self, concepts: List[Dict]) -> List[Dict]:

return concepts

def _add_degree_counts(self, concepts: List[Dict], ontology: str) -> List[Dict]:
"""Add degree (total edge count) to concepts via bulk Cypher query."""
concept_ids = [c["concept_id"] for c in concepts if c.get("concept_id")]
if not concept_ids:
return concepts

try:
# Use _execute_cypher which handles AGE setup, query wrapping, and result parsing
results = self.client._execute_cypher(
"""
MATCH (c:Concept)
WHERE c.concept_id IN $concept_ids
OPTIONAL MATCH (c)-[r_out]->(:Concept)
OPTIONAL MATCH (:Concept)-[r_in]->(c)
RETURN c.concept_id as concept_id,
count(DISTINCT r_out) + count(DISTINCT r_in) as degree
""",
params={"concept_ids": concept_ids}
)

degree_map = {}
for row in results:
cid = row.get("concept_id")
deg = row.get("degree", 0)
if cid:
degree_map[str(cid)] = int(deg) if deg is not None else 0

for concept in concepts:
concept["degree"] = degree_map.get(concept["concept_id"], 0)

logger.info(f"Added degree counts for {len(degree_map)}/{len(concept_ids)} concepts")

except Exception as e:
logger.error(f"Error adding degree counts: {e}", exc_info=True)
for concept in concepts:
concept["degree"] = None

return concepts

def compute_projection(
self,
embeddings: np.ndarray,
Expand Down Expand Up @@ -867,6 +906,7 @@ def generate_projection_dataset(
include_grounding: bool = True,
refresh_grounding: bool = False,
include_diversity: bool = False, # Off by default for performance
include_degree: bool = True, # On by default (cheap bulk query)
embedding_source: Literal["concepts", "sources", "vocabulary", "combined"] = "concepts"
) -> Dict[str, Any]:
"""
Expand Down Expand Up @@ -1005,6 +1045,10 @@ def generate_projection_dataset(
if cid and cid in fresh_groundings:
item["grounding_strength"] = fresh_groundings[cid]

# Add degree counts if requested (bulk Cypher, cheap)
if include_degree and embedding_source in ("concepts", "combined"):
self._add_degree_counts(items, ontology)

# Build result dataset
result_items = []
grounding_values = []
Expand Down Expand Up @@ -1044,6 +1088,9 @@ def generate_projection_dataset(
if item.get("diversity_score") is not None:
diversity_values.append(item["diversity_score"])

if include_degree:
entry["degree"] = item.get("degree")

# Add extra metadata for sources
if "full_text" in item:
entry["full_text"] = item["full_text"]
Expand Down
2 changes: 2 additions & 0 deletions api/app/workers/projection_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def run_projection_worker(
include_grounding = job_data.get("include_grounding", True)
refresh_grounding = job_data.get("refresh_grounding", False)
include_diversity = job_data.get("include_diversity", False)
include_degree = job_data.get("include_degree", True)
embedding_source = job_data.get("embedding_source", "concepts")

logger.info(
Expand Down Expand Up @@ -128,6 +129,7 @@ def run_projection_worker(
include_grounding=include_grounding,
refresh_grounding=refresh_grounding,
include_diversity=include_diversity,
include_degree=include_degree,
embedding_source=embedding_source
)

Expand Down
1 change: 1 addition & 0 deletions web/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,7 @@ class APIClient {
include_grounding?: boolean;
refresh_grounding?: boolean; // Compute fresh grounding values
include_diversity?: boolean;
include_degree?: boolean; // Include degree (edge count) per concept
embedding_source?: 'concepts' | 'sources' | 'vocabulary' | 'combined';
}
): Promise<{
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/blocks/AndBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const AndBlock: React.FC<NodeProps<BlockData>> = ({ id }) => {
<div className="flex flex-col items-center justify-center gap-1">
<div className="flex items-center gap-2">
<span className="font-bold text-base text-amber-700 dark:text-amber-300">AND</span>
<span className="px-1.5 py-0.5 bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300 rounded text-[10px] font-medium">
<span className="px-1.5 py-0.5 bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300 rounded text-[0.625rem] font-medium">
LOGIC
</span>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/blocks/BlockBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1162,7 +1162,7 @@ export const BlockBuilder = forwardRef<BlockBuilderHandle, BlockBuilderProps>(({
{diagram.description}
</div>
)}
<div className="text-[10px] text-muted-foreground dark:text-gray-500 mt-1">
<div className="text-[0.625rem] text-muted-foreground dark:text-gray-500 mt-1">
{diagram.nodeCount} blocks, {diagram.edgeCount} connections
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/blocks/BlockHelpPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const BlockHelpPopup: React.FC<BlockHelpPopupProps> = ({ blockType, posit
<div className="px-4 py-3 border-b border-border dark:border-gray-700 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-semibold text-card-foreground dark:text-gray-100">{content.title}</span>
<span className={`px-1.5 py-0.5 rounded text-[10px] font-medium ${tagStyle.bg} ${tagStyle.text}`}>
<span className={`px-1.5 py-0.5 rounded text-[0.625rem] font-medium ${tagStyle.bg} ${tagStyle.text}`}>
{content.tag}
</span>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/blocks/BlockPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export const BlockPalette: React.FC<BlockPaletteProps> = ({ onAddBlock, classNam
{section.title}
</h4>
{section.description && (
<p className="text-[10px] text-muted-foreground dark:text-gray-500">{section.description}</p>
<p className="text-[0.625rem] text-muted-foreground dark:text-gray-500">{section.description}</p>
)}
</div>

Expand Down
4 changes: 2 additions & 2 deletions web/src/components/blocks/EdgeFilterBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const EdgeFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
}}
className="w-3 h-3 text-blue-500 dark:text-blue-400 rounded"
/>
<span className="text-[10px] text-muted-foreground dark:text-gray-500">Regex</span>
<span className="text-[0.625rem] text-muted-foreground dark:text-gray-500">Regex</span>
</label>
</div>

Expand Down Expand Up @@ -102,7 +102,7 @@ export const EdgeFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {

{/* Regex error message */}
{regexError && (
<div className="text-[10px] text-red-500 dark:text-red-400">
<div className="text-[0.625rem] text-red-500 dark:text-red-400">
{regexError}
</div>
)}
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/blocks/EndBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ export const EndBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
<div className="flex items-center justify-center gap-2 mb-2">
<Square className="w-4 h-4 text-red-600 dark:text-red-400 fill-current" />
<span className="font-medium text-sm text-card-foreground dark:text-gray-100">END</span>
<span className="px-1.5 py-0.5 bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300 rounded text-[10px] font-medium">
<span className="px-1.5 py-0.5 bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300 rounded text-[0.625rem] font-medium">
FLOW
</span>
</div>

{/* Output Format Control */}
<div className="flex items-center justify-center gap-1 text-[10px]">
<div className="flex items-center justify-center gap-1 text-[0.625rem]">
<button
className={`flex items-center gap-1 px-2 py-1 rounded ${
outputFormat === 'visualization'
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/blocks/EnrichBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const EnrichBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
/>
<div className="flex-1">
<span className="text-sm text-card-foreground dark:text-gray-100">Ontology</span>
<p className="text-[10px] text-muted-foreground dark:text-gray-500">Color-code by source document</p>
<p className="text-[0.625rem] text-muted-foreground dark:text-gray-500">Color-code by source document</p>
</div>
</label>

Expand All @@ -72,7 +72,7 @@ export const EnrichBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
/>
<div className="flex-1">
<span className="text-sm text-card-foreground dark:text-gray-100">Grounding</span>
<p className="text-[10px] text-muted-foreground dark:text-gray-500">Confidence/reliability score</p>
<p className="text-[0.625rem] text-muted-foreground dark:text-gray-500">Confidence/reliability score</p>
</div>
</label>

Expand All @@ -85,7 +85,7 @@ export const EnrichBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
/>
<div className="flex-1">
<span className="text-sm text-card-foreground dark:text-gray-100">Search Terms</span>
<p className="text-[10px] text-muted-foreground dark:text-gray-500">Alternative labels/aliases</p>
<p className="text-[0.625rem] text-muted-foreground dark:text-gray-500">Alternative labels/aliases</p>
</div>
</label>
</div>
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/blocks/EpistemicFilterBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const EpistemicFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) =
<div className="flex items-center gap-2 mb-3">
<Snowflake className="w-4 h-4 text-indigo-600 dark:text-indigo-400" />
<span className="font-medium text-sm text-card-foreground dark:text-gray-100">Epistemic Filter</span>
<span className="ml-auto px-1.5 py-0.5 bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 rounded text-[10px] font-medium">
<span className="ml-auto px-1.5 py-0.5 bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 rounded text-[0.625rem] font-medium">
SMART
</span>
</div>
Expand All @@ -76,7 +76,7 @@ export const EpistemicFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) =
>
Include Only
{includeStatuses.length > 0 && (
<span className="ml-1 px-1 bg-white/20 rounded text-[10px]">{includeStatuses.length}</span>
<span className="ml-1 px-1 bg-white/20 rounded text-[0.625rem]">{includeStatuses.length}</span>
)}
</button>
<button
Expand All @@ -89,7 +89,7 @@ export const EpistemicFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) =
>
Exclude
{excludeStatuses.length > 0 && (
<span className="ml-1 px-1 bg-white/20 rounded text-[10px]">{excludeStatuses.length}</span>
<span className="ml-1 px-1 bg-white/20 rounded text-[0.625rem]">{excludeStatuses.length}</span>
)}
</button>
</div>
Expand Down Expand Up @@ -121,7 +121,7 @@ export const EpistemicFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) =
<span className="text-xs font-medium text-card-foreground dark:text-gray-200 block">
{status.label}
</span>
<span className="text-[10px] text-muted-foreground dark:text-gray-500 block truncate">
<span className="text-[0.625rem] text-muted-foreground dark:text-gray-500 block truncate">
{status.description}
</span>
</div>
Expand All @@ -132,7 +132,7 @@ export const EpistemicFilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) =

{/* Active filters summary */}
{activeCount > 0 && (
<div className="mt-2 pt-2 border-t border-border dark:border-gray-700 text-[10px] text-muted-foreground dark:text-gray-500">
<div className="mt-2 pt-2 border-t border-border dark:border-gray-700 text-[0.625rem] text-muted-foreground dark:text-gray-500">
{includeStatuses.length > 0 && (
<div>Include: {includeStatuses.join(', ')}</div>
)}
Expand Down
8 changes: 4 additions & 4 deletions web/src/components/blocks/FilterBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const FilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
<div className="flex items-center gap-2 mb-3">
<Filter className="w-4 h-4 text-orange-600 dark:text-orange-400" />
<span className="font-medium text-sm text-card-foreground dark:text-gray-100">Filter Results</span>
<span className="ml-auto px-1.5 py-0.5 bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 rounded text-[10px] font-medium">
<span className="ml-auto px-1.5 py-0.5 bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 rounded text-[0.625rem] font-medium">
CYPHER
</span>
</div>
Expand All @@ -86,7 +86,7 @@ export const FilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
}}
className="w-3 h-3 text-orange-500 dark:text-orange-400 rounded"
/>
<span className="text-[10px] text-muted-foreground dark:text-gray-500">Regex</span>
<span className="text-[0.625rem] text-muted-foreground dark:text-gray-500">Regex</span>
</label>
{useRegex && (
<div className="relative">
Expand All @@ -99,7 +99,7 @@ export const FilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
<HelpCircle className="w-3 h-3 text-muted-foreground dark:text-gray-500" />
</button>
{showRegexHelp && (
<div className="absolute right-0 top-5 z-50 w-48 p-2 bg-card dark:bg-gray-800 border border-border dark:border-gray-600 rounded shadow-lg text-[10px]">
<div className="absolute right-0 top-5 z-50 w-48 p-2 bg-card dark:bg-gray-800 border border-border dark:border-gray-600 rounded shadow-lg text-[0.625rem]">
<div className="font-medium text-card-foreground dark:text-gray-100 mb-1">Regex Examples</div>
<div className="space-y-0.5 text-muted-foreground dark:text-gray-400">
<div><code className="text-orange-600 dark:text-orange-400">.*</code> any characters</div>
Expand Down Expand Up @@ -139,7 +139,7 @@ export const FilterBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {

{/* Regex error message */}
{regexError && (
<div className="text-[10px] text-red-500 dark:text-red-400">
<div className="text-[0.625rem] text-red-500 dark:text-red-400">
{regexError}
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/blocks/LimitBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const LimitBlock: React.FC<NodeProps<BlockData>> = ({ data }) => {
<div className="flex items-center gap-2 mb-3">
<Hash className="w-4 h-4 text-gray-600 dark:text-gray-400" />
<span className="font-medium text-sm text-card-foreground dark:text-gray-100">Limit Results</span>
<span className="ml-auto px-1.5 py-0.5 bg-gray-100 dark:bg-gray-900/50 text-gray-700 dark:text-gray-300 rounded text-[10px] font-medium">
<span className="ml-auto px-1.5 py-0.5 bg-gray-100 dark:bg-gray-900/50 text-gray-700 dark:text-gray-300 rounded text-[0.625rem] font-medium">
CYPHER
</span>
</div>
Expand Down
Loading