"""A reading page for regrouped predicaments: does each group read as one predicament? cd v2 && python3 ../groups_page.py predicament_embeddings.json predicaments_clean.json > groups-k60-s24.html Clusters the embeddings at k = 60, seed 24 (the seed the first review used, kept apart from the measurement seeds 11-13), keeps the candidate groups (at least 5 scenes, 4 stories, no story over 40%), and lists them most cohesive first. Each scene shows its predicament sentence, the scene's original summary underneath, and its story. The reviewer marks each group: one predicament, a few mixed, or not a predicament, with an optional name and note. Marks are kept in the browser and saved as a JSON file. Naming a kept group ("A ___ wants ___ from ___") is left for the full review page, once groups read well. No inference. """ import collections, html, json, pathlib, re, sys from topic_words import TOPIC_WORDS emb_path, text_path = sys.argv[1], sys.argv[2] K, SEED = 60, 24 sys.argv = ['measure.py', emb_path] exec(pathlib.Path(__file__).with_name('measure.py').read_text(encoding='utf-8').split("print(f'{path.name}")[0]) # now defined: chunks, V, n, story, kmeans, describe, MIN_SIZE, MIN_STORIES, MAX_DOMINANT pred = {x['chunk']: x['summary'] for x in json.loads(pathlib.Path(text_path).read_text(encoding='utf-8'))['summaries']} orig = {x['chunk']: x['summary'] for x in json.loads(pathlib.Path('summaries_clean.json').read_text(encoding='utf-8'))['summaries']} def cross_story_cohesion(group): tot = cnt = 0 for a in group: for b in group: if a < b and story[a] != story[b]: tot += sum(x * y for x, y in zip(V[a], V[b])); cnt += 1 return tot / cnt if cnt else 0.0 groups = [] for g in kmeans(K, SEED): size, nst, dom, _ = describe(g) if size < MIN_SIZE or nst < MIN_STORIES or dom > MAX_DOMINANT: continue topics = collections.Counter(w for i in g for w in set(re.findall(r"[a-zé]+", orig.get(i, '').lower())) & TOPIC_WORDS) word, hits = topics.most_common(1)[0] if topics else ('-', 0) groups.append({'members': g, 'stories': nst, 'cohesion': cross_story_cohesion(g), 'topic': (word, hits / size)}) groups.sort(key=lambda x: -x['cohesion']) run = f'{pathlib.Path(emb_path).stem}-k{K}-s{SEED}' sections = [] for gi, grp in enumerate(groups, 1): rows = ''.join( f'
  • {html.escape(pred.get(i, ""))}' f'{html.escape(orig.get(i, ""))}' f'{html.escape(chunks[i]["title"].title())}
  • ' for i in grp['members']) word, share = grp['topic'] sections.append(f'''

    Group {gi}

    {len(grp["members"])} scenes from {grp["stories"]} stories · cohesion {grp["cohesion"]:.3f} · most common job, relationship or place word in the original summaries: {html.escape(word)}, {share:.0%} of scenes

      {rows}
    ''') index = ''.join(f'{i}' for i in range(1, len(groups) + 1)) data = json.dumps({'run': run, 'groups': [{'group': i, 'chunks': grp['members']} for i, grp in enumerate(groups, 1)]}) print(f''' Predicament groups

    Predicament groups

    Every scene was re-described as its main person's predicament and the scenes were regrouped by those sentences. Each line below is one scene: its predicament, the scene's original summary under it, and its story. Read a group and mark whether most of its scenes are one predicament (the same kind of trouble, and wanting the same kind of way out), two or three mixed together, or not a predicament (held together by wording, a topic, or nothing). Most cohesive groups first; the first ten or so are enough to judge. Marks save in this browser as you go; "Save marks" downloads them for the program.

    {index}
    {"".join(sections)}
    ''')