Skip to content
Merged
5 changes: 5 additions & 0 deletions src/prerna/collaboration/BrainOrgDomains.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ private Org(Set<String> domains) {
this.domains = domains;
}

/** The organisation's domains, as saved. */
public Set<String> domains() {
return Set.copyOf(domains);
}

/**
* The domain, or one it is a subdomain of, belongs to the owner's organisation.
*/
Expand Down
128 changes: 119 additions & 9 deletions src/prerna/collaboration/BrainProfileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

import prerna.auth.AccessToken;
import prerna.auth.User;
import prerna.io.connector.mail.MailRecipientRules;
import prerna.util.RuntimeTimeContext;
import prerna.util.Utility;

Expand Down Expand Up @@ -95,7 +96,8 @@ public static Map<String, Object> getProfile(String ownerId, String ownerType) {
Map<String, Object> profile = CollaborationDbUtils
.queryOne(
"SELECT DISPLAY_NAME, EMAIL, ORG, ROLE, ROLE_STATE, ROLE_NOTE, TIMEZONE, WORKING_HOURS_JSON, "
+ "STYLE_SUMMARY, STYLE_STATE, STYLE_EXAMPLES_JSON FROM BRAIN_PROFILE "
+ "STYLE_SUMMARY, STYLE_STATE, STYLE_EXAMPLES_JSON, STYLE_VARIANTS_JSON, STYLE_PENDING_JSON, "
+ "STYLE_LEARNED_AT FROM BRAIN_PROFILE "
+ "WHERE OWNER_ID = ? AND OWNER_TYPE = ?",
BrainProfileUtils::mapProfile, ownerId, ownerType);
if (profile != null) {
Expand All @@ -104,7 +106,7 @@ public static Map<String, Object> getProfile(String ownerId, String ownerType) {
return profile;
}

// who the assistant writes as; role and style only once the owner set or confirmed them
// Owner identity stays in the general prompt; writing style is retrieved only when drafting.
@SuppressWarnings("unchecked")
public static String promptBlock(User user) {
Map<String, Object> profile;
Expand All @@ -117,7 +119,8 @@ public static String promptBlock(User user) {
if (profile == null) {
return null;
}
StringBuilder out = new StringBuilder("## Owner\nThe owner you work for. Write and sign as them.");
StringBuilder out = new StringBuilder(
"## Owner\nThe owner you work for. Write and sign as them when drafting on their behalf.");
appendField(out, "Name", profile.get("name"));
appendField(out, "Email", profile.get("email"));
appendField(out, "Organization", profile.get("org"));
Expand All @@ -126,13 +129,50 @@ public static String promptBlock(User user) {
if (role != null && YOU.equals(role.get("source"))) {
appendField(out, "Role", role.get("value"));
}
Map<String, Object> style = (Map<String, Object>) profile.get("style");
if (style != null && Boolean.TRUE.equals(style.get("confirmed"))) {
appendField(out, "Writing style", style.get("summary"));
}
return out.toString();
}

/** Current, confirmed writing guidance; unknown recipients use only the general style. */
public static Map<String, Object> getWritingStyle(User user, List<String> recipients) {
List<String> addresses = MailRecipientRules.validate(recipients);
Map<String, Object> profile = getProfile(user);
Map<String, Object> out = new LinkedHashMap<>();
out.put("confirmed", false);
out.put("audience", "unknown");
out.put("summary", "");
out.put("examples", List.of());
if (profile == null || !(profile.get("style") instanceof Map<?, ?> style)
|| !Boolean.TRUE.equals(style.get("confirmed"))) {
return out;
}
out.put("confirmed", true);
String summary = style.get("summary") instanceof String text ? text.strip() : "";
out.put("summary", summary.substring(0, Math.min(summary.length(), MAX_SUMMARY_CHARS)));
out.put("examples", cleanExamples(style.get("examples")));
if (addresses.isEmpty()) {
return out;
}
try {
Pair<String, String> owner = CollaborationDbUtils.ownerOf(user);
String email = CollaborationDbUtils.asString(profile.get("email"));
BrainOrgDomains.Org org = BrainOrgDomains.load(owner.getValue0(), owner.getValue1(),
email == null || !email.contains("@") ? null : email.substring(email.lastIndexOf('@') + 1));
if (!org.domains().isEmpty()) {
String audience = addresses.stream()
.allMatch(address -> org.isMine(address.substring(address.lastIndexOf('@') + 1)))
? "internal" : "external";
out.put("audience", audience);
Object variant = cleanVariants(style.get("variants")).get(audience);
if (variant != null) {
out.put("variant", variant);
}
}
} catch (RuntimeException e) {
classLogger.warn("Could not resolve the writing style audience; using the general style", e);
}
return out;
}

private static void appendField(StringBuilder out, String label, Object value) {
if (value instanceof String text && !text.isBlank()) {
out.append("\n- ").append(label).append(": ").append(text.strip());
Expand Down Expand Up @@ -168,14 +208,26 @@ public static Map<String, Object> saveProfile(User user, Map<String, Object> cha
if (style.containsKey("summary")) {
CollaborationDbUtils.addSet(sets, params, "STYLE_SUMMARY",
CollaborationDbUtils.asString(style.get("summary")));
CollaborationDbUtils.addSet(sets, params, "STYLE_STATE", YOU);
// an accepted proposal stays "learned and confirmed"; what the owner typed is "you"; a reset
// style says confirmed false and goes back to unconfirmed whatever the summary is
boolean accepted = Boolean.TRUE.equals(style.get("confirmed")) && LEARNED.equals(style.get("source"));
String state = Boolean.FALSE.equals(style.get("confirmed")) ? LEARNED : accepted ? CONFIRMED : YOU;
CollaborationDbUtils.addSet(sets, params, "STYLE_STATE", state);
} else if (style.containsKey("confirmed")) {
CollaborationDbUtils.addSet(sets, params, "STYLE_STATE",
Boolean.TRUE.equals(style.get("confirmed")) ? CONFIRMED : LEARNED);
}
if (style.containsKey("examples")) {
CollaborationDbUtils.addSet(sets, params, "STYLE_EXAMPLES_JSON",
CollaborationDbUtils.toJson(style.get("examples")));
CollaborationDbUtils.toJson(cleanExamples(style.get("examples"))));
}
if (style.containsKey("variants")) {
CollaborationDbUtils.addSet(sets, params, "STYLE_VARIANTS_JSON",
CollaborationDbUtils.toJson(cleanVariants(style.get("variants"))));
}
// only the proposal's review clears it; a proposal is written by the style learning, never by a client
if (style.containsKey("pending") && isEmpty(style.get("pending"))) {
CollaborationDbUtils.addSet(sets, params, "STYLE_PENDING_JSON", null);
}
}
List<String> vips = changes.get("vips") instanceof List
Expand Down Expand Up @@ -239,6 +291,16 @@ private static Map<String, Object> mapProfile(ResultSet rs) throws SQLException
style.put("confirmed", YOU.equals(styleState) || CONFIRMED.equals(styleState));
style.put("examples",
CollaborationDbUtils.parseList(CollaborationDbUtils.getString(rs, "STYLE_EXAMPLES_JSON")));
style.put("variants", cleanVariants(
CollaborationDbUtils.parseMap(CollaborationDbUtils.getString(rs, "STYLE_VARIANTS_JSON"))));
String pending = CollaborationDbUtils.getString(rs, "STYLE_PENDING_JSON");
if (pending != null && !pending.isBlank()) {
style.put("pending", CollaborationDbUtils.parseMap(pending));
}
java.sql.Timestamp learnedAt = rs.getTimestamp("STYLE_LEARNED_AT");
if (learnedAt != null) {
style.put("learnedAt", learnedAt.toInstant().toString());
}

Map<String, Object> profile = new LinkedHashMap<>();
profile.put("id", "me");
Expand All @@ -252,6 +314,54 @@ private static Map<String, Object> mapProfile(ResultSet rs) throws SQLException
return profile;
}

/** The audiences a style variant can be written for. */
static final List<String> AUDIENCES = List.of("internal", "external");
static final int MAX_EXAMPLES = 5;
static final int MAX_EXAMPLE_CHARS = 600;
static final int MAX_SUMMARY_CHARS = 1500;

private static boolean isEmpty(Object value) {
return value == null || (value instanceof String text && text.isBlank())
|| (value instanceof Map<?, ?> map && map.isEmpty());
}

// only short, non-blank strings, so a style can never smuggle in a large block
static List<String> cleanExamples(Object value) {
List<String> out = new ArrayList<>();
if (value instanceof List<?> list) {
for (Object item : list) {
if (item instanceof String text && !text.isBlank() && out.size() < MAX_EXAMPLES) {
String trimmed = text.strip();
out.add(trimmed.length() > MAX_EXAMPLE_CHARS ? trimmed.substring(0, MAX_EXAMPLE_CHARS) : trimmed);
}
}
}
return out;
}

// {internal, external}, each {summary, examples}; a variant with no summary is dropped
static Map<String, Object> cleanVariants(Object value) {
Map<String, Object> out = new LinkedHashMap<>();
if (!(value instanceof Map<?, ?> map)) {
return out;
}
for (String audience : AUDIENCES) {
if (!(map.get(audience) instanceof Map<?, ?> variant)) {
continue;
}
String summary = variant.get("summary") instanceof String text ? text.strip() : "";
if (summary.isEmpty()) {
continue;
}
Map<String, Object> entry = new LinkedHashMap<>();
entry.put("summary",
summary.length() > MAX_SUMMARY_CHARS ? summary.substring(0, MAX_SUMMARY_CHARS) : summary);
entry.put("examples", cleanExamples(variant.get("examples")));
out.put(audience, entry);
}
return out;
}

private static List<String> getVipIds(String ownerId, String ownerType) {
return CollaborationDbUtils
.query("SELECT PERSON_ID FROM BRAIN_PERSON WHERE OWNER_ID = ? AND OWNER_TYPE = ? AND IS_VIP = ? "
Expand Down
Loading
Loading