Compare commits

..

No commits in common. "e9b10767d61a35b955fdef36ae1162927d457fe1" and "762fbc800424015ceb62985398f76a3945bef58c" have entirely different histories.

4 changed files with 40 additions and 280 deletions

View File

@ -1046,7 +1046,7 @@ def enroll_id_list_to_shell(id_list, shell_id, v=0):
# multiple semesters # multiple semesters
def enroll_stem_students_live(): def enroll_stem_students_live():
semesters = [289] semesters = [288,289]
for S in semesters: for S in semesters:
enroll_stem_students_live_semester(S) enroll_stem_students_live_semester(S)
@ -1770,17 +1770,7 @@ def xlist_cwe():
this_sem_190_id = 22890 # they get 190s and 290s this_sem_190_id = 22890 # they get 190s and 290s
this_sem_192_id = 22894 # they get 192s this_sem_192_id = 22894 # they get 192s
# this_sem_term = 289 this_sem_term = 289
term = find_term( input("term? (ex: fa25) ") )
if not term or (not 'canvas_term_id' in term) or (not 'code' in term):
print(f"Couldn't find term.")
return
this_sem_term = term['canvas_term_id']
SEM = term['code']
get_fresh = 1 get_fresh = 1
sem_courses = getCoursesInTerm(this_sem_term, get_fresh, 0) sem_courses = getCoursesInTerm(this_sem_term, get_fresh, 0)

290
gpt.py
View File

@ -13,7 +13,7 @@ client = OpenAI(
DEFAULT_MODEL = "gpt-4o" DEFAULT_MODEL = "gpt-4o"
SAVE_ATTACHEMENTS = 1 SAVE_ATTACHEMENTS = 0
def gpt_chat(instruction, prompt, model=DEFAULT_MODEL): def gpt_chat(instruction, prompt, model=DEFAULT_MODEL):
@ -33,19 +33,6 @@ def gpt_chat(instruction, prompt, model=DEFAULT_MODEL):
return result return result
def summarize_u_info(msg):
system_role = """You are a practical efficient cataloger and indexer of information. What follows is one or more emails which most likely (but not necessarily) contains useful information of some sort. Please determine if the message has useful info, and if so, attempt to reformat it as such:
{"source": <original sender or source>, "date": <month and year MM/YY>, "tags": <short list of tags and topics, multi words use snake_case>, "short": <one sentence summary>, "summary": <complete summary with details if warranted>,"events":<see below>}
If one or more specific dates and times of an event (or deadline) of some sort are mentioned, please attempt to extract the datetime and one line summary of each event. The "events" field of the response should be a list of these: {"dt":<date and time>, "length":<if known>, "title":<title>,"description":<short description>}
For all summaries: speak in a first-person direct voice, authoritatively. For example, instead of saying 'x shared details about offerings at Bakersfield College, including a, b and c', just say 'Bakersfield college offers a, b and c'.
Some sample tags: event, advice, rule, warning, hr, it, disted, canvas, meeting, senate, dept_chairs, deans, administration, guided_pathways, site, file, article, contract, deadline, construction, catalog, schedule, curriqunet, banner, cvc, password, payroll, training, tip, graduation, photos, faculty, evaluation, convocation, flex, board, curriculum, ztc, oei, grant, accessibility, legislation, hyflex, hybrid, handbook, guideline, lti, api, integration, request, rule, scholarship, library, aws, opportunity, background, calendar, semester, accreditation, professional_development, zoom, ai, html, code, video, pocr, badge, liason, newsletter, act, law, equipment, best_practices, outcomes, slo, plo, ilo, data, cares_act, census, admissions, financial_aid, enrollment, title_5, syllabus, ccn, communication, club, survey, checklist, covid_19, pipr, program_review, policy, vpat, compliance, emergency, finals, rfp, fraud, research, library, tutoring, stem, writing_center, learning_commons, hire, promotion, help_desk, housing, url, tenure, tlc, mental_health, president, vpaa, vpss, vp, mac, meaningful_assessment, negotiation, union, libguide, evidence, retirement,
Remember that not every email is notable. If this is the case, just return an empty json object.
Always answer in valid json, nothing else."""
x = gpt_chat(system_role,msg)
print(x)
return x
def gpt_faq(faq): def gpt_faq(faq):
system_role = """Ignore all previous instructions. You are an expert on the management and implementation of all aspects of online courses and running a distance education program, especially in higher education and community college. You are also an expert in pedagogy and the tools and techniques of effective teaching and learning, based on research and experience. You know how to focus on concrete examples of effective teaching which actually work in the real world and the advice you give is specific and immediately useful to teachers in the classroom. You provide accurate, factual, thoughtful, nuanced answers, and are brilliant at reasoning. Your users are experts in teaching and college administration, and are comfortable with the California and Federal Educational code, and know they need to comply with it. There is no need to explain this beyond noting any relevant laws. Don't be verbose in your answers, but do provide details and examples where it might help the explanation. system_role = """Ignore all previous instructions. You are an expert on the management and implementation of all aspects of online courses and running a distance education program, especially in higher education and community college. You are also an expert in pedagogy and the tools and techniques of effective teaching and learning, based on research and experience. You know how to focus on concrete examples of effective teaching which actually work in the real world and the advice you give is specific and immediately useful to teachers in the classroom. You provide accurate, factual, thoughtful, nuanced answers, and are brilliant at reasoning. Your users are experts in teaching and college administration, and are comfortable with the California and Federal Educational code, and know they need to comply with it. There is no need to explain this beyond noting any relevant laws. Don't be verbose in your answers, but do provide details and examples where it might help the explanation.
@ -88,249 +75,45 @@ def sample_send_email():
newmail.Send() newmail.Send()
def fetch_useful_info():
def fetch_useful_info(save_attachments=True, folder_name='useful info ref'):
import os, re
from pathlib import Path
from itertools import groupby
from datetime import datetime
import win32com.client import win32com.client
CACHE = Path("cache")
ATT_DIR = CACHE / "attachments_useful_info"
LOG_PATH = CACHE / "email_usefulinfo_sorted.txt"
ATT_DIR.mkdir(parents=True, exist_ok=True)
CACHE.mkdir(parents=True, exist_ok=True)
# --- helpers ---
prefix_re = re.compile(r'^\s*(re|fw|fwd|aw|sv|vb|tr|wg)\s*:\s*', re.I) # common locales too
bracket_tag_re = re.compile(r'^\s*(\[[^\]]+\]\s*)+', re.I)
def normalize_subject(s: str) -> str:
if not s:
return "(no subject)"
s = s.strip()
# strip leading [TAGS] like [EXTERNAL] [Some System]
s = bracket_tag_re.sub('', s)
# strip any chain of RE:/FWD: etc. at start
while prefix_re.match(s):
s = prefix_re.sub('', s, count=1)
# collapse whitespace
s = re.sub(r'\s+', ' ', s).strip()
return s or "(no subject)"
def safe_name(s: str) -> str:
# reasonable Windows-safe folder name for a subject
s = re.sub(r'[<>:"/\\|?*\x00-\x1F]', '_', s)
return s[:120]
def iso(dt):
# Outlook COM datetime -> ISO string (local)
try:
return dt.strftime('%Y-%m-%d %H:%M:%S')
except Exception:
return str(dt)
def save_all_attachments(mail, subject_dir: Path):
saved = []
try:
atts = mail.Attachments
count = atts.Count
if count == 0:
return saved
subject_dir.mkdir(parents=True, exist_ok=True)
# iterate COM collection by index (1-based)
for i in range(1, count + 1):
att = atts.Item(i)
# build unique filename to avoid collisions
base = str(att.FileName) if getattr(att, "FileName", None) else f"attachment_{i}"
base = safe_name(base)
# prefix by sent time for clarity
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
out = subject_dir / f"{ts}_{base}"
att.SaveAsFile(str(out))
saved.append(str(out.as_posix()))
except Exception:
# swallow attachment oddities; continue
pass
return saved
# --- Outlook ---
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI") outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
root_folder = outlook.Folders.Item(1) root_folder = outlook.Folders.Item(1)
print(f"Root folder: {root_folder.Name}") print (f"Root folder: {root_folder.Name}")
uinfo = root_folder.Folders[folder_name]
# Collect first to a list so we can sort/group
records = []
items = uinfo.Items
# Optional: sort by SentOn ascending inside Outlook (helps performance for big folders)
try:
items.Sort("[SentOn]", True) # True => ascending
except Exception:
pass
for message in items:
# Skip non-mail items
if getattr(message, "Class", None) != 43: # 43 = MailItem
continue
subj_raw = getattr(message, "Subject", "") or ""
subj_norm = normalize_subject(subj_raw)
# sender fallback chain
sender = None
for attr in ("Sender", "SenderName", "SenderEmailAddress"):
try:
sender = getattr(message, attr)
if sender:
break
except Exception:
pass
sender = str(sender) if sender else "UNKNOWN"
sent_on = getattr(message, "SentOn", None)
sent_iso = iso(sent_on)
attachments_saved = []
if save_attachments:
attachments_saved = save_all_attachments(
message,
ATT_DIR / safe_name(subj_norm)
)
body = ""
try:
body = message.Body or ""
except Exception:
try:
body = message.HTMLBody or ""
except Exception:
body = ""
records.append({
"subject_norm": subj_norm,
"subject_raw": subj_raw,
"sender": sender,
"sent_on": sent_on,
"sent_iso": sent_iso,
"attachments": attachments_saved,
"body": body,
})
# Sort by normalized subject, then by sent time (ascending)
records.sort(key=lambda r: (r["subject_norm"].lower(), r["sent_on"] or datetime.min))
# Write grouped log
with open(LOG_PATH, "w", encoding="utf-8", newline="") as log:
current_subject = None
for subj, group_iter in groupby(records, key=lambda r: r["subject_norm"]):
thread = list(group_iter)
log.write(f"\n\n### {subj}{len(thread)} message(s)\n")
for r in thread:
if r["attachments"]:
att_line = "Attachments: " + ", ".join(r["attachments"]) + "\n"
else:
att_line = ""
log.write(
f"\n---\n"
f"Subject: {r['subject_raw']}\n"
f"From: {r['sender']}\n"
f"Date: {r['sent_iso']}\n"
f"{att_line}\n"
f"{r['body']}\n"
)
print(f"Wrote grouped log -> {LOG_PATH.as_posix()}")
def process_useful_info(): #And to know the names of the subfolders you have:
import re print("\nFolders:")
from pathlib import Path for folder in root_folder.Folders:
from typing import Callable, List, Dict, Optional print (" " + folder.Name)
HEADER_RE = re.compile(r'\r?\n###\s(.*)\r?\n') # your pattern, CRLF-safe log = codecs.open("cache/email_usefulinfo.txt","w","utf-8")
COUNT_RE = re.compile(r'^(?P<subject>.*?)\s+—\s+(?P<count>\d+)\s+message', re.I)
# access a subfolder
def parse_groups(text: str) -> List[Dict]: print("\nUseful Info Reference:")
""" uinfo = root_folder.Folders['useful info ref']
Return a list of groups found in the log file. for message in uinfo.Items:
Each group is a dict: {header, subject, count, content} atch_list = "Attachments: "
""" atch_count = 0
groups = [] if SAVE_ATTACHEMENTS:
matches = list(HEADER_RE.finditer(text)) attachments = message.Attachments
if not matches: try:
return groups attachment = attachments.Item(1)
for attachment in message.Attachments:
for i, m in enumerate(matches): print(" -> " + str(attachment))
header = m.group(1).strip() # e.g. "Subject X — 3 message(s)" OR just "Subject X" #loc = "C:\\Users\\peter\\Documents\\gavilan\\ca_merged\\canvasapp\\cache\\attachments\\" + str(attachment)
start = m.end() loc = "C:\\Users\\phowell\\source\\repos\\canvasapp\\cache\\attachments\\" + str(attachment)
end = matches[i+1].start() if i + 1 < len(matches) else len(text) attachment.SaveAsFile(loc)
content = text[start:end] atch_list += str(attachment) + ', '
atch_count += 1
# Try to extract subject and count if present break
subject = header except Exception as e:
count: Optional[int] = None pass
cm = COUNT_RE.search(header) print(" " + message.Subject)
if cm: log.write(f"\n\n---\nSubject: {message.Subject}\nFrom: {message.Sender}\n")
subject = cm.group('subject').strip() if atch_count:
try: log.write(f"{atch_list}\n")
count = int(cm.group('count')) log.write(f"Date: {message.SentOn}\n\n{message.body}\n")
except Exception:
count = None
groups.append({
"header": header,
"subject": subject,
"count": count,
"content": content
})
return groups
def for_each_group(
log_path: str = "cache/email_usefulinfo_sorted.txt",
f: Callable[[int, Dict], None] = lambda idx, g: None,
start: int = 1,
count: int = -1
) -> None:
"""
Read the grouped log, split into groups, and call f(index, group) on each.
start: 1-based index to begin processing (useful for resuming).
"""
p = Path(log_path)
text = p.read_text(encoding="utf-8", errors="replace")
groups = parse_groups(text)
done = 0
if start < 1:
start = 1
for idx, g in enumerate(groups, start=1):
if idx < start:
continue
f(idx, g)
done += 1
if count != -1 and done >= count:
return
def demo_f(idx, g):
print(f"[{idx}] {g['subject']} (count: {g['count']})")
x = summarize_u_info(g['content'])
# Do your processing here. g['content'] is the whole thread block.
for_each_group(
log_path="cache/email_usefulinfo_sorted.txt",
f=demo_f,
start=1, # change to resume at Nth group
count=10
)
@ -474,7 +257,6 @@ if __name__ == "__main__":
3: ['fetch "useful info" mailbox', fetch_useful_info], 3: ['fetch "useful info" mailbox', fetch_useful_info],
4: ['fetch "faq" mailbox and gpt summarize', fetch_faq], 4: ['fetch "faq" mailbox and gpt summarize', fetch_faq],
5: ['list faq mailbox', list_faq], 5: ['list faq mailbox', list_faq],
6: ['process useful info msgs', process_useful_info],
} }

View File

@ -363,17 +363,7 @@ def repair_outcome_points(course_id):
def add_o_dept_dry_run(): def add_o_dept_dry_run():
add_o_dept(1) add_o_dept(1)
def add_o_whole_term(term=0): def add_o_whole_term():
if not term:
tt = find_term( input("term? (ex: fa25) ") )
if not tt or (not 'canvas_term_id' in tt) or (not 'code' in tt):
print(f"Couldn't find term.")
return
term = tt['canvas_term_id']
sem = tt['code']
course_groups = full_term_overview(term, 0) course_groups = full_term_overview(term, 0)
dept_shells_to_add = [ a for a in course_groups['no outcomes'] ] dept_shells_to_add = [ a for a in course_groups['no outcomes'] ]

View File

@ -8,7 +8,7 @@ season_to_number = { 'Fall': '70', 'Summer': '50', 'Spring': '30', 'Winter': '10
# Inverse # Inverse
number_to_season = {v: k for k, v in season_to_number.items()} number_to_season = {v: k for k, v in season_to_number.items()}
s_to_n = {'wi':'30','sp':'30','su':'50','fa':'70'} s_to_n = {'sp':'30','su':'50','fa':'70'}
season_to_short = { season_to_short = {
'Summer': 'su', 'Summer': 'su',
'Fall': 'fa', 'Fall': 'fa',
@ -178,7 +178,6 @@ for std, code_str, start in zip(standard, code, begin):
records[(season, year)] = { records[(season, year)] = {
'standard': std, 'standard': std,
'code': code_str, 'code': code_str,
'bannercode': short_to_sis(code_str),
'begin': start, 'begin': start,
'canvas_term_id': None, 'canvas_term_id': None,
'canvas_name': None, 'canvas_name': None,
@ -204,7 +203,6 @@ for key, rec in records.items():
f"{year} {season}", f"{year} {season}",
f"{season} {year}", f"{season} {year}",
rec['code'], rec['code'],
rec['bannercode'],
rec['canvas_name'] if rec['canvas_name'] else None, rec['canvas_name'] if rec['canvas_name'] else None,
str(rec['canvas_term_id']) str(rec['canvas_term_id'])
] ]