Add rubric instructions and attributes to frameworks
Browse files- README.md +1 -1
- backend/api/analysis_routes.py +2 -0
- backend/api/analysis_template_routes.py +19 -7
- backend/api/conversation_service.py +21 -5
- backend/core/analysis_knobs.py +4 -0
- backend/core/default_analysis_templates.py +2 -0
- backend/core/persona_seed.py +4 -0
- backend/storage/models.py +1 -0
- backend/storage/sqlite_persona_store.py +11 -0
- docs/capabilities.md +1 -1
- frontend/pages/config_view.py +102 -32
README.md
CHANGED
|
@@ -103,7 +103,7 @@ After the conversation completes, the app runs post-conversation analysis and po
|
|
| 103 |
|
| 104 |
- An **analysis framework** (stored in SQLite at `DB_PATH`) defines:
|
| 105 |
- bottom-up instructions + attributes
|
| 106 |
-
- rubric attributes
|
| 107 |
- top-down instructions + attributes + codebook categories (optional per-category descriptions)
|
| 108 |
- The **active framework is selected per run** using the dropdown next to the **📊 Analysis** header (AI-to-AI / Human-to-AI / Upload Text).
|
| 109 |
- The **Configuration** panel is used to create/duplicate/delete frameworks and edit them; selection there is for editing/review only.
|
|
|
|
| 103 |
|
| 104 |
- An **analysis framework** (stored in SQLite at `DB_PATH`) defines:
|
| 105 |
- bottom-up instructions + attributes
|
| 106 |
+
- rubric instructions + attributes
|
| 107 |
- top-down instructions + attributes + codebook categories (optional per-category descriptions)
|
| 108 |
- The **active framework is selected per run** using the dropdown next to the **📊 Analysis** header (AI-to-AI / Human-to-AI / Upload Text).
|
| 109 |
- The **Configuration** panel is used to create/duplicate/delete frameworks and edit them; selection there is for editing/review only.
|
backend/api/analysis_routes.py
CHANGED
|
@@ -169,6 +169,7 @@ async def _analyze_from_text(
|
|
| 169 |
analysis_system_prompt=effective_analysis_system_prompt if isinstance(effective_analysis_system_prompt, str) else None,
|
| 170 |
bottom_up_instructions=template_record.bottom_up_instructions if template_record else None,
|
| 171 |
bottom_up_attributes=template_record.bottom_up_attributes if template_record else None,
|
|
|
|
| 172 |
rubric_attributes=template_record.rubric_attributes if template_record else None,
|
| 173 |
top_down_instructions=template_record.top_down_instructions if template_record else None,
|
| 174 |
top_down_attributes=template_record.top_down_attributes if template_record else None,
|
|
@@ -198,6 +199,7 @@ async def _analyze_from_text(
|
|
| 198 |
"analysis_system_prompt": effective_analysis_system_prompt if isinstance(effective_analysis_system_prompt, str) else None,
|
| 199 |
"bottom_up_instructions": template_record.bottom_up_instructions if template_record else None,
|
| 200 |
"bottom_up_attributes": template_record.bottom_up_attributes if template_record else None,
|
|
|
|
| 201 |
"rubric_attributes": template_record.rubric_attributes if template_record else None,
|
| 202 |
"top_down_instructions": template_record.top_down_instructions if template_record else None,
|
| 203 |
"top_down_attributes": template_record.top_down_attributes if template_record else None,
|
|
|
|
| 169 |
analysis_system_prompt=effective_analysis_system_prompt if isinstance(effective_analysis_system_prompt, str) else None,
|
| 170 |
bottom_up_instructions=template_record.bottom_up_instructions if template_record else None,
|
| 171 |
bottom_up_attributes=template_record.bottom_up_attributes if template_record else None,
|
| 172 |
+
rubric_instructions=template_record.rubric_instructions if template_record else None,
|
| 173 |
rubric_attributes=template_record.rubric_attributes if template_record else None,
|
| 174 |
top_down_instructions=template_record.top_down_instructions if template_record else None,
|
| 175 |
top_down_attributes=template_record.top_down_attributes if template_record else None,
|
|
|
|
| 199 |
"analysis_system_prompt": effective_analysis_system_prompt if isinstance(effective_analysis_system_prompt, str) else None,
|
| 200 |
"bottom_up_instructions": template_record.bottom_up_instructions if template_record else None,
|
| 201 |
"bottom_up_attributes": template_record.bottom_up_attributes if template_record else None,
|
| 202 |
+
"rubric_instructions": template_record.rubric_instructions if template_record else None,
|
| 203 |
"rubric_attributes": template_record.rubric_attributes if template_record else None,
|
| 204 |
"top_down_instructions": template_record.top_down_instructions if template_record else None,
|
| 205 |
"top_down_attributes": template_record.top_down_attributes if template_record else None,
|
backend/api/analysis_template_routes.py
CHANGED
|
@@ -13,6 +13,7 @@ from .storage_service import get_persona_store
|
|
| 13 |
from backend.core.analysis_knobs import (
|
| 14 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 15 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
|
|
|
| 16 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 17 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 18 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
@@ -71,6 +72,7 @@ class AnalysisTemplateDetailResponse(BaseModel):
|
|
| 71 |
version_id: str
|
| 72 |
bottom_up_instructions: str = ""
|
| 73 |
bottom_up_attributes: List[str] = Field(default_factory=list)
|
|
|
|
| 74 |
rubric_attributes: List[str] = Field(default_factory=list)
|
| 75 |
top_down_instructions: str = ""
|
| 76 |
top_down_attributes: List[str] = Field(default_factory=list)
|
|
@@ -85,6 +87,7 @@ class CreateAnalysisTemplateRequest(BaseModel):
|
|
| 85 |
class UpdateAnalysisTemplateRequest(BaseModel):
|
| 86 |
bottom_up_instructions: Optional[str] = None
|
| 87 |
bottom_up_attributes: Optional[List[str]] = None
|
|
|
|
| 88 |
rubric_attributes: Optional[List[str]] = None
|
| 89 |
top_down_instructions: Optional[str] = None
|
| 90 |
top_down_attributes: Optional[List[str]] = None
|
|
@@ -120,6 +123,7 @@ async def get_template(template_id: str) -> AnalysisTemplateDetailResponse:
|
|
| 120 |
name=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("name") or "Default top-down codebook (v1)"),
|
| 121 |
bottom_up_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_instructions") or DEFAULT_BOTTOM_UP_INSTRUCTIONS),
|
| 122 |
bottom_up_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_attributes") or DEFAULT_BOTTOM_UP_ATTRIBUTES), # type: ignore[list-item]
|
|
|
|
| 123 |
rubric_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("rubric_attributes") or DEFAULT_RUBRIC_ATTRIBUTES), # type: ignore[list-item]
|
| 124 |
top_down_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_instructions") or DEFAULT_TOP_DOWN_INSTRUCTIONS),
|
| 125 |
top_down_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_attributes") or DEFAULT_TOP_DOWN_ATTRIBUTES), # type: ignore[list-item]
|
|
@@ -137,6 +141,7 @@ async def get_template(template_id: str) -> AnalysisTemplateDetailResponse:
|
|
| 137 |
version_id=record.current_version_id,
|
| 138 |
bottom_up_instructions=record.bottom_up_instructions,
|
| 139 |
bottom_up_attributes=record.bottom_up_attributes,
|
|
|
|
| 140 |
rubric_attributes=record.rubric_attributes,
|
| 141 |
top_down_instructions=record.top_down_instructions,
|
| 142 |
top_down_attributes=record.top_down_attributes,
|
|
@@ -154,6 +159,7 @@ async def create_template(payload: CreateAnalysisTemplateRequest) -> AnalysisTem
|
|
| 154 |
categories: List[Dict[str, str]] = []
|
| 155 |
bottom_up_instructions = DEFAULT_BOTTOM_UP_INSTRUCTIONS
|
| 156 |
bottom_up_attributes = list(DEFAULT_BOTTOM_UP_ATTRIBUTES)
|
|
|
|
| 157 |
rubric_attributes = list(DEFAULT_RUBRIC_ATTRIBUTES)
|
| 158 |
top_down_instructions = DEFAULT_TOP_DOWN_INSTRUCTIONS
|
| 159 |
top_down_attributes = list(DEFAULT_TOP_DOWN_ATTRIBUTES)
|
|
@@ -164,6 +170,7 @@ async def create_template(payload: CreateAnalysisTemplateRequest) -> AnalysisTem
|
|
| 164 |
categories = src.categories
|
| 165 |
bottom_up_instructions = src.bottom_up_instructions
|
| 166 |
bottom_up_attributes = list(src.bottom_up_attributes)
|
|
|
|
| 167 |
rubric_attributes = list(src.rubric_attributes)
|
| 168 |
top_down_instructions = src.top_down_instructions
|
| 169 |
top_down_attributes = list(src.top_down_attributes)
|
|
@@ -174,6 +181,7 @@ async def create_template(payload: CreateAnalysisTemplateRequest) -> AnalysisTem
|
|
| 174 |
name=name,
|
| 175 |
bottom_up_instructions=bottom_up_instructions,
|
| 176 |
bottom_up_attributes=bottom_up_attributes,
|
|
|
|
| 177 |
rubric_attributes=rubric_attributes,
|
| 178 |
top_down_instructions=top_down_instructions,
|
| 179 |
top_down_attributes=top_down_attributes,
|
|
@@ -190,6 +198,7 @@ async def create_template(payload: CreateAnalysisTemplateRequest) -> AnalysisTem
|
|
| 190 |
version_id=record.current_version_id,
|
| 191 |
bottom_up_instructions=record.bottom_up_instructions,
|
| 192 |
bottom_up_attributes=record.bottom_up_attributes,
|
|
|
|
| 193 |
rubric_attributes=record.rubric_attributes,
|
| 194 |
top_down_instructions=record.top_down_instructions,
|
| 195 |
top_down_attributes=record.top_down_attributes,
|
|
@@ -212,19 +221,21 @@ async def update_template(template_id: str, payload: UpdateAnalysisTemplateReque
|
|
| 212 |
|
| 213 |
bottom_up_instructions = payload.bottom_up_instructions if isinstance(payload.bottom_up_instructions, str) else record.bottom_up_instructions
|
| 214 |
bottom_up_attributes = payload.bottom_up_attributes if isinstance(payload.bottom_up_attributes, list) else record.bottom_up_attributes
|
|
|
|
| 215 |
rubric_attributes = payload.rubric_attributes if isinstance(payload.rubric_attributes, list) else record.rubric_attributes
|
| 216 |
top_down_instructions = payload.top_down_instructions if isinstance(payload.top_down_instructions, str) else record.top_down_instructions
|
| 217 |
top_down_attributes = payload.top_down_attributes if isinstance(payload.top_down_attributes, list) else record.top_down_attributes
|
| 218 |
|
| 219 |
try:
|
| 220 |
await store.update_analysis_template(
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
| 228 |
)
|
| 229 |
except PermissionError as e:
|
| 230 |
raise HTTPException(status_code=403, detail=str(e))
|
|
@@ -241,6 +252,7 @@ async def update_template(template_id: str, payload: UpdateAnalysisTemplateReque
|
|
| 241 |
version_id=updated.current_version_id,
|
| 242 |
bottom_up_instructions=updated.bottom_up_instructions,
|
| 243 |
bottom_up_attributes=updated.bottom_up_attributes,
|
|
|
|
| 244 |
rubric_attributes=updated.rubric_attributes,
|
| 245 |
top_down_instructions=updated.top_down_instructions,
|
| 246 |
top_down_attributes=updated.top_down_attributes,
|
|
|
|
| 13 |
from backend.core.analysis_knobs import (
|
| 14 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 15 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
| 16 |
+
DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 17 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 18 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 19 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
|
|
| 72 |
version_id: str
|
| 73 |
bottom_up_instructions: str = ""
|
| 74 |
bottom_up_attributes: List[str] = Field(default_factory=list)
|
| 75 |
+
rubric_instructions: str = ""
|
| 76 |
rubric_attributes: List[str] = Field(default_factory=list)
|
| 77 |
top_down_instructions: str = ""
|
| 78 |
top_down_attributes: List[str] = Field(default_factory=list)
|
|
|
|
| 87 |
class UpdateAnalysisTemplateRequest(BaseModel):
|
| 88 |
bottom_up_instructions: Optional[str] = None
|
| 89 |
bottom_up_attributes: Optional[List[str]] = None
|
| 90 |
+
rubric_instructions: Optional[str] = None
|
| 91 |
rubric_attributes: Optional[List[str]] = None
|
| 92 |
top_down_instructions: Optional[str] = None
|
| 93 |
top_down_attributes: Optional[List[str]] = None
|
|
|
|
| 123 |
name=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("name") or "Default top-down codebook (v1)"),
|
| 124 |
bottom_up_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_instructions") or DEFAULT_BOTTOM_UP_INSTRUCTIONS),
|
| 125 |
bottom_up_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_attributes") or DEFAULT_BOTTOM_UP_ATTRIBUTES), # type: ignore[list-item]
|
| 126 |
+
rubric_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("rubric_instructions") or DEFAULT_RUBRIC_INSTRUCTIONS),
|
| 127 |
rubric_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("rubric_attributes") or DEFAULT_RUBRIC_ATTRIBUTES), # type: ignore[list-item]
|
| 128 |
top_down_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_instructions") or DEFAULT_TOP_DOWN_INSTRUCTIONS),
|
| 129 |
top_down_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_attributes") or DEFAULT_TOP_DOWN_ATTRIBUTES), # type: ignore[list-item]
|
|
|
|
| 141 |
version_id=record.current_version_id,
|
| 142 |
bottom_up_instructions=record.bottom_up_instructions,
|
| 143 |
bottom_up_attributes=record.bottom_up_attributes,
|
| 144 |
+
rubric_instructions=record.rubric_instructions,
|
| 145 |
rubric_attributes=record.rubric_attributes,
|
| 146 |
top_down_instructions=record.top_down_instructions,
|
| 147 |
top_down_attributes=record.top_down_attributes,
|
|
|
|
| 159 |
categories: List[Dict[str, str]] = []
|
| 160 |
bottom_up_instructions = DEFAULT_BOTTOM_UP_INSTRUCTIONS
|
| 161 |
bottom_up_attributes = list(DEFAULT_BOTTOM_UP_ATTRIBUTES)
|
| 162 |
+
rubric_instructions = DEFAULT_RUBRIC_INSTRUCTIONS
|
| 163 |
rubric_attributes = list(DEFAULT_RUBRIC_ATTRIBUTES)
|
| 164 |
top_down_instructions = DEFAULT_TOP_DOWN_INSTRUCTIONS
|
| 165 |
top_down_attributes = list(DEFAULT_TOP_DOWN_ATTRIBUTES)
|
|
|
|
| 170 |
categories = src.categories
|
| 171 |
bottom_up_instructions = src.bottom_up_instructions
|
| 172 |
bottom_up_attributes = list(src.bottom_up_attributes)
|
| 173 |
+
rubric_instructions = src.rubric_instructions
|
| 174 |
rubric_attributes = list(src.rubric_attributes)
|
| 175 |
top_down_instructions = src.top_down_instructions
|
| 176 |
top_down_attributes = list(src.top_down_attributes)
|
|
|
|
| 181 |
name=name,
|
| 182 |
bottom_up_instructions=bottom_up_instructions,
|
| 183 |
bottom_up_attributes=bottom_up_attributes,
|
| 184 |
+
rubric_instructions=rubric_instructions,
|
| 185 |
rubric_attributes=rubric_attributes,
|
| 186 |
top_down_instructions=top_down_instructions,
|
| 187 |
top_down_attributes=top_down_attributes,
|
|
|
|
| 198 |
version_id=record.current_version_id,
|
| 199 |
bottom_up_instructions=record.bottom_up_instructions,
|
| 200 |
bottom_up_attributes=record.bottom_up_attributes,
|
| 201 |
+
rubric_instructions=record.rubric_instructions,
|
| 202 |
rubric_attributes=record.rubric_attributes,
|
| 203 |
top_down_instructions=record.top_down_instructions,
|
| 204 |
top_down_attributes=record.top_down_attributes,
|
|
|
|
| 221 |
|
| 222 |
bottom_up_instructions = payload.bottom_up_instructions if isinstance(payload.bottom_up_instructions, str) else record.bottom_up_instructions
|
| 223 |
bottom_up_attributes = payload.bottom_up_attributes if isinstance(payload.bottom_up_attributes, list) else record.bottom_up_attributes
|
| 224 |
+
rubric_instructions = payload.rubric_instructions if isinstance(payload.rubric_instructions, str) else record.rubric_instructions
|
| 225 |
rubric_attributes = payload.rubric_attributes if isinstance(payload.rubric_attributes, list) else record.rubric_attributes
|
| 226 |
top_down_instructions = payload.top_down_instructions if isinstance(payload.top_down_instructions, str) else record.top_down_instructions
|
| 227 |
top_down_attributes = payload.top_down_attributes if isinstance(payload.top_down_attributes, list) else record.top_down_attributes
|
| 228 |
|
| 229 |
try:
|
| 230 |
await store.update_analysis_template(
|
| 231 |
+
template_id=template_id,
|
| 232 |
+
bottom_up_instructions=bottom_up_instructions,
|
| 233 |
+
bottom_up_attributes=bottom_up_attributes,
|
| 234 |
+
rubric_instructions=rubric_instructions,
|
| 235 |
+
rubric_attributes=rubric_attributes,
|
| 236 |
+
top_down_instructions=top_down_instructions,
|
| 237 |
+
top_down_attributes=top_down_attributes,
|
| 238 |
+
categories=categories,
|
| 239 |
)
|
| 240 |
except PermissionError as e:
|
| 241 |
raise HTTPException(status_code=403, detail=str(e))
|
|
|
|
| 252 |
version_id=updated.current_version_id,
|
| 253 |
bottom_up_instructions=updated.bottom_up_instructions,
|
| 254 |
bottom_up_attributes=updated.bottom_up_attributes,
|
| 255 |
+
rubric_instructions=updated.rubric_instructions,
|
| 256 |
rubric_attributes=updated.rubric_attributes,
|
| 257 |
top_down_instructions=updated.top_down_instructions,
|
| 258 |
top_down_attributes=updated.top_down_attributes,
|
backend/api/conversation_service.py
CHANGED
|
@@ -50,6 +50,7 @@ from backend.core.analysis_knobs import (
|
|
| 50 |
compile_analysis_rules_block,
|
| 51 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 52 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
|
|
|
| 53 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 54 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 55 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
@@ -106,6 +107,7 @@ async def run_resource_agent_analysis(
|
|
| 106 |
analysis_system_prompt: Optional[str] = None,
|
| 107 |
bottom_up_instructions: Optional[str] = None,
|
| 108 |
bottom_up_attributes: Optional[List[str]] = None,
|
|
|
|
| 109 |
rubric_attributes: Optional[List[str]] = None,
|
| 110 |
top_down_instructions: Optional[str] = None,
|
| 111 |
top_down_attributes: Optional[List[str]] = None,
|
|
@@ -175,8 +177,11 @@ async def run_resource_agent_analysis(
|
|
| 175 |
+ "\n\n"
|
| 176 |
+ compile_analysis_rules_block(bottom_up_attributes, defaults=DEFAULT_BOTTOM_UP_ATTRIBUTES)
|
| 177 |
).strip()
|
|
|
|
| 178 |
rubric_system_prompt = (
|
| 179 |
common_system_prompt
|
|
|
|
|
|
|
| 180 |
+ "\n\n"
|
| 181 |
+ compile_analysis_rules_block(rubric_attributes, defaults=DEFAULT_RUBRIC_ATTRIBUTES)
|
| 182 |
).strip()
|
|
@@ -435,6 +440,7 @@ class ConversationInfo:
|
|
| 435 |
analysis_system_prompt: str = ""
|
| 436 |
bottom_up_instructions: str = ""
|
| 437 |
bottom_up_attributes: List[str] = field(default_factory=list)
|
|
|
|
| 438 |
rubric_attributes: List[str] = field(default_factory=list)
|
| 439 |
top_down_instructions: str = ""
|
| 440 |
top_down_attributes: List[str] = field(default_factory=list)
|
|
@@ -464,6 +470,7 @@ class HumanChatInfo:
|
|
| 464 |
analysis_system_prompt: str = ""
|
| 465 |
bottom_up_instructions: str = ""
|
| 466 |
bottom_up_attributes: List[str] = field(default_factory=list)
|
|
|
|
| 467 |
rubric_attributes: List[str] = field(default_factory=list)
|
| 468 |
top_down_instructions: str = ""
|
| 469 |
top_down_attributes: List[str] = field(default_factory=list)
|
|
@@ -537,6 +544,7 @@ class ConversationService:
|
|
| 537 |
"template_version_id": record.current_version_id,
|
| 538 |
"bottom_up_instructions": record.bottom_up_instructions,
|
| 539 |
"bottom_up_attributes": list(record.bottom_up_attributes),
|
|
|
|
| 540 |
"rubric_attributes": list(record.rubric_attributes),
|
| 541 |
"top_down_instructions": record.top_down_instructions,
|
| 542 |
"top_down_attributes": list(record.top_down_attributes),
|
|
@@ -552,6 +560,7 @@ class ConversationService:
|
|
| 552 |
"template_version_id": record.current_version_id,
|
| 553 |
"bottom_up_instructions": record.bottom_up_instructions,
|
| 554 |
"bottom_up_attributes": list(record.bottom_up_attributes),
|
|
|
|
| 555 |
"rubric_attributes": list(record.rubric_attributes),
|
| 556 |
"top_down_instructions": record.top_down_instructions,
|
| 557 |
"top_down_attributes": list(record.top_down_attributes),
|
|
@@ -562,6 +571,7 @@ class ConversationService:
|
|
| 562 |
"template_version_id": "",
|
| 563 |
"bottom_up_instructions": DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 564 |
"bottom_up_attributes": list(DEFAULT_BOTTOM_UP_ATTRIBUTES),
|
|
|
|
| 565 |
"rubric_attributes": list(DEFAULT_RUBRIC_ATTRIBUTES),
|
| 566 |
"top_down_instructions": DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 567 |
"top_down_attributes": list(DEFAULT_TOP_DOWN_ATTRIBUTES),
|
|
@@ -612,6 +622,7 @@ class ConversationService:
|
|
| 612 |
template = await self._resolve_top_down_template(top_down_codebook_template_id)
|
| 613 |
resolved_bottom_instructions = str(template.get("bottom_up_instructions") or "").strip() or DEFAULT_BOTTOM_UP_INSTRUCTIONS
|
| 614 |
resolved_top_down_instructions = str(template.get("top_down_instructions") or "").strip() or DEFAULT_TOP_DOWN_INSTRUCTIONS
|
|
|
|
| 615 |
bua = template.get("bottom_up_attributes")
|
| 616 |
ra = template.get("rubric_attributes")
|
| 617 |
tda = template.get("top_down_attributes")
|
|
@@ -637,6 +648,7 @@ class ConversationService:
|
|
| 637 |
analysis_system_prompt=resolved_analysis_prompt,
|
| 638 |
bottom_up_instructions=resolved_bottom_instructions,
|
| 639 |
bottom_up_attributes=resolved_bottom_attrs,
|
|
|
|
| 640 |
rubric_attributes=resolved_rubric_attrs,
|
| 641 |
top_down_instructions=resolved_top_down_instructions,
|
| 642 |
top_down_attributes=resolved_top_down_attrs,
|
|
@@ -995,6 +1007,7 @@ class ConversationService:
|
|
| 995 |
template = await self._resolve_top_down_template(top_down_codebook_template_id)
|
| 996 |
resolved_bottom_instructions = str(template.get("bottom_up_instructions") or "").strip() or DEFAULT_BOTTOM_UP_INSTRUCTIONS
|
| 997 |
resolved_top_down_instructions = str(template.get("top_down_instructions") or "").strip() or DEFAULT_TOP_DOWN_INSTRUCTIONS
|
|
|
|
| 998 |
bua = template.get("bottom_up_attributes")
|
| 999 |
ra = template.get("rubric_attributes")
|
| 1000 |
tda = template.get("top_down_attributes")
|
|
@@ -1021,6 +1034,7 @@ class ConversationService:
|
|
| 1021 |
analysis_system_prompt=resolved_analysis_prompt,
|
| 1022 |
bottom_up_instructions=resolved_bottom_instructions,
|
| 1023 |
bottom_up_attributes=resolved_bottom_attrs,
|
|
|
|
| 1024 |
rubric_attributes=resolved_rubric_attrs,
|
| 1025 |
top_down_instructions=resolved_top_down_instructions,
|
| 1026 |
top_down_attributes=resolved_top_down_attrs,
|
|
@@ -1312,6 +1326,7 @@ class ConversationService:
|
|
| 1312 |
analysis_system_prompt=getattr(conv_info, "analysis_system_prompt", None),
|
| 1313 |
bottom_up_instructions=getattr(conv_info, "bottom_up_instructions", None),
|
| 1314 |
bottom_up_attributes=getattr(conv_info, "bottom_up_attributes", None),
|
|
|
|
| 1315 |
rubric_attributes=getattr(conv_info, "rubric_attributes", None),
|
| 1316 |
top_down_instructions=getattr(conv_info, "top_down_instructions", None),
|
| 1317 |
top_down_attributes=getattr(conv_info, "top_down_attributes", None),
|
|
@@ -1367,11 +1382,12 @@ class ConversationService:
|
|
| 1367 |
"asked_question_ids": asked_question_ids,
|
| 1368 |
},
|
| 1369 |
"analysis": {
|
| 1370 |
-
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
|
| 1374 |
-
|
|
|
|
| 1375 |
"top_down_attributes": getattr(conv_info, "top_down_attributes", None),
|
| 1376 |
"top_down_codebook_template_id": getattr(conv_info, "top_down_template_id", None),
|
| 1377 |
"top_down_codebook_template_version_id": getattr(conv_info, "top_down_template_version_id", None),
|
|
|
|
| 50 |
compile_analysis_rules_block,
|
| 51 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 52 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
| 53 |
+
DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 54 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 55 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 56 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
|
|
| 107 |
analysis_system_prompt: Optional[str] = None,
|
| 108 |
bottom_up_instructions: Optional[str] = None,
|
| 109 |
bottom_up_attributes: Optional[List[str]] = None,
|
| 110 |
+
rubric_instructions: Optional[str] = None,
|
| 111 |
rubric_attributes: Optional[List[str]] = None,
|
| 112 |
top_down_instructions: Optional[str] = None,
|
| 113 |
top_down_attributes: Optional[List[str]] = None,
|
|
|
|
| 177 |
+ "\n\n"
|
| 178 |
+ compile_analysis_rules_block(bottom_up_attributes, defaults=DEFAULT_BOTTOM_UP_ATTRIBUTES)
|
| 179 |
).strip()
|
| 180 |
+
resolved_rubric_instructions = (rubric_instructions or "").strip() or DEFAULT_RUBRIC_INSTRUCTIONS
|
| 181 |
rubric_system_prompt = (
|
| 182 |
common_system_prompt
|
| 183 |
+
+ "\n\nCare experience rubric instructions:\n"
|
| 184 |
+
+ resolved_rubric_instructions.strip()
|
| 185 |
+ "\n\n"
|
| 186 |
+ compile_analysis_rules_block(rubric_attributes, defaults=DEFAULT_RUBRIC_ATTRIBUTES)
|
| 187 |
).strip()
|
|
|
|
| 440 |
analysis_system_prompt: str = ""
|
| 441 |
bottom_up_instructions: str = ""
|
| 442 |
bottom_up_attributes: List[str] = field(default_factory=list)
|
| 443 |
+
rubric_instructions: str = ""
|
| 444 |
rubric_attributes: List[str] = field(default_factory=list)
|
| 445 |
top_down_instructions: str = ""
|
| 446 |
top_down_attributes: List[str] = field(default_factory=list)
|
|
|
|
| 470 |
analysis_system_prompt: str = ""
|
| 471 |
bottom_up_instructions: str = ""
|
| 472 |
bottom_up_attributes: List[str] = field(default_factory=list)
|
| 473 |
+
rubric_instructions: str = ""
|
| 474 |
rubric_attributes: List[str] = field(default_factory=list)
|
| 475 |
top_down_instructions: str = ""
|
| 476 |
top_down_attributes: List[str] = field(default_factory=list)
|
|
|
|
| 544 |
"template_version_id": record.current_version_id,
|
| 545 |
"bottom_up_instructions": record.bottom_up_instructions,
|
| 546 |
"bottom_up_attributes": list(record.bottom_up_attributes),
|
| 547 |
+
"rubric_instructions": record.rubric_instructions,
|
| 548 |
"rubric_attributes": list(record.rubric_attributes),
|
| 549 |
"top_down_instructions": record.top_down_instructions,
|
| 550 |
"top_down_attributes": list(record.top_down_attributes),
|
|
|
|
| 560 |
"template_version_id": record.current_version_id,
|
| 561 |
"bottom_up_instructions": record.bottom_up_instructions,
|
| 562 |
"bottom_up_attributes": list(record.bottom_up_attributes),
|
| 563 |
+
"rubric_instructions": record.rubric_instructions,
|
| 564 |
"rubric_attributes": list(record.rubric_attributes),
|
| 565 |
"top_down_instructions": record.top_down_instructions,
|
| 566 |
"top_down_attributes": list(record.top_down_attributes),
|
|
|
|
| 571 |
"template_version_id": "",
|
| 572 |
"bottom_up_instructions": DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 573 |
"bottom_up_attributes": list(DEFAULT_BOTTOM_UP_ATTRIBUTES),
|
| 574 |
+
"rubric_instructions": DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 575 |
"rubric_attributes": list(DEFAULT_RUBRIC_ATTRIBUTES),
|
| 576 |
"top_down_instructions": DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 577 |
"top_down_attributes": list(DEFAULT_TOP_DOWN_ATTRIBUTES),
|
|
|
|
| 622 |
template = await self._resolve_top_down_template(top_down_codebook_template_id)
|
| 623 |
resolved_bottom_instructions = str(template.get("bottom_up_instructions") or "").strip() or DEFAULT_BOTTOM_UP_INSTRUCTIONS
|
| 624 |
resolved_top_down_instructions = str(template.get("top_down_instructions") or "").strip() or DEFAULT_TOP_DOWN_INSTRUCTIONS
|
| 625 |
+
resolved_rubric_instructions = str(template.get("rubric_instructions") or "").strip() or DEFAULT_RUBRIC_INSTRUCTIONS
|
| 626 |
bua = template.get("bottom_up_attributes")
|
| 627 |
ra = template.get("rubric_attributes")
|
| 628 |
tda = template.get("top_down_attributes")
|
|
|
|
| 648 |
analysis_system_prompt=resolved_analysis_prompt,
|
| 649 |
bottom_up_instructions=resolved_bottom_instructions,
|
| 650 |
bottom_up_attributes=resolved_bottom_attrs,
|
| 651 |
+
rubric_instructions=resolved_rubric_instructions,
|
| 652 |
rubric_attributes=resolved_rubric_attrs,
|
| 653 |
top_down_instructions=resolved_top_down_instructions,
|
| 654 |
top_down_attributes=resolved_top_down_attrs,
|
|
|
|
| 1007 |
template = await self._resolve_top_down_template(top_down_codebook_template_id)
|
| 1008 |
resolved_bottom_instructions = str(template.get("bottom_up_instructions") or "").strip() or DEFAULT_BOTTOM_UP_INSTRUCTIONS
|
| 1009 |
resolved_top_down_instructions = str(template.get("top_down_instructions") or "").strip() or DEFAULT_TOP_DOWN_INSTRUCTIONS
|
| 1010 |
+
resolved_rubric_instructions = str(template.get("rubric_instructions") or "").strip() or DEFAULT_RUBRIC_INSTRUCTIONS
|
| 1011 |
bua = template.get("bottom_up_attributes")
|
| 1012 |
ra = template.get("rubric_attributes")
|
| 1013 |
tda = template.get("top_down_attributes")
|
|
|
|
| 1034 |
analysis_system_prompt=resolved_analysis_prompt,
|
| 1035 |
bottom_up_instructions=resolved_bottom_instructions,
|
| 1036 |
bottom_up_attributes=resolved_bottom_attrs,
|
| 1037 |
+
rubric_instructions=resolved_rubric_instructions,
|
| 1038 |
rubric_attributes=resolved_rubric_attrs,
|
| 1039 |
top_down_instructions=resolved_top_down_instructions,
|
| 1040 |
top_down_attributes=resolved_top_down_attrs,
|
|
|
|
| 1326 |
analysis_system_prompt=getattr(conv_info, "analysis_system_prompt", None),
|
| 1327 |
bottom_up_instructions=getattr(conv_info, "bottom_up_instructions", None),
|
| 1328 |
bottom_up_attributes=getattr(conv_info, "bottom_up_attributes", None),
|
| 1329 |
+
rubric_instructions=getattr(conv_info, "rubric_instructions", None),
|
| 1330 |
rubric_attributes=getattr(conv_info, "rubric_attributes", None),
|
| 1331 |
top_down_instructions=getattr(conv_info, "top_down_instructions", None),
|
| 1332 |
top_down_attributes=getattr(conv_info, "top_down_attributes", None),
|
|
|
|
| 1382 |
"asked_question_ids": asked_question_ids,
|
| 1383 |
},
|
| 1384 |
"analysis": {
|
| 1385 |
+
"analysis_system_prompt": getattr(conv_info, "analysis_system_prompt", None),
|
| 1386 |
+
"bottom_up_instructions": getattr(conv_info, "bottom_up_instructions", None),
|
| 1387 |
+
"bottom_up_attributes": getattr(conv_info, "bottom_up_attributes", None),
|
| 1388 |
+
"rubric_instructions": getattr(conv_info, "rubric_instructions", None),
|
| 1389 |
+
"rubric_attributes": getattr(conv_info, "rubric_attributes", None),
|
| 1390 |
+
"top_down_instructions": getattr(conv_info, "top_down_instructions", None),
|
| 1391 |
"top_down_attributes": getattr(conv_info, "top_down_attributes", None),
|
| 1392 |
"top_down_codebook_template_id": getattr(conv_info, "top_down_template_id", None),
|
| 1393 |
"top_down_codebook_template_version_id": getattr(conv_info, "top_down_template_version_id", None),
|
backend/core/analysis_knobs.py
CHANGED
|
@@ -11,6 +11,10 @@ DEFAULT_TOP_DOWN_INSTRUCTIONS: str = (
|
|
| 11 |
"You are performing top-down coding using the provided codebook categories. For each category, extract the most relevant codes/items supported by evidence."
|
| 12 |
)
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
DEFAULT_BOTTOM_UP_ATTRIBUTES: List[str] = [
|
| 16 |
"Prefer fewer, higher-confidence items.",
|
|
|
|
| 11 |
"You are performing top-down coding using the provided codebook categories. For each category, extract the most relevant codes/items supported by evidence."
|
| 12 |
)
|
| 13 |
|
| 14 |
+
DEFAULT_RUBRIC_INSTRUCTIONS: str = (
|
| 15 |
+
"You are performing a care experience rubric analysis. Summarize the overall experience and list the key reasons for each bucket."
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
|
| 19 |
DEFAULT_BOTTOM_UP_ATTRIBUTES: List[str] = [
|
| 20 |
"Prefer fewer, higher-confidence items.",
|
backend/core/default_analysis_templates.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Dict, List
|
|
| 5 |
from backend.core.analysis_knobs import (
|
| 6 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 7 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
|
|
|
| 8 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 9 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 10 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
@@ -15,6 +16,7 @@ DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE: Dict[str, object] = {
|
|
| 15 |
"name": "Default top-down codebook (v1)",
|
| 16 |
"bottom_up_instructions": DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 17 |
"bottom_up_attributes": list(DEFAULT_BOTTOM_UP_ATTRIBUTES),
|
|
|
|
| 18 |
"rubric_attributes": list(DEFAULT_RUBRIC_ATTRIBUTES),
|
| 19 |
"top_down_instructions": DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 20 |
"top_down_attributes": list(DEFAULT_TOP_DOWN_ATTRIBUTES),
|
|
|
|
| 5 |
from backend.core.analysis_knobs import (
|
| 6 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 7 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
| 8 |
+
DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 9 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 10 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 11 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
|
|
| 16 |
"name": "Default top-down codebook (v1)",
|
| 17 |
"bottom_up_instructions": DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 18 |
"bottom_up_attributes": list(DEFAULT_BOTTOM_UP_ATTRIBUTES),
|
| 19 |
+
"rubric_instructions": DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 20 |
"rubric_attributes": list(DEFAULT_RUBRIC_ATTRIBUTES),
|
| 21 |
"top_down_instructions": DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 22 |
"top_down_attributes": list(DEFAULT_TOP_DOWN_ATTRIBUTES),
|
backend/core/persona_seed.py
CHANGED
|
@@ -7,6 +7,7 @@ from backend.core.universal_prompts import DEFAULT_PATIENT_SYSTEM_PROMPT, DEFAUL
|
|
| 7 |
from backend.core.analysis_knobs import (
|
| 8 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 9 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
|
|
|
| 10 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 11 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 12 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
@@ -38,6 +39,7 @@ async def seed_defaults_overwrite(*, store: SQLitePersonaStore) -> None:
|
|
| 38 |
name=template_name,
|
| 39 |
bottom_up_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_instructions") or DEFAULT_BOTTOM_UP_INSTRUCTIONS),
|
| 40 |
bottom_up_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_attributes") or DEFAULT_BOTTOM_UP_ATTRIBUTES), # type: ignore[list-item]
|
|
|
|
| 41 |
rubric_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("rubric_attributes") or DEFAULT_RUBRIC_ATTRIBUTES), # type: ignore[list-item]
|
| 42 |
top_down_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_instructions") or DEFAULT_TOP_DOWN_INSTRUCTIONS),
|
| 43 |
top_down_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_attributes") or DEFAULT_TOP_DOWN_ATTRIBUTES), # type: ignore[list-item]
|
|
@@ -62,6 +64,7 @@ async def seed_defaults_overwrite(*, store: SQLitePersonaStore) -> None:
|
|
| 62 |
not isinstance(t.bottom_up_instructions, str)
|
| 63 |
or not t.bottom_up_instructions.strip()
|
| 64 |
or not (isinstance(t.bottom_up_attributes, list) and any(isinstance(x, str) and x.strip() for x in t.bottom_up_attributes))
|
|
|
|
| 65 |
or not (isinstance(t.rubric_attributes, list) and any(isinstance(x, str) and x.strip() for x in t.rubric_attributes))
|
| 66 |
or not (isinstance(t.top_down_instructions, str) and t.top_down_instructions.strip())
|
| 67 |
or not (isinstance(t.top_down_attributes, list) and any(isinstance(x, str) and x.strip() for x in t.top_down_attributes))
|
|
@@ -72,6 +75,7 @@ async def seed_defaults_overwrite(*, store: SQLitePersonaStore) -> None:
|
|
| 72 |
template_id=t.template_id,
|
| 73 |
bottom_up_instructions=t.bottom_up_instructions.strip() if isinstance(t.bottom_up_instructions, str) and t.bottom_up_instructions.strip() else DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 74 |
bottom_up_attributes=t.bottom_up_attributes if t.bottom_up_attributes else list(DEFAULT_BOTTOM_UP_ATTRIBUTES),
|
|
|
|
| 75 |
rubric_attributes=t.rubric_attributes if t.rubric_attributes else list(DEFAULT_RUBRIC_ATTRIBUTES),
|
| 76 |
top_down_instructions=t.top_down_instructions.strip() if isinstance(t.top_down_instructions, str) and t.top_down_instructions.strip() else DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 77 |
top_down_attributes=t.top_down_attributes if t.top_down_attributes else list(DEFAULT_TOP_DOWN_ATTRIBUTES),
|
|
|
|
| 7 |
from backend.core.analysis_knobs import (
|
| 8 |
DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 9 |
DEFAULT_BOTTOM_UP_ATTRIBUTES,
|
| 10 |
+
DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 11 |
DEFAULT_RUBRIC_ATTRIBUTES,
|
| 12 |
DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 13 |
DEFAULT_TOP_DOWN_ATTRIBUTES,
|
|
|
|
| 39 |
name=template_name,
|
| 40 |
bottom_up_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_instructions") or DEFAULT_BOTTOM_UP_INSTRUCTIONS),
|
| 41 |
bottom_up_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("bottom_up_attributes") or DEFAULT_BOTTOM_UP_ATTRIBUTES), # type: ignore[list-item]
|
| 42 |
+
rubric_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("rubric_instructions") or DEFAULT_RUBRIC_INSTRUCTIONS),
|
| 43 |
rubric_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("rubric_attributes") or DEFAULT_RUBRIC_ATTRIBUTES), # type: ignore[list-item]
|
| 44 |
top_down_instructions=str(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_instructions") or DEFAULT_TOP_DOWN_INSTRUCTIONS),
|
| 45 |
top_down_attributes=list(DEFAULT_TOP_DOWN_CODEBOOK_TEMPLATE.get("top_down_attributes") or DEFAULT_TOP_DOWN_ATTRIBUTES), # type: ignore[list-item]
|
|
|
|
| 64 |
not isinstance(t.bottom_up_instructions, str)
|
| 65 |
or not t.bottom_up_instructions.strip()
|
| 66 |
or not (isinstance(t.bottom_up_attributes, list) and any(isinstance(x, str) and x.strip() for x in t.bottom_up_attributes))
|
| 67 |
+
or not isinstance(t.rubric_instructions, str)
|
| 68 |
or not (isinstance(t.rubric_attributes, list) and any(isinstance(x, str) and x.strip() for x in t.rubric_attributes))
|
| 69 |
or not (isinstance(t.top_down_instructions, str) and t.top_down_instructions.strip())
|
| 70 |
or not (isinstance(t.top_down_attributes, list) and any(isinstance(x, str) and x.strip() for x in t.top_down_attributes))
|
|
|
|
| 75 |
template_id=t.template_id,
|
| 76 |
bottom_up_instructions=t.bottom_up_instructions.strip() if isinstance(t.bottom_up_instructions, str) and t.bottom_up_instructions.strip() else DEFAULT_BOTTOM_UP_INSTRUCTIONS,
|
| 77 |
bottom_up_attributes=t.bottom_up_attributes if t.bottom_up_attributes else list(DEFAULT_BOTTOM_UP_ATTRIBUTES),
|
| 78 |
+
rubric_instructions=t.rubric_instructions.strip() if isinstance(t.rubric_instructions, str) and t.rubric_instructions.strip() else DEFAULT_RUBRIC_INSTRUCTIONS,
|
| 79 |
rubric_attributes=t.rubric_attributes if t.rubric_attributes else list(DEFAULT_RUBRIC_ATTRIBUTES),
|
| 80 |
top_down_instructions=t.top_down_instructions.strip() if isinstance(t.top_down_instructions, str) and t.top_down_instructions.strip() else DEFAULT_TOP_DOWN_INSTRUCTIONS,
|
| 81 |
top_down_attributes=t.top_down_attributes if t.top_down_attributes else list(DEFAULT_TOP_DOWN_ATTRIBUTES),
|
backend/storage/models.py
CHANGED
|
@@ -73,6 +73,7 @@ class AnalysisTemplateRecord:
|
|
| 73 |
updated_at: str
|
| 74 |
bottom_up_instructions: str
|
| 75 |
bottom_up_attributes: List[str]
|
|
|
|
| 76 |
rubric_attributes: List[str]
|
| 77 |
top_down_instructions: str
|
| 78 |
top_down_attributes: List[str]
|
|
|
|
| 73 |
updated_at: str
|
| 74 |
bottom_up_instructions: str
|
| 75 |
bottom_up_attributes: List[str]
|
| 76 |
+
rubric_instructions: str
|
| 77 |
rubric_attributes: List[str]
|
| 78 |
top_down_instructions: str
|
| 79 |
top_down_attributes: List[str]
|
backend/storage/sqlite_persona_store.py
CHANGED
|
@@ -212,6 +212,7 @@ class SQLitePersonaStore:
|
|
| 212 |
updated_at=row[6],
|
| 213 |
bottom_up_instructions=_clean_str(content.get("bottom_up_instructions")),
|
| 214 |
bottom_up_attributes=_clean_str_list(content.get("bottom_up_attributes")),
|
|
|
|
| 215 |
rubric_attributes=_clean_str_list(content.get("rubric_attributes")),
|
| 216 |
top_down_instructions=_clean_str(content.get("top_down_instructions")),
|
| 217 |
top_down_attributes=_clean_str_list(content.get("top_down_attributes")),
|
|
@@ -270,6 +271,7 @@ class SQLitePersonaStore:
|
|
| 270 |
updated_at=row[6],
|
| 271 |
bottom_up_instructions=_clean_str(content.get("bottom_up_instructions")),
|
| 272 |
bottom_up_attributes=_clean_str_list(content.get("bottom_up_attributes")),
|
|
|
|
| 273 |
rubric_attributes=_clean_str_list(content.get("rubric_attributes")),
|
| 274 |
top_down_instructions=_clean_str(content.get("top_down_instructions")),
|
| 275 |
top_down_attributes=_clean_str_list(content.get("top_down_attributes")),
|
|
@@ -283,6 +285,7 @@ class SQLitePersonaStore:
|
|
| 283 |
name: str,
|
| 284 |
bottom_up_instructions: str,
|
| 285 |
bottom_up_attributes: List[str],
|
|
|
|
| 286 |
rubric_attributes: List[str],
|
| 287 |
top_down_instructions: str,
|
| 288 |
top_down_attributes: List[str],
|
|
@@ -297,6 +300,7 @@ class SQLitePersonaStore:
|
|
| 297 |
raise ValueError("categories are required")
|
| 298 |
bui = _clean_str(bottom_up_instructions)
|
| 299 |
bua = _clean_str_list(bottom_up_attributes)
|
|
|
|
| 300 |
ra = _clean_str_list(rubric_attributes)
|
| 301 |
tdi = _clean_str(top_down_instructions)
|
| 302 |
tda = _clean_str_list(top_down_attributes)
|
|
@@ -307,6 +311,7 @@ class SQLitePersonaStore:
|
|
| 307 |
{
|
| 308 |
"bottom_up_instructions": bui,
|
| 309 |
"bottom_up_attributes": bua,
|
|
|
|
| 310 |
"rubric_attributes": ra,
|
| 311 |
"top_down_instructions": tdi,
|
| 312 |
"top_down_attributes": tda,
|
|
@@ -357,6 +362,7 @@ class SQLitePersonaStore:
|
|
| 357 |
name: str,
|
| 358 |
bottom_up_instructions: str,
|
| 359 |
bottom_up_attributes: List[str],
|
|
|
|
| 360 |
rubric_attributes: List[str],
|
| 361 |
top_down_instructions: str,
|
| 362 |
top_down_attributes: List[str],
|
|
@@ -370,6 +376,7 @@ class SQLitePersonaStore:
|
|
| 370 |
raise ValueError("categories are required")
|
| 371 |
bui = _clean_str(bottom_up_instructions)
|
| 372 |
bua = _clean_str_list(bottom_up_attributes)
|
|
|
|
| 373 |
ra = _clean_str_list(rubric_attributes)
|
| 374 |
tdi = _clean_str(top_down_instructions)
|
| 375 |
tda = _clean_str_list(top_down_attributes)
|
|
@@ -381,6 +388,7 @@ class SQLitePersonaStore:
|
|
| 381 |
{
|
| 382 |
"bottom_up_instructions": bui,
|
| 383 |
"bottom_up_attributes": bua,
|
|
|
|
| 384 |
"rubric_attributes": ra,
|
| 385 |
"top_down_instructions": tdi,
|
| 386 |
"top_down_attributes": tda,
|
|
@@ -419,6 +427,7 @@ class SQLitePersonaStore:
|
|
| 419 |
template_id: str,
|
| 420 |
bottom_up_instructions: str,
|
| 421 |
bottom_up_attributes: List[str],
|
|
|
|
| 422 |
rubric_attributes: List[str],
|
| 423 |
top_down_instructions: str,
|
| 424 |
top_down_attributes: List[str],
|
|
@@ -432,6 +441,7 @@ class SQLitePersonaStore:
|
|
| 432 |
raise ValueError("categories are required")
|
| 433 |
bui = _clean_str(bottom_up_instructions)
|
| 434 |
bua = _clean_str_list(bottom_up_attributes)
|
|
|
|
| 435 |
ra = _clean_str_list(rubric_attributes)
|
| 436 |
tdi = _clean_str(top_down_instructions)
|
| 437 |
tda = _clean_str_list(top_down_attributes)
|
|
@@ -442,6 +452,7 @@ class SQLitePersonaStore:
|
|
| 442 |
{
|
| 443 |
"bottom_up_instructions": bui,
|
| 444 |
"bottom_up_attributes": bua,
|
|
|
|
| 445 |
"rubric_attributes": ra,
|
| 446 |
"top_down_instructions": tdi,
|
| 447 |
"top_down_attributes": tda,
|
|
|
|
| 212 |
updated_at=row[6],
|
| 213 |
bottom_up_instructions=_clean_str(content.get("bottom_up_instructions")),
|
| 214 |
bottom_up_attributes=_clean_str_list(content.get("bottom_up_attributes")),
|
| 215 |
+
rubric_instructions=_clean_str(content.get("rubric_instructions")),
|
| 216 |
rubric_attributes=_clean_str_list(content.get("rubric_attributes")),
|
| 217 |
top_down_instructions=_clean_str(content.get("top_down_instructions")),
|
| 218 |
top_down_attributes=_clean_str_list(content.get("top_down_attributes")),
|
|
|
|
| 271 |
updated_at=row[6],
|
| 272 |
bottom_up_instructions=_clean_str(content.get("bottom_up_instructions")),
|
| 273 |
bottom_up_attributes=_clean_str_list(content.get("bottom_up_attributes")),
|
| 274 |
+
rubric_instructions=_clean_str(content.get("rubric_instructions")),
|
| 275 |
rubric_attributes=_clean_str_list(content.get("rubric_attributes")),
|
| 276 |
top_down_instructions=_clean_str(content.get("top_down_instructions")),
|
| 277 |
top_down_attributes=_clean_str_list(content.get("top_down_attributes")),
|
|
|
|
| 285 |
name: str,
|
| 286 |
bottom_up_instructions: str,
|
| 287 |
bottom_up_attributes: List[str],
|
| 288 |
+
rubric_instructions: str,
|
| 289 |
rubric_attributes: List[str],
|
| 290 |
top_down_instructions: str,
|
| 291 |
top_down_attributes: List[str],
|
|
|
|
| 300 |
raise ValueError("categories are required")
|
| 301 |
bui = _clean_str(bottom_up_instructions)
|
| 302 |
bua = _clean_str_list(bottom_up_attributes)
|
| 303 |
+
ri = _clean_str(rubric_instructions)
|
| 304 |
ra = _clean_str_list(rubric_attributes)
|
| 305 |
tdi = _clean_str(top_down_instructions)
|
| 306 |
tda = _clean_str_list(top_down_attributes)
|
|
|
|
| 311 |
{
|
| 312 |
"bottom_up_instructions": bui,
|
| 313 |
"bottom_up_attributes": bua,
|
| 314 |
+
"rubric_instructions": ri,
|
| 315 |
"rubric_attributes": ra,
|
| 316 |
"top_down_instructions": tdi,
|
| 317 |
"top_down_attributes": tda,
|
|
|
|
| 362 |
name: str,
|
| 363 |
bottom_up_instructions: str,
|
| 364 |
bottom_up_attributes: List[str],
|
| 365 |
+
rubric_instructions: str,
|
| 366 |
rubric_attributes: List[str],
|
| 367 |
top_down_instructions: str,
|
| 368 |
top_down_attributes: List[str],
|
|
|
|
| 376 |
raise ValueError("categories are required")
|
| 377 |
bui = _clean_str(bottom_up_instructions)
|
| 378 |
bua = _clean_str_list(bottom_up_attributes)
|
| 379 |
+
ri = _clean_str(rubric_instructions)
|
| 380 |
ra = _clean_str_list(rubric_attributes)
|
| 381 |
tdi = _clean_str(top_down_instructions)
|
| 382 |
tda = _clean_str_list(top_down_attributes)
|
|
|
|
| 388 |
{
|
| 389 |
"bottom_up_instructions": bui,
|
| 390 |
"bottom_up_attributes": bua,
|
| 391 |
+
"rubric_instructions": ri,
|
| 392 |
"rubric_attributes": ra,
|
| 393 |
"top_down_instructions": tdi,
|
| 394 |
"top_down_attributes": tda,
|
|
|
|
| 427 |
template_id: str,
|
| 428 |
bottom_up_instructions: str,
|
| 429 |
bottom_up_attributes: List[str],
|
| 430 |
+
rubric_instructions: str,
|
| 431 |
rubric_attributes: List[str],
|
| 432 |
top_down_instructions: str,
|
| 433 |
top_down_attributes: List[str],
|
|
|
|
| 441 |
raise ValueError("categories are required")
|
| 442 |
bui = _clean_str(bottom_up_instructions)
|
| 443 |
bua = _clean_str_list(bottom_up_attributes)
|
| 444 |
+
ri = _clean_str(rubric_instructions)
|
| 445 |
ra = _clean_str_list(rubric_attributes)
|
| 446 |
tdi = _clean_str(top_down_instructions)
|
| 447 |
tda = _clean_str_list(top_down_attributes)
|
|
|
|
| 452 |
{
|
| 453 |
"bottom_up_instructions": bui,
|
| 454 |
"bottom_up_attributes": bua,
|
| 455 |
+
"rubric_instructions": ri,
|
| 456 |
"rubric_attributes": ra,
|
| 457 |
"top_down_instructions": tdi,
|
| 458 |
"top_down_attributes": tda,
|
docs/capabilities.md
CHANGED
|
@@ -104,7 +104,7 @@ In the UI, each column populates as its pass completes, while later passes show
|
|
| 104 |
|
| 105 |
- An **analysis framework** defines:
|
| 106 |
- bottom-up instructions + attributes
|
| 107 |
-
- rubric attributes
|
| 108 |
- top-down instructions + attributes + codebook categories (optional per-category descriptions)
|
| 109 |
- Frameworks are managed in **Configuration** (create/duplicate/delete + edit the framework).
|
| 110 |
- The **active framework is selected per run** using the dropdown next to the **📊 Analysis** header in:
|
|
|
|
| 104 |
|
| 105 |
- An **analysis framework** defines:
|
| 106 |
- bottom-up instructions + attributes
|
| 107 |
+
- rubric instructions + attributes
|
| 108 |
- top-down instructions + attributes + codebook categories (optional per-category descriptions)
|
| 109 |
- Frameworks are managed in **Configuration** (create/duplicate/delete + edit the framework).
|
| 110 |
- The **active framework is selected per run** using the dropdown next to the **📊 Analysis** header in:
|
frontend/pages/config_view.py
CHANGED
|
@@ -13,6 +13,7 @@ def get_config_view_js() -> str:
|
|
| 13 |
};
|
| 14 |
const DEFAULT_BOTTOM_UP_INSTRUCTIONS = `You are performing bottom-up thematic analysis. Identify the most prominent themes in the conversation, with no a priori concepts; the themes are emergent from the text.`;
|
| 15 |
const DEFAULT_TOP_DOWN_INSTRUCTIONS = `You are performing top-down coding using the provided codebook categories. For each category, extract the most relevant codes/items supported by evidence.`;
|
|
|
|
| 16 |
|
| 17 |
const BOTTOM_UP_DEFAULT_ATTRIBUTES = [
|
| 18 |
"Prefer fewer, higher-confidence items.",
|
|
@@ -92,6 +93,7 @@ Requirements:
|
|
| 92 |
const [patientPersonaDetail, setPatientPersonaDetail] = React.useState(null);
|
| 93 |
const [bottomUpInstructions, setBottomUpInstructions] = React.useState(() => '');
|
| 94 |
const [bottomUpAttributeItems, setBottomUpAttributeItems] = React.useState(() => []);
|
|
|
|
| 95 |
const [rubricAttributeItems, setRubricAttributeItems] = React.useState(() => []);
|
| 96 |
const [topDownInstructions, setTopDownInstructions] = React.useState(() => '');
|
| 97 |
const [topDownAttributeItems, setTopDownAttributeItems] = React.useState(() => []);
|
|
@@ -231,22 +233,27 @@ Requirements:
|
|
| 231 |
})
|
| 232 |
.catch(() => null);
|
| 233 |
};
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
const applyBottomUpDefaults = () => {
|
| 243 |
setBottomUpInstructions(DEFAULT_BOTTOM_UP_INSTRUCTIONS);
|
| 244 |
setBottomUpAttributeItems([...(BOTTOM_UP_DEFAULT_ATTRIBUTES || [])]);
|
| 245 |
};
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
|
|
|
| 250 |
|
| 251 |
const applyTopDownDefaults = () => {
|
| 252 |
setTopDownInstructions(DEFAULT_TOP_DOWN_INSTRUCTIONS);
|
|
@@ -309,13 +316,18 @@ Requirements:
|
|
| 309 |
} else {
|
| 310 |
setBottomUpInstructions(DEFAULT_BOTTOM_UP_INSTRUCTIONS);
|
| 311 |
}
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
|
| 320 |
const tda = Array.isArray(t?.top_down_attributes) ? t.top_down_attributes : [];
|
| 321 |
const tdaClean = tda.map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0);
|
|
@@ -334,15 +346,16 @@ Requirements:
|
|
| 334 |
description: (c && typeof c.description === 'string') ? c.description : '',
|
| 335 |
})));
|
| 336 |
})
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
| 346 |
}, [selectedTemplateId]);
|
| 347 |
|
| 348 |
React.useEffect(() => {
|
|
@@ -428,12 +441,13 @@ Requirements:
|
|
| 428 |
method: 'PUT',
|
| 429 |
headers: { 'Content-Type': 'application/json' },
|
| 430 |
body: JSON.stringify({
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
|
|
|
| 437 |
category_id: (c && typeof c.category_id === 'string') ? c.category_id : '',
|
| 438 |
label: (c && typeof c.label === 'string') ? c.label.trim() : '',
|
| 439 |
description: (c && typeof c.description === 'string') ? c.description.trim() : '',
|
|
@@ -1001,8 +1015,64 @@ Requirements:
|
|
| 1001 |
|
| 1002 |
<div>
|
| 1003 |
<div className="text-sm font-semibold text-slate-700 mb-2">Care experience rubric</div>
|
| 1004 |
-
<div className="
|
| 1005 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1006 |
</div>
|
| 1007 |
</div>
|
| 1008 |
|
|
|
|
| 13 |
};
|
| 14 |
const DEFAULT_BOTTOM_UP_INSTRUCTIONS = `You are performing bottom-up thematic analysis. Identify the most prominent themes in the conversation, with no a priori concepts; the themes are emergent from the text.`;
|
| 15 |
const DEFAULT_TOP_DOWN_INSTRUCTIONS = `You are performing top-down coding using the provided codebook categories. For each category, extract the most relevant codes/items supported by evidence.`;
|
| 16 |
+
const DEFAULT_RUBRIC_INSTRUCTIONS = `You are performing a care experience rubric analysis. Summarize the overall experience and list the key reasons for each bucket.`;
|
| 17 |
|
| 18 |
const BOTTOM_UP_DEFAULT_ATTRIBUTES = [
|
| 19 |
"Prefer fewer, higher-confidence items.",
|
|
|
|
| 93 |
const [patientPersonaDetail, setPatientPersonaDetail] = React.useState(null);
|
| 94 |
const [bottomUpInstructions, setBottomUpInstructions] = React.useState(() => '');
|
| 95 |
const [bottomUpAttributeItems, setBottomUpAttributeItems] = React.useState(() => []);
|
| 96 |
+
const [rubricInstructions, setRubricInstructions] = React.useState(() => '');
|
| 97 |
const [rubricAttributeItems, setRubricAttributeItems] = React.useState(() => []);
|
| 98 |
const [topDownInstructions, setTopDownInstructions] = React.useState(() => '');
|
| 99 |
const [topDownAttributeItems, setTopDownAttributeItems] = React.useState(() => []);
|
|
|
|
| 233 |
})
|
| 234 |
.catch(() => null);
|
| 235 |
};
|
| 236 |
+
const addBottomUpAttribute = () => setBottomUpAttributeItems((prev) => ([...(prev || []), '' ]));
|
| 237 |
+
const updateBottomUpAttribute = (idx, text) => setBottomUpAttributeItems((prev) => (prev || []).map((v, i) => (i === idx ? text : v)));
|
| 238 |
+
const removeBottomUpAttribute = (idx) => setBottomUpAttributeItems((prev) => (prev || []).filter((_, i) => i !== idx));
|
| 239 |
|
| 240 |
+
const addRubricAttribute = () => setRubricAttributeItems((prev) => ([...(prev || []), '' ]));
|
| 241 |
+
const updateRubricAttribute = (idx, text) => setRubricAttributeItems((prev) => (prev || []).map((v, i) => (i === idx ? text : v)));
|
| 242 |
+
const removeRubricAttribute = (idx) => setRubricAttributeItems((prev) => (prev || []).filter((_, i) => i !== idx));
|
| 243 |
+
|
| 244 |
+
const addTopDownAttribute = () => setTopDownAttributeItems((prev) => ([...(prev || []), '' ]));
|
| 245 |
+
const updateTopDownAttribute = (idx, text) => setTopDownAttributeItems((prev) => (prev || []).map((v, i) => (i === idx ? text : v)));
|
| 246 |
+
const removeTopDownAttribute = (idx) => setTopDownAttributeItems((prev) => (prev || []).filter((_, i) => i !== idx));
|
| 247 |
|
| 248 |
const applyBottomUpDefaults = () => {
|
| 249 |
setBottomUpInstructions(DEFAULT_BOTTOM_UP_INSTRUCTIONS);
|
| 250 |
setBottomUpAttributeItems([...(BOTTOM_UP_DEFAULT_ATTRIBUTES || [])]);
|
| 251 |
};
|
| 252 |
|
| 253 |
+
const applyRubricDefaults = () => {
|
| 254 |
+
setRubricInstructions(DEFAULT_RUBRIC_INSTRUCTIONS);
|
| 255 |
+
setRubricAttributeItems([...(RUBRIC_DEFAULT_ATTRIBUTES || [])]);
|
| 256 |
+
};
|
| 257 |
|
| 258 |
const applyTopDownDefaults = () => {
|
| 259 |
setTopDownInstructions(DEFAULT_TOP_DOWN_INSTRUCTIONS);
|
|
|
|
| 316 |
} else {
|
| 317 |
setBottomUpInstructions(DEFAULT_BOTTOM_UP_INSTRUCTIONS);
|
| 318 |
}
|
| 319 |
+
const bua = Array.isArray(t?.bottom_up_attributes) ? t.bottom_up_attributes : [];
|
| 320 |
+
const buaClean = bua.map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0);
|
| 321 |
+
setBottomUpAttributeItems(buaClean.length ? buaClean : [ ...(BOTTOM_UP_DEFAULT_ATTRIBUTES || []) ]);
|
| 322 |
|
| 323 |
+
if (typeof t?.rubric_instructions === 'string' && t.rubric_instructions.trim()) {
|
| 324 |
+
setRubricInstructions(t.rubric_instructions);
|
| 325 |
+
} else {
|
| 326 |
+
setRubricInstructions(DEFAULT_RUBRIC_INSTRUCTIONS);
|
| 327 |
+
}
|
| 328 |
+
const ra = Array.isArray(t?.rubric_attributes) ? t.rubric_attributes : [];
|
| 329 |
+
const raClean = ra.map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0);
|
| 330 |
+
setRubricAttributeItems(raClean.length ? raClean : [ ...(RUBRIC_DEFAULT_ATTRIBUTES || []) ]);
|
| 331 |
|
| 332 |
const tda = Array.isArray(t?.top_down_attributes) ? t.top_down_attributes : [];
|
| 333 |
const tdaClean = tda.map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0);
|
|
|
|
| 346 |
description: (c && typeof c.description === 'string') ? c.description : '',
|
| 347 |
})));
|
| 348 |
})
|
| 349 |
+
.catch(() => {
|
| 350 |
+
setSelectedTemplateDetail(null);
|
| 351 |
+
setBottomUpInstructions(DEFAULT_BOTTOM_UP_INSTRUCTIONS);
|
| 352 |
+
setBottomUpAttributeItems([ ...(BOTTOM_UP_DEFAULT_ATTRIBUTES || []) ]);
|
| 353 |
+
setRubricInstructions(DEFAULT_RUBRIC_INSTRUCTIONS);
|
| 354 |
+
setRubricAttributeItems([ ...(RUBRIC_DEFAULT_ATTRIBUTES || []) ]);
|
| 355 |
+
setTopDownInstructions(DEFAULT_TOP_DOWN_INSTRUCTIONS);
|
| 356 |
+
setTopDownAttributeItems([ ...(TOP_DOWN_DEFAULT_ATTRIBUTES || []) ]);
|
| 357 |
+
setSelectedTemplateCategories([]);
|
| 358 |
+
});
|
| 359 |
}, [selectedTemplateId]);
|
| 360 |
|
| 361 |
React.useEffect(() => {
|
|
|
|
| 441 |
method: 'PUT',
|
| 442 |
headers: { 'Content-Type': 'application/json' },
|
| 443 |
body: JSON.stringify({
|
| 444 |
+
bottom_up_instructions: (bottomUpInstructions || '').trim(),
|
| 445 |
+
bottom_up_attributes: (bottomUpAttributeItems || []).map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0),
|
| 446 |
+
rubric_instructions: (rubricInstructions || '').trim(),
|
| 447 |
+
rubric_attributes: (rubricAttributeItems || []).map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0),
|
| 448 |
+
top_down_instructions: (topDownInstructions || '').trim(),
|
| 449 |
+
top_down_attributes: (topDownAttributeItems || []).map((x) => (typeof x === 'string' ? x.trim() : '')).filter((x) => x.length > 0),
|
| 450 |
+
categories: (selectedTemplateCategories || []).map((c) => ({
|
| 451 |
category_id: (c && typeof c.category_id === 'string') ? c.category_id : '',
|
| 452 |
label: (c && typeof c.label === 'string') ? c.label.trim() : '',
|
| 453 |
description: (c && typeof c.description === 'string') ? c.description.trim() : '',
|
|
|
|
| 1015 |
|
| 1016 |
<div>
|
| 1017 |
<div className="text-sm font-semibold text-slate-700 mb-2">Care experience rubric</div>
|
| 1018 |
+
<div className="flex items-center justify-between gap-3 mb-2">
|
| 1019 |
+
<label className="block text-sm font-semibold text-slate-700">Care experience rubric instructions</label>
|
| 1020 |
+
<button
|
| 1021 |
+
type="button"
|
| 1022 |
+
onClick={applyRubricDefaults}
|
| 1023 |
+
disabled={!(selectedTemplateDetail?.is_default === false)}
|
| 1024 |
+
className="bg-slate-100 hover:bg-slate-200 disabled:hover:bg-slate-100 disabled:opacity-50 text-slate-800 px-3 py-2 rounded-lg text-sm font-semibold transition-all border border-slate-300"
|
| 1025 |
+
>
|
| 1026 |
+
Apply defaults
|
| 1027 |
+
</button>
|
| 1028 |
+
</div>
|
| 1029 |
+
<textarea
|
| 1030 |
+
disabled={!(selectedTemplateDetail?.is_default === false)}
|
| 1031 |
+
className={`w-full border border-slate-300 rounded-lg px-3 py-2 text-sm h-32 ${selectedTemplateDetail?.is_default === false ? 'bg-white' : 'bg-slate-50 text-slate-700'}`}
|
| 1032 |
+
value={rubricInstructions || ''}
|
| 1033 |
+
onChange={(e) => setRubricInstructions(e.target.value)}
|
| 1034 |
+
placeholder="Rubric instructions..."
|
| 1035 |
+
/>
|
| 1036 |
+
|
| 1037 |
+
<div className="flex items-center justify-between gap-3 mt-4 mb-2">
|
| 1038 |
+
<label className="block text-sm font-semibold text-slate-700">Rubric attributes</label>
|
| 1039 |
+
</div>
|
| 1040 |
+
<div className="space-y-2">
|
| 1041 |
+
{(rubricAttributeItems || []).length === 0 ? (
|
| 1042 |
+
<div className="text-sm text-slate-500">No attributes yet.</div>
|
| 1043 |
+
) : (
|
| 1044 |
+
(rubricAttributeItems || []).map((attr, idx) => (
|
| 1045 |
+
<div key={idx} className="flex items-start gap-2">
|
| 1046 |
+
<div className="mt-2 text-xs font-mono text-slate-500 w-10 shrink-0">{String(idx + 1).padStart(2, '0')}</div>
|
| 1047 |
+
<input
|
| 1048 |
+
disabled={!(selectedTemplateDetail?.is_default === false)}
|
| 1049 |
+
className={`flex-1 border border-slate-300 rounded-lg px-3 py-2 text-sm ${selectedTemplateDetail?.is_default === false ? 'bg-white' : 'bg-slate-50 text-slate-600'}`}
|
| 1050 |
+
value={attr || ''}
|
| 1051 |
+
onChange={(e) => updateRubricAttribute(idx, e.target.value)}
|
| 1052 |
+
placeholder="Type an attribute..."
|
| 1053 |
+
/>
|
| 1054 |
+
{selectedTemplateDetail?.is_default === false && (
|
| 1055 |
+
<button
|
| 1056 |
+
type="button"
|
| 1057 |
+
onClick={() => removeRubricAttribute(idx)}
|
| 1058 |
+
className="text-slate-600 hover:text-red-600 px-2 py-2 text-sm"
|
| 1059 |
+
title="Remove attribute"
|
| 1060 |
+
>
|
| 1061 |
+
✕
|
| 1062 |
+
</button>
|
| 1063 |
+
)}
|
| 1064 |
+
</div>
|
| 1065 |
+
))
|
| 1066 |
+
)}
|
| 1067 |
+
{selectedTemplateDetail?.is_default === false && (
|
| 1068 |
+
<button
|
| 1069 |
+
type="button"
|
| 1070 |
+
onClick={addRubricAttribute}
|
| 1071 |
+
className="bg-slate-100 hover:bg-slate-200 text-slate-800 px-3 py-2 rounded-lg text-sm font-semibold transition-all border border-slate-300"
|
| 1072 |
+
>
|
| 1073 |
+
+ Add attribute
|
| 1074 |
+
</button>
|
| 1075 |
+
)}
|
| 1076 |
</div>
|
| 1077 |
</div>
|
| 1078 |
|